Skip to content

Conversation

@uson1004
Copy link
Member

@uson1004 uson1004 commented Dec 27, 2025

κ°œμš”

μ‹ μ²­ νŽ˜μ΄μ§€ κ΅¬ν˜„

μž‘μ—…μ‚¬ν•­

μΆ”κ°€ 둜 ν•  말

Summary by CodeRabbit

  • New Features

    • Full remain application flow with selection, time-window notice, apply action, and cross-screen result sharing
    • Voting feature: vote list, detailed vote screens, multiple vote types, candidate selection, and submission UI
    • New design system controls and icons (cards, tabs/tab-row, floating notice, layered button, shadow utilities)
    • Cross-screen ResultStore and date formatting helper
  • Bug Fixes

    • Clears caches on authentication reissue failure (401)
  • Improvements

    • Meal screen now supports vertical scrolling
    • Minimum Android SDK raised to 26

✏️ Tip: You can customize this high-level summary in your review settings.

@uson1004 uson1004 requested a review from a team December 27, 2025 12:30
@uson1004 uson1004 self-assigned this Dec 27, 2025
@uson1004 uson1004 added the feat μƒˆλ‘œμš΄ κΈ°λŠ₯을 μΆ”κ°€ ν•  경우 label Dec 27, 2025
@uson1004 uson1004 linked an issue Dec 27, 2025 that may be closed by this pull request
@coderabbitai
Copy link

coderabbitai bot commented Dec 27, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

πŸ“ Walkthrough

Walkthrough

Adds Remain and Vote features with navigation keys, a CompositionLocal-backed ResultStore for cross-destination results, new models/mappers/repos/DI, multiple Compose UI components and screens, ViewModels, network signature updates, design-system primitives/resources, and small build/minSdk adjustments.

Changes

Cohort / File(s) Summary
App navigation & result plumbing
app/src/dev/.../DmsApp.kt, core/ui/src/dev/.../ResultStore.kt, feature/.../application/navigation/ApplicationRoute.kt, feature/.../remain/navigation/RemainApplicationRoute.kt
Adds VoteScreenNav and RemainScreenNav NavKeys; introduces ResultStore (CompositionLocal + rememberSaveable) and provides it around NavDisplay; ApplicationRoute now accepts navigation/snackbar callbacks and pushes nav keys / proxies snackbars.
Design system UI & resources
core/design-system/.../card/DmsApplicationCard.kt, core/design-system/.../tab/DmsTab.kt, core/design-system/.../tab/DmsTabRow.kt, core/design-system/.../modifier/DmsShadowModifier.kt, core/design-system/.../button/Button.kt, core/design-system/src/dev/res/drawable/ic_approve.xml, core/design-system/src/dev/res/drawable/ic_oppose.xml
New UI primitives: application card, tab/tab-row, shadow modifier enum/extension, layered button; adds approve/oppose vector drawables.
Remain feature β€” models, mappers, repo, DI
data/.../remain/model/*.kt, data/.../remain/mapper/RemainsMapper.kt, data/.../remain/repository/RemainsRepository.kt, data/.../remain/repository/RemainsRepositoryImpl.kt, data/.../remain/di/RepositoryModule.kt
Adds domain models, mapper functions, repository interface and runCatching-backed impl, and Hilt module binding RemainsRepositoryImpl β†’ RemainsRepository.
Remain UI & VM
feature/.../remain/ui/RemainApplicationScreen.kt, feature/.../remain/viewmodel/RemainApplicationViewModel.kt, feature/.../remain/ui/component/DmsFloatingNotice.kt
New RemainApplication screen, ViewModel with application-time validation, selection/change flows, snackbar feedback, floating notice component, and a route wrapper.
Voting feature β€” models, mappers, repo, API changes
data/.../voting/model/*.kt, data/.../voting/mapper/VotingMapper.kt, data/.../voting/repository/VotingRepository.kt, data/.../voting/repository/VotingRepositoryImpl.kt, network/.../voting/apiservice/VotingApiService.kt, network/.../voting/datasource/*
Adds voting models, mappers, repository interface and impl; network layer changed: fetchCreateVotingItem now returns Result<Unit> (API, datasource, impl updated).
Vote UI, routes, components, VM
feature/.../vote/navigation/VoteRoute.kt, feature/.../vote/ui/VoteScreen.kt, feature/.../vote/viewmodel/VoteViewModel.kt, feature/.../vote/ui/component/*
Adds VoteRoute and Vote composable with ViewModel handling side-effects (snackbars, conflicts), plus UI components (TitleContent, VoteItemContent, Option/Student/Approval components) and state/effects.
Application feature β€” UI, VM, components
feature/.../application/ui/ApplicationScreen.kt, feature/.../application/viewmodel/ApplicationViewModel.kt, feature/.../application/ui/component/ApplicationContent.kt, feature/.../application/ui/component/VoteContent.kt
ApplicationRoute/Application UI now accept navigation/snackbar callbacks and read ResultStore; ApplicationViewModel loads/persists applied remain title and fetches votes; adds ApplicationContent and VoteContent.
Utilities & infra
core/ui/src/dev/.../util/Date.kt, core/jwt/src/main/.../JwtProviderImpl.kt, feature/.../meal/ui/MealScreen.kt, data/build.gradle.kts, feature/build.gradle.kts, app/build.gradle.kts, buildSrc/src/main/kotlin/ProjectProperties.kt, gradle/libs.versions.toml
Adds LocalDateTime.toDateString(); clears caches on JWT reissue 401; enables vertical scrolling on MealScreen; adds Kotlin serialization plugin; feature adds core network dependency; disables core library desugaring in app; bumps minSdk 24β†’26 (ProjectProperties + libs.versions).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ApplicationScreen
    participant ResultStore
    participant NavDisplay
    participant RemainScreen
    participant RemainVM
    participant RemainsRepository

    User->>ApplicationScreen: open Application tab
    ApplicationScreen->>ResultStore: read "remain_application_result"
    Note right of ApplicationScreen `#e6f7ff`: show applied badge if present

    User->>ApplicationScreen: tap "μž”λ₯˜" card
    ApplicationScreen->>NavDisplay: push RemainScreenNav
    NavDisplay->>RemainScreen: render RemainApplication

    RemainScreen->>RemainVM: load options & time
    RemainVM->>RemainsRepository: fetchRemainsOptions()/fetchRemainsApplicationTime()
    RemainsRepository-->>RemainVM: returns data

    User->>RemainScreen: select option & confirm
    RemainScreen->>RemainVM: changeRemainsOption()
    RemainVM->>RemainsRepository: updateRemainsOption(id)
    RemainsRepository-->>RemainVM: success
    RemainVM->>ResultStore: set("remain_application_result", selectedTitle)
    RemainVM->>NavDisplay: navigateBack()
    NavDisplay->>ApplicationScreen: resumed
    ApplicationScreen->>ResultStore: read "remain_application_result"
    ResultStore-->>ApplicationScreen: selectedTitle
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • JunJaBoy
  • parkuiery

Poem

🐰 I hopped through code with nimble feet,

Cards and tabs aligned so neat,
A little store to share the cheer,
From Remain to Vote I bounce near,
Snackbars clap β€” the features meet!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
βœ… Passed checks (2 passed)
Check name Status Explanation
Description Check βœ… Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check βœ… Passed The title 'πŸ”€ :: μ‹ μ²­ νŽ˜μ΄μ§€ κ΅¬ν˜„' describes implementing an application page, which is directly supported by the substantial changes across multiple files adding application navigation, UI components, voting features, and remain application flows.
✨ Finishing touches
  • πŸ“ Generate docstrings
πŸ§ͺ Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/814-μ‹ μ²­-νŽ˜μ΄μ§€-κ΅¬ν˜„

πŸ“œ Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between f08f3d1 and 8520fe7.

πŸ“’ Files selected for processing (2)
  • feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/RemainApplicationScreen.kt
  • feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt
🚧 Files skipped from review as they are similar to previous changes (2)
  • feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt
  • feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/RemainApplicationScreen.kt
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build

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

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link

Summary of Changes

Hello @uson1004, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

이 Pull RequestλŠ” μƒˆλ‘œμš΄ "μ‹ μ²­" νŽ˜μ΄μ§€λ₯Ό κ΅¬ν˜„ν•˜κ³ , 이 νŽ˜μ΄μ§€ λ‚΄μ—μ„œ μž”λ₯˜ μ‹ μ²­ κΈ°λŠ₯κ³Ό νˆ¬ν‘œ κΈ°λŠ₯을 μ œκ³΅ν•©λ‹ˆλ‹€. μ‚¬μš©μž κ²½ν—˜μ„ κ°œμ„ ν•˜κΈ° μœ„ν•΄ μƒˆλ‘œμš΄ UI μ»΄ν¬λ„ŒνŠΈλ“€μ΄ μΆ”κ°€λ˜μ—ˆμœΌλ©°, ν™”λ©΄ κ°„ 데이터 전달을 μœ„ν•œ ResultStore νŒ¨ν„΄μ΄ λ„μž…λ˜μ—ˆμŠ΅λ‹ˆλ‹€. λ˜ν•œ, JWT 토큰 μž¬λ°œκΈ‰ μ‹€νŒ¨ μ‹œ μΊμ‹œλ₯Ό μ§€μš°λŠ” 둜직이 μΆ”κ°€λ˜μ–΄ 인증 κ΄€λ ¨ μ•ˆμ •μ„±μ΄ ν–₯μƒλ˜μ—ˆμŠ΅λ‹ˆλ‹€.

Highlights

  • μ‹ μ²­ νŽ˜μ΄μ§€ κ΅¬ν˜„: μž”λ₯˜ μ‹ μ²­ 및 νˆ¬ν‘œ κΈ°λŠ₯을 ν¬ν•¨ν•˜λŠ” μƒˆλ‘œμš΄ 'μ‹ μ²­' νŽ˜μ΄μ§€κ°€ κ΅¬ν˜„λ˜μ—ˆμŠ΅λ‹ˆλ‹€. 이 νŽ˜μ΄μ§€λŠ” 'μ‹ μ²­'κ³Ό 'νˆ¬ν‘œ' νƒ­μœΌλ‘œ κ΅¬μ„±λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.
  • μƒˆλ‘œμš΄ UI μ»΄ν¬λ„ŒνŠΈ μΆ”κ°€: μ‹ μ²­ μ˜΅μ…˜μ„ ν‘œμ‹œν•˜λŠ” DmsApplicationCard, μ»€μŠ€ν…€ νƒ­ DmsTab 및 DmsTabRow, 그리고 ν”Œλ‘œνŒ… μ•Œλ¦Ό DmsFloatingNotice λ“± μž¬μ‚¬μš© κ°€λŠ₯ν•œ UI μ»΄ν¬λ„ŒνŠΈλ“€μ΄ μΆ”κ°€λ˜μ—ˆμŠ΅λ‹ˆλ‹€.
  • ν™”λ©΄ κ°„ κ²°κ³Ό 전달 μ‹œμŠ€ν…œ λ„μž…: Jetpack Compose λ„€λΉ„κ²Œμ΄μ…˜μ—μ„œ ν™”λ©΄ κ°„ κ²°κ³Όλ₯Ό μ „λ‹¬ν•˜κΈ° μœ„ν•œ ResultStore μœ ν‹Έλ¦¬ν‹°κ°€ μΆ”κ°€λ˜μ–΄, RemainApplicationRouteμ—μ„œ κ²°κ³Όλ₯Ό ApplicationScreen으둜 μ „λ‹¬ν•˜λŠ” 데 μ‚¬μš©λ©λ‹ˆλ‹€.
  • JWT μž¬λ°œκΈ‰ 였λ₯˜ 처리 κ°œμ„ : JWT 토큰 μž¬λ°œκΈ‰ μ‹€νŒ¨ μ‹œ (401 Unauthorized) μΊμ‹œλ₯Ό μžλ™μœΌλ‘œ μ§€μš°λŠ” 둜직이 JwtProviderImpl에 μΆ”κ°€λ˜μ–΄ 인증 κ΄€λ ¨ μ•ˆμ •μ„±μ΄ ν–₯μƒλ˜μ—ˆμŠ΅λ‹ˆλ‹€.
  • μž”λ₯˜ 및 νˆ¬ν‘œ κΈ°λŠ₯ 데이터 λ ˆμ΄μ–΄ ꡬ좕: μž”λ₯˜ 및 νˆ¬ν‘œ κΈ°λŠ₯에 ν•„μš”ν•œ 데이터 λͺ¨λΈ, 맀퍼, 리포지토리 및 Dagger Hilt μ˜μ‘΄μ„± μ£Όμž… λͺ¨λ“ˆμ΄ μƒˆλ‘­κ²Œ μΆ”κ°€λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with πŸ‘ and πŸ‘Ž on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

이 PR은 μ‹ μ²­ νŽ˜μ΄μ§€λ₯Ό κ΅¬ν˜„ν•˜λ©°, μž”λ₯˜ μ‹ μ²­ 및 νˆ¬ν‘œ λͺ©λ‘ κΈ°λŠ₯을 ν¬ν•¨ν•©λ‹ˆλ‹€. μƒˆλ‘œμš΄ UI μ»΄ν¬λ„ŒνŠΈ, λ‚΄λΉ„κ²Œμ΄μ…˜ 둜직, λ·°λͺ¨λΈ, 데이터 계측이 μΆ”κ°€λ˜μ—ˆμŠ΅λ‹ˆλ‹€. μ „λ°˜μ μΈ κ΅¬μ‘°λŠ” μ’‹μ§€λ§Œ, μ½”λ“œ ν’ˆμ§ˆ, μ•ˆμ •μ„±, μœ μ§€λ³΄μˆ˜μ„± μΈ‘λ©΄μ—μ„œ κ°œμ„ ν•  뢀뢄이 λͺ‡ κ°€μ§€ μžˆμŠ΅λ‹ˆλ‹€. ꡬ체적인 ν”Όλ“œλ°±μœΌλ‘œλŠ” μ•ˆμ „ν•˜μ§€ μ•Šμ€ API 섀계, 맀직 슀트링 μ‚¬μš©, 잘λͺ»λœ μƒνƒœ 관리, 일관성 μ—†λŠ” 데이터 λͺ¨λΈλ§ λ“±μ˜ 문제λ₯Ό μ§€μ ν–ˆμŠ΅λ‹ˆλ‹€.

viewModelScope.launch {
remainsRepository.fetchRemainsOptions().onSuccess { remainsOptions ->
val selectRemainsOptionId =
remainsOptions.find { it.applied }?.id ?: UUID.randomUUID()

Choose a reason for hiding this comment

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

high

ν˜„μž¬ 적용된 μ˜΅μ…˜μ΄ μ—†λŠ” 경우 UUID.randomUUID()λ₯Ό μ‚¬μš©ν•˜μ—¬ μž„μ˜μ˜ UUIDλ₯Ό μƒμ„±ν•©λ‹ˆλ‹€. μ΄λŠ” μ˜λ―Έμƒ μ˜¬λ°”λ₯΄μ§€ μ•ŠμœΌλ©° ν˜Όλž€μ„ μ•ΌκΈ°ν•  수 μžˆμŠ΅λ‹ˆλ‹€. μ„ νƒλœ μ˜΅μ…˜μ΄ μ—†μŒμ„ λ‚˜νƒ€λ‚΄κΈ° μœ„ν•΄ null을 μ‚¬μš©ν•˜λŠ” 것이 더 μ’‹μŠ΅λ‹ˆλ‹€. selectRemainsOptionId 속성은 nullable(UUID?)둜 λ§Œλ“€μ–΄μ•Ό ν•©λ‹ˆλ‹€.

Suggested change
remainsOptions.find { it.applied }?.id ?: UUID.randomUUID()
remainsOptions.find { it.applied }?.id

Comment on lines +61 to +62
inline fun <reified T> getResultState(resultKey: String = T::class.toString()) =
resultStateMap[resultKey]?.value as T

Choose a reason for hiding this comment

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

medium

getResultState ν•¨μˆ˜λŠ” νƒ€μž… μΊμŠ€νŒ…μ΄ μ•ˆμ „ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. resultStateMap[resultKey]κ°€ nullμ΄κ±°λ‚˜ νƒ€μž…μ΄ μΌμΉ˜ν•˜μ§€ μ•ŠμœΌλ©΄ as T μΊμŠ€νŒ…μœΌλ‘œ 인해 앱이 μΆ©λŒν•  수 μžˆμŠ΅λ‹ˆλ‹€. as? Tλ₯Ό μ‚¬μš©ν•˜κ³  λ°˜ν™˜ νƒ€μž…μ„ T?둜 λ³€κ²½ν•˜μ—¬ 더 μ•ˆμ „ν•˜κ²Œ λ§Œλ“€ 수 μžˆμŠ΅λ‹ˆλ‹€.

Suggested change
inline fun <reified T> getResultState(resultKey: String = T::class.toString()) =
resultStateMap[resultKey]?.value as T
inline fun <reified T> getResultState(resultKey: String = T::class.toString()): T? =
resultStateMap[resultKey]?.value as? T


LaunchedEffect(Unit) {
snapshotFlow {
resultStore.resultStateMap["remain_application_result"]?.value as? String?

Choose a reason for hiding this comment

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

medium

ResultStore의 ν‚€λ‘œ μ‚¬μš©λ˜λŠ” λ¬Έμžμ—΄ λ¦¬ν„°λŸ΄ "remain_application_result"κ°€ ν•˜λ“œμ½”λ”©λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€. 이 ν‚€λŠ” DmsApp.ktμ—μ„œλ„ μ‚¬μš©λ©λ‹ˆλ‹€. 이런 '맀직 슀트링'은 μ˜€νƒ€λ₯Ό μœ λ°œν•˜κ³  μœ μ§€λ³΄μˆ˜λ₯Ό μ–΄λ ΅κ²Œ λ§Œλ“­λ‹ˆλ‹€. const val둜 μƒμˆ˜λ₯Ό μ •μ˜ν•˜μ—¬ μ—¬λŸ¬ κ³³μ—μ„œ μž¬μ‚¬μš©ν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€.

Comment on lines +31 to +42
listOf(
Triple("μž”λ₯˜", R.drawable.img_home, onNavigateRemainApplication),
Triple("μ™ΈμΆœ μ‹ μ²­ν•˜κΈ°", R.drawable.img_outing, onNavigateOutingApplication),
Triple("봉사 ν™œλ™ μ‹ μ²­ν•˜κΈ°", R.drawable.img_volunteer, onNavigateVolunteerApplication),
).forEach { (title, icon, onClick) ->
DmsApplicationCard(
title = title,
iconRes = icon,
onClick = onClick,
appliedTitle = if (title == "μž”λ₯˜") appliedTitle else null,
)
}

Choose a reason for hiding this comment

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

medium

μ‹ μ²­ ν•­λͺ© λͺ©λ‘μ΄ listOf(Triple(...))둜 ν•˜λ“œμ½”λ”©λ˜μ–΄ 있고, if (title == "μž”λ₯˜")와 같은 λ‘œμ§μ—μ„œ '맀직 슀트링'을 μ‚¬μš©ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€. μ΄λŠ” μ»΄ν¬λ„ŒνŠΈμ˜ μž¬μ‚¬μš©μ„±μ„ λ–¨μ–΄λœ¨λ¦¬κ³  μœ μ§€λ³΄μˆ˜λ₯Ό μ–΄λ ΅κ²Œ λ§Œλ“­λ‹ˆλ‹€. μ‹ μ²­ ν•­λͺ©μ— λŒ€ν•œ 데이터 클래슀λ₯Ό μ •μ˜ν•˜κ³  이 λͺ¨λΈμ˜ 리슀트λ₯Ό μ „λ‹¬ν•˜λŠ” 것이 더 μ’‹μŠ΅λ‹ˆλ‹€. appliedTitle을 ν‘œμ‹œν•˜λŠ” λ‘œμ§μ€ ν•΄λ‹Ή λͺ¨λΈμ˜ Boolean ν”Œλž˜κ·Έλ‘œ μ²˜λ¦¬ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

Comment on lines +34 to +36
}.onFailure {
// Logger.a(it) { it.message.toString() }
}

Choose a reason for hiding this comment

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

medium

λ„€νŠΈμ›Œν¬ 호좜의 onFailure 블둝이 λΉ„μ–΄ μžˆμŠ΅λ‹ˆλ‹€(주석 처리됨). 였λ₯˜λ₯Ό λ‘œκΉ…ν•˜κ±°λ‚˜ μ‚¬μš©μžμ—κ²Œ λ©”μ‹œμ§€λ₯Ό ν‘œμ‹œν•˜λŠ” λ“± 였λ₯˜λ₯Ό 적절히 μ²˜λ¦¬ν•˜μ—¬ μ•±μ˜ μ•ˆμ •μ„±μ„ 높이고 디버깅을 μš©μ΄ν•˜κ²Œ ν•΄μ•Ό ν•©λ‹ˆλ‹€.

Comment on lines +88 to +95
val icon = when (remainsOption.title) {
"κΈˆμš”μΌ κ·€κ°€" -> R.drawable.img_night_bus
"ν† μš”μΌ κ·€κ°€" -> R.drawable.img_bus
"ν† μš”μΌ 귀사" -> R.drawable.img_home
"주말 μž”λ₯˜" -> R.drawable.img_small_home
else -> R.drawable.img_bus
}
val appliedTitle = if (remainsOption.applied) "신청됨" else null

Choose a reason for hiding this comment

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

medium

μ•„μ΄μ½˜μ„ κ²°μ •ν•˜κΈ° μœ„ν•΄ when λ¬Έμ—μ„œ "κΈˆμš”μΌ κ·€κ°€"와 같은 '맀직 슀트링'을 μ‚¬μš©ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€. μ΄λŠ” κ²¬κ³ ν•˜μ§€ λͺ»ν•œ λ°©μ‹μž…λ‹ˆλ‹€. RemainsOption 데이터 λͺ¨λΈμ— μ•„μ΄μ½˜ λ¦¬μ†ŒμŠ€ IDλ‚˜ μ•„μ΄μ½˜μ„ λ‚˜νƒ€λ‚΄λŠ” enum을 ν¬ν•¨ν•˜λŠ” 것이 더 μ’‹μŠ΅λ‹ˆλ‹€. λ˜ν•œ, "신청됨" λ¬Έμžμ—΄μ΄ ν•˜λ“œμ½”λ”©λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€. ν˜„μ§€ν™” 및 관리λ₯Ό μœ„ν•΄ λ¬Έμžμ—΄ λ¦¬μ†ŒμŠ€λ‘œ μΆ”μΆœν•΄μ•Ό ν•©λ‹ˆλ‹€.

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: 13

🧹 Nitpick comments (19)
data/src/dev/kotlin/team.aliens.dms.android.data/voting/model/StudentGcnInfo.kt (1)

3-6: Consider adding serialization annotations.

If this model is used for network requests/responses or database storage, you'll need serialization annotations. For example, with kotlinx.serialization:

πŸ”Ž Example with kotlinx.serialization
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
 data class StudentGcnInfo(
+    @SerialName("student_gcn")
     val studentGcn: String,
+    @SerialName("student_filter_id")
     val studentFilterId: Int,
 )

Note: Adjust @SerialName values to match your actual API field names if they differ from the property names.

Optionally, add KDoc for clarity.

Example documentation
+/**
+ * Student GCN (Grade-Class-Number) information for voting.
+ * @property studentGcn The student's GCN identifier
+ * @property studentFilterId The filter ID associated with the student
+ */
 data class StudentGcnInfo(
     val studentGcn: String,
     val studentFilterId: Int,
 )
core/ui/src/dev/java/team/aliens/dms/android/core/ui/util/Date.kt (1)

6-6: Consider using string resources for potential i18n support.

The date format hardcodes Korean locale strings ("λ…„", "μ›”", "일"). While this is acceptable if the app is Korea-only, consider using string resources if there's any possibility of supporting multiple locales in the future.

Example using string resources

In strings.xml:

<string name="date_format">%1$dλ…„ %2$dμ›” %3$d일</string>

In code:

fun LocalDateTime.toDateString(context: Context): String {
    return context.getString(R.string.date_format, this.year, this.monthValue, this.dayOfMonth)
}

Note: This requires passing a Context parameter, which may not be ideal for a utility function. Alternatively, keep the current approach if the app will remain Korean-only.

core/jwt/src/main/java/team/aliens/dms/android/core/jwt/JwtProviderImpl.kt (1)

110-121: Consider adding observability for authentication failures.

The reissueTokens() method now silently handles 401 errors by clearing caches, which is correct behavior. However, adding logging or metrics would help track authentication issues in production (e.g., frequency of token reissue failures, 401 responses, etc.).

This would improve debugging and help identify patterns in auth-related issues.

data/src/dev/kotlin/team.aliens.dms.android.data/remain/model/RemainsApplicationTime.kt (1)

5-10: Consider removing default values or using more type-safe time representation.

The default values (SUNDAY and empty strings) create an instance that doesn't represent a valid time window. Consider either:

  1. Removing defaults to force callers to provide meaningful values
  2. Using a more type-safe type like LocalTime instead of String for time fields to enforce format validation
πŸ”Ž Alternative using LocalTime
 package team.aliens.dms.android.data.remain.model
 
-import java.time.DayOfWeek
+import java.time.DayOfWeek
+import java.time.LocalTime
 
 data class RemainsApplicationTime(
-    val startDayOfWeek: DayOfWeek = DayOfWeek.SUNDAY,
-    val startTime: String  = "",
-    val endDayOfWeek: DayOfWeek = DayOfWeek.SUNDAY,
-    val endTime: String = "",
+    val startDayOfWeek: DayOfWeek,
+    val startTime: LocalTime,
+    val endDayOfWeek: DayOfWeek,
+    val endTime: LocalTime,
 )
data/src/dev/kotlin/team.aliens.dms.android.data/voting/model/ModelStudentCandidates.kt (1)

5-10: Consider nullability for profileImageUrl.

If students may not always have a profile image, consider making profileImageUrl nullable (String?) to prevent potential empty string handling or runtime issues when the URL is missing from the backend response.

core/ui/src/dev/java/team/aliens/dms/android/core/ui/navigation/ResultStore.kt (1)

56-56: Consider making resultStateMap internal or private.

Exposing resultStateMap as public allows external code to modify the internal state directly, bypassing the provided setResult/removeResult methods.

πŸ”Ž Proposed fix
-    val resultStateMap: MutableMap<String, MutableState<Any?>> = mutableMapOf()
+    internal val resultStateMap: MutableMap<String, MutableState<Any?>> = mutableMapOf()
feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/component/VoteContent.kt (1)

13-13: Unused import.

toDateString is imported but not used in this file.

πŸ”Ž Proposed fix
-import team.aliens.dms.android.core.ui.util.toDateString
app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt (1)

161-170: Extract result key to a constant.

The hardcoded string "remain_application_result" should be extracted to a named constant to prevent typos and ensure consistency when reading this result elsewhere (e.g., in ApplicationRoute).

πŸ”Ž Proposed fix

Add a constant at the top level or in a companion object:

private const val REMAIN_APPLICATION_RESULT_KEY = "remain_application_result"

Then use it:

-                                resultStore.setResult<String?>("remain_application_result", title)
+                                resultStore.setResult<String?>(REMAIN_APPLICATION_RESULT_KEY, title)
feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/component/DmsFloatingNotice.kt (2)

49-52: Consider adding a tint to the Icon for visual consistency.

The Icon doesn't have a tint specified, so it will use the default LocalContentColor. Since the text uses DmsTheme.colorScheme.onBackground, the icon may not match visually. Consider explicitly setting the tint.

πŸ”Ž Proposed fix
         Icon(
             painter = painterResource(iconResource),
             contentDescription = null,
+            tint = DmsTheme.colorScheme.onBackground,
         )

58-61: Review minimum font size for readability.

A minFontSize of 1.sp may result in unreadable text on some devices. Consider using a higher minimum (e.g., 8.sp or 10.sp) to ensure accessibility.

πŸ”Ž Proposed fix
             autoSize = TextAutoSize.StepBased(
-                minFontSize = 1.sp,
+                minFontSize = 10.sp,
                 maxFontSize = 50.sp,
             ),
core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/card/DmsApplicationCard.kt (1)

44-50: Consider adding a label to the animation for debugging.

Adding a label parameter helps identify this animation in Android Studio's Animation Preview and Compose tooling.

πŸ”Ž Proposed fix
     val borderColor by animateColorAsState(
         targetValue = if (isSelected) {
             DmsTheme.colorScheme.onPrimaryContainer
         } else {
             DmsTheme.colorScheme.surfaceTint
         },
+        label = "DmsApplicationCard_borderColor",
     )
feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/viewmodel/ApplicationViewModel.kt (1)

21-22: Consider abstracting SharedPreferences access.

Directly using SharedPreferences in the ViewModel reduces testability. Consider injecting a DataStore or a custom repository/preferences wrapper that can be mocked in tests.

feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/RemainApplicationScreen.kt (2)

3-3: Remove unused import.

android.util.Log is imported but never used in this file.

πŸ”Ž Proposed fix
-import android.util.Log

88-94: Hardcoded string matching for icon selection is fragile.

Mapping icons based on exact Korean title strings ("κΈˆμš”μΌ κ·€κ°€", "ν† μš”μΌ κ·€κ°€", etc.) is brittle. If the backend returns slightly different titles or the text changes, the icon mapping silently falls back to the default.

Consider using an enum, type field, or identifier from the remainsOption model to determine the icon instead of relying on display text matching.

data/src/dev/kotlin/team.aliens.dms.android.data/voting/repository/VotingRepository.kt (2)

9-23: Consider using an interface instead of abstract class.

Kotlin idiom prefers interfaces for repository contracts when there's no shared implementation logic. This provides more flexibility for testing (mocking) and allows implementing multiple interfaces.

πŸ”Ž Proposed change
-abstract class VotingRepository {
-
-    abstract suspend fun fetchAllVoteSearch(): Result<List<AllVoteSearch>>
-
-    abstract suspend fun fetchCheckVotingItem(votingTopicId: UUID): Result<List<VotingItem>>
-
-    abstract suspend fun fetchCreateVotingItem(
-        votingTopicId: UUID,
-        selectedId: UUID,
-    ): Result<Unit>
-
-    abstract suspend fun fetchDeleteVotingItem(voteId: UUID): Result<Unit>
-
-    abstract suspend fun fetchModelStudentCandidates(requestDate: LocalDate): Result<List<ModelStudentCandidates>>
-}
+interface VotingRepository {
+    suspend fun fetchAllVoteSearch(): Result<List<AllVoteSearch>>
+    suspend fun fetchCheckVotingItem(votingTopicId: UUID): Result<List<VotingItem>>
+    suspend fun createVotingItem(votingTopicId: UUID, selectedId: UUID): Result<Unit>
+    suspend fun deleteVotingItem(voteId: UUID): Result<Unit>
+    suspend fun fetchModelStudentCandidates(requestDate: LocalDate): Result<List<ModelStudentCandidates>>
+}

15-20: Misleading "fetch" prefix on mutation methods.

fetchCreateVotingItem and fetchDeleteVotingItem use the "fetch" prefix which typically implies a read operation. These are write operations that should use more appropriate naming like createVotingItem and deleteVotingItem.

feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/ApplicationScreen.kt (2)

3-3: Remove unused import.

android.util.Log is imported but never used in this file.

πŸ”Ž Proposed fix
-import android.util.Log

43-51: Magic string for result key.

The string "remain_application_result" is used directly. Consider extracting this to a constant to avoid typos and improve maintainability, especially since it's likely used elsewhere (e.g., when setting the result).

feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/component/ApplicationContent.kt (1)

31-42: Refactor to avoid fragile string comparison.

The conditional logic on line 40 relies on string matching (title == "μž”λ₯˜") to determine which card should display the appliedTitle. If the title on line 32 is modified, this logic will silently break. Consider using an index-based check or a data class with an explicit flag to make the code more robust and maintainable.

πŸ”Ž Proposed refactor using index-based approach
-        listOf(
+        listOf(
             Triple("μž”λ₯˜", R.drawable.img_home, onNavigateRemainApplication),
             Triple("μ™ΈμΆœ μ‹ μ²­ν•˜κΈ°", R.drawable.img_outing, onNavigateOutingApplication),
             Triple("봉사 ν™œλ™ μ‹ μ²­ν•˜κΈ°", R.drawable.img_volunteer, onNavigateVolunteerApplication),
-        ).forEach { (title, icon, onClick) ->
+        ).forEachIndexed { index, (title, icon, onClick) ->
             DmsApplicationCard(
                 title = title,
                 iconRes = icon,
                 onClick = onClick,
-                appliedTitle = if (title == "μž”λ₯˜") appliedTitle else null,
+                appliedTitle = if (index == 0) appliedTitle else null,
             )
         }
πŸ“œ Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between d4d7915 and 6157fdc.

β›” Files ignored due to path filters (10)
  • core/design-system/src/dev/res/drawable/img_bus.png is excluded by !**/*.png
  • core/design-system/src/dev/res/drawable/img_choice.png is excluded by !**/*.png
  • core/design-system/src/dev/res/drawable/img_home.png is excluded by !**/*.png
  • core/design-system/src/dev/res/drawable/img_model_student.png is excluded by !**/*.png
  • core/design-system/src/dev/res/drawable/img_night_bus.png is excluded by !**/*.png
  • core/design-system/src/dev/res/drawable/img_outing.png is excluded by !**/*.png
  • core/design-system/src/dev/res/drawable/img_percent.png is excluded by !**/*.png
  • core/design-system/src/dev/res/drawable/img_small_home.png is excluded by !**/*.png
  • core/design-system/src/dev/res/drawable/img_student_tag.png is excluded by !**/*.png
  • core/design-system/src/dev/res/drawable/img_volunteer.png is excluded by !**/*.png
πŸ“’ Files selected for processing (33)
  • app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt
  • core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/card/DmsApplicationCard.kt
  • core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/tab/DmsTab.kt
  • core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/tab/DmsTabRow.kt
  • core/jwt/src/main/java/team/aliens/dms/android/core/jwt/JwtProviderImpl.kt
  • core/ui/src/dev/java/team/aliens/dms/android/core/ui/navigation/ResultStore.kt
  • core/ui/src/dev/java/team/aliens/dms/android/core/ui/util/Date.kt
  • data/src/dev/kotlin/team.aliens.dms.android.data/remain/di/RepositoryModule.kt
  • data/src/dev/kotlin/team.aliens.dms.android.data/remain/mapper/RemainsMapper.kt
  • data/src/dev/kotlin/team.aliens.dms.android.data/remain/model/AppliedRemainsOption.kt
  • data/src/dev/kotlin/team.aliens.dms.android.data/remain/model/RemainsApplicationTime.kt
  • data/src/dev/kotlin/team.aliens.dms.android.data/remain/model/RemainsOption.kt
  • data/src/dev/kotlin/team.aliens.dms.android.data/remain/repository/RemainsRepository.kt
  • data/src/dev/kotlin/team.aliens.dms.android.data/remain/repository/RemainsRepositoryImpl.kt
  • data/src/dev/kotlin/team.aliens.dms.android.data/voting/di/RepositoryModule.kt
  • data/src/dev/kotlin/team.aliens.dms.android.data/voting/mapper/VotingMapper.kt
  • data/src/dev/kotlin/team.aliens.dms.android.data/voting/model/AllVoteSearch.kt
  • data/src/dev/kotlin/team.aliens.dms.android.data/voting/model/ModelStudentCandidates.kt
  • data/src/dev/kotlin/team.aliens.dms.android.data/voting/model/StudentGcnInfo.kt
  • data/src/dev/kotlin/team.aliens.dms.android.data/voting/model/Vote.kt
  • data/src/dev/kotlin/team.aliens.dms.android.data/voting/model/VotingItem.kt
  • data/src/dev/kotlin/team.aliens.dms.android.data/voting/repository/VotingRepository.kt
  • data/src/dev/kotlin/team.aliens.dms.android.data/voting/repository/VotingRepositoryImpl.kt
  • feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/navigation/ApplicationRoute.kt
  • feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/ApplicationScreen.kt
  • feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/component/ApplicationContent.kt
  • feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/component/VoteContent.kt
  • feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/viewmodel/ApplicationViewModel.kt
  • feature/src/dev/kotlin/team/aliens/dms/android/feature/meal/ui/MealScreen.kt
  • feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/navigation/RemainApplicationRoute.kt
  • feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/RemainApplicationScreen.kt
  • feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/component/DmsFloatingNotice.kt
  • feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt
🧰 Additional context used
🧬 Code graph analysis (11)
feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/navigation/RemainApplicationRoute.kt (1)
feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/RemainApplicationScreen.kt (1)
  • RemainApplication (36-52)
data/src/dev/kotlin/team.aliens.dms.android.data/remain/model/AppliedRemainsOption.kt (1)
data/src/prod/kotlin/team/aliens/dms/android/data/remains/model/AppliedRemainsOption.kt (1)
  • id (5-8)
feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/viewmodel/ApplicationViewModel.kt (1)
core/ui/src/dev/java/team/aliens/dms/android/core/ui/viewmodel/BaseStateViewModel.kt (1)
  • setState (29-31)
feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/navigation/ApplicationRoute.kt (1)
feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/ApplicationScreen.kt (1)
  • Application (31-61)
data/src/dev/kotlin/team.aliens.dms.android.data/voting/di/RepositoryModule.kt (1)
data/src/prod/kotlin/team/aliens/dms/android/data/voting/di/RepositoryModule.kt (1)
  • bindVotingRepository (11-18)
data/src/dev/kotlin/team.aliens.dms.android.data/remain/di/RepositoryModule.kt (1)
data/src/prod/kotlin/team/aliens/dms/android/data/remains/di/RepositoryModule.kt (1)
  • bindRemainsRepository (11-18)
feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/ApplicationScreen.kt (4)
core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/tab/DmsTabRow.kt (1)
  • DmsTabRow (16-55)
core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/tab/DmsTab.kt (1)
  • DmsTab (14-44)
feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/component/ApplicationContent.kt (1)
  • ApplicationContent (14-44)
feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/component/VoteContent.kt (1)
  • VoteContent (17-49)
data/src/dev/kotlin/team.aliens.dms.android.data/remain/repository/RemainsRepositoryImpl.kt (1)
data/src/prod/kotlin/team/aliens/dms/android/data/remains/repository/RemainsRepositoryImpl.kt (1)
  • networkRemainsDataSource (11-27)
data/src/dev/kotlin/team.aliens.dms.android.data/remain/repository/RemainsRepository.kt (1)
data/src/prod/kotlin/team/aliens/dms/android/data/remains/repository/RemainsRepository.kt (1)
  • updateRemainsOption (8-17)
feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/RemainApplicationScreen.kt (4)
feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/component/DmsFloatingNotice.kt (1)
  • DmsFloatingNotice (29-65)
core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/card/DmsApplicationCard.kt (1)
  • DmsApplicationCard (33-116)
feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt (1)
  • setSelectRemainsOption (49-51)
core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/button/Button.kt (1)
  • DmsButton (253-351)
feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt (1)
core/ui/src/dev/java/team/aliens/dms/android/core/ui/viewmodel/BaseStateViewModel.kt (1)
  • setState (29-31)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
πŸ”‡ Additional comments (27)
feature/src/dev/kotlin/team/aliens/dms/android/feature/meal/ui/MealScreen.kt (1)

17-18: LGTM! Clean scrolling implementation.

The vertical scroll addition properly handles content overflow scenarios. The implementation follows Compose best practices using rememberScrollState(), and the vertical scrolling gestures won't conflict with the horizontal HorizontalPager gestures.

Also applies to: 151-152

data/src/dev/kotlin/team.aliens.dms.android.data/remain/di/RepositoryModule.kt (1)

1-18: Confirm package naming differs between dev and prod variants: "remain" vs "remains".

The dev variant consistently uses team.aliens.dms.android.data.remain.* (singular) across all feature packages (repository, model, mapper, di), while the prod variant uses team.aliens.dms.android.data.remains.* (plural). This systematic difference should be verified as intentional, as other features maintain consistent naming across build variants.

The Dagger Hilt module structure itself is correct and follows best practices.

data/src/dev/kotlin/team.aliens.dms.android.data/voting/model/StudentGcnInfo.kt (1)

1-6: The source set location is intentional and correct for this project structure.

This is a multi-variant Android project using src/prod/ and src/dev/ source sets, not src/main/. The StudentGcnInfo class exists in both the dev and prod variants as appropriate. The prod variant version is actively imported and used in VotingStudentScreen.kt and VotingModelStudentScreen.kt. This location is not a misconfiguration.

Likely an incorrect or invalid review comment.

core/ui/src/dev/java/team/aliens/dms/android/core/ui/util/Date.kt (1)

1-7: Unused import and function with no critical issues.

The toDateString() function is not used in production code. While VoteContent.kt imports it on line 13, the actual code does not call toDateString()β€”instead using toLocalTime() on line 42. The import is unused. Since both this utility and its only importer are in dev source sets, having the file in src/dev is appropriate and intentional.

data/src/dev/kotlin/team.aliens.dms.android.data/remain/model/AppliedRemainsOption.kt (1)

5-8: LGTM!

Clean data model with appropriate types for the domain.

data/src/dev/kotlin/team.aliens.dms.android.data/remain/model/RemainsOption.kt (1)

5-10: LGTM!

Well-structured data model with clear property semantics.

data/src/dev/kotlin/team.aliens.dms.android.data/remain/mapper/RemainsMapper.kt (1)

10-33: LGTM!

Clean mapper implementation with appropriate visibility modifiers. The use of internal for public mappers and private for the nested helper function follows good encapsulation practices.

data/src/dev/kotlin/team.aliens.dms.android.data/remain/repository/RemainsRepositoryImpl.kt (1)

11-30: Implementation pattern is correct for dev variant, but confirms the dev/prod API incompatibility.

The use of runCatching to wrap network calls aligns with the Result<T> return types in the dev abstract class. However, the prod version (shown in relevant code snippets) doesn't use runCatching and returns values directly, confirming the critical API incompatibility already flagged in RemainsRepository.kt.

The implementation itself is sound for the dev variant. Please address the dev/prod inconsistency flagged in the repository interface.

data/src/dev/kotlin/team.aliens.dms.android.data/voting/model/VotingItem.kt (1)

5-8: LGTM!

Clean and minimal data class with appropriate types. The structure aligns well with the voting data layer requirements.

core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/tab/DmsTab.kt (1)

14-44: LGTM!

Well-structured wrapper around Material3 Tab with appropriate theme integration. The destructuring for text style and color based on selection state is clean and readable.

core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/tab/DmsTabRow.kt (1)

16-55: LGTM!

Well-implemented tab row wrapper with proper bounds checking for the indicator (line 24). The rounded corner styling on the indicator is a nice touch, and the theming integration is consistent.

core/ui/src/dev/java/team/aliens/dms/android/core/ui/navigation/ResultStore.kt (1)

79-84: Verify Saver works correctly across process death.

The Saver saves MutableState<Any?> objects, but MutableState itself is not Parcelable. This may work for configuration changes (where the instance is retained in memory) but could fail on process death when actual serialization is required.

Ensure the values stored in resultStateMap are Parcelable or Serializable, or test process death scenarios to confirm restoration works correctly.

data/src/dev/kotlin/team.aliens.dms.android.data/voting/model/Vote.kt (1)

3-6: LGTM!

Clear and well-named enum constants for vote types.

feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/component/VoteContent.kt (1)

32-47: LGTM on the card rendering logic.

The icon selection based on voteType using when expression is clean and exhaustive. The integration with DmsApplicationCard follows the expected API pattern.

data/src/dev/kotlin/team.aliens.dms.android.data/voting/model/AllVoteSearch.kt (1)

6-14: LGTM!

Clean data class definition with appropriate types. Using java.time.LocalDateTime is the recommended approach for date-time handling in modern Kotlin/Android.

data/src/dev/kotlin/team.aliens.dms.android.data/voting/di/RepositoryModule.kt (1)

11-18: LGTM!

Standard Hilt module binding pattern, consistent with the existing prod variant and other repository modules in the codebase.

feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/navigation/RemainApplicationRoute.kt (1)

7-16: LGTM!

Clean route wrapper that properly delegates to the internal RemainApplication composable, maintaining good separation between navigation routing and screen implementation.

core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/card/DmsApplicationCard.kt (1)

33-116: LGTM!

Well-structured reusable card component with appropriate theming and layout. The conditional appliedTitle display logic (header vs. description row) is clear and the animated border state provides good visual feedback.

feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/viewmodel/ApplicationViewModel.kt (1)

57-60: LGTM on the state design.

Clean state class with sensible defaults. Keeping this as a simple data class is appropriate.

feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/RemainApplicationScreen.kt (2)

36-52: LGTM!

The entry point composable correctly obtains the ViewModel via hiltViewModel(), collects state with lifecycle awareness, and properly wires the callbacks to the screen composable.


54-118: LGTM!

The screen composable is well-structured with proper separation of concerns. The UI layout with top app bar, floating notice, lazy column, and bottom button follows a clean pattern. The button is correctly enabled only when a selection exists.

feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/navigation/ApplicationRoute.kt (1)

9-24: LGTM!

The route function correctly serves as a thin wrapper that forwards all navigation and snackbar callbacks to the Application composable. This follows a clean separation pattern between navigation wiring and UI logic.

data/src/dev/kotlin/team.aliens.dms.android.data/voting/mapper/VotingMapper.kt (2)

16-25: Potential runtime exceptions in mapping.

UUID.fromString() (line 18) and Vote.valueOf() (line 23) can throw IllegalArgumentException if the backend returns an invalid UUID or an unrecognized vote type. While these exceptions will be caught by runCatching in the repository layer, the error message won't be descriptive.

Consider adding defensive checks or using runCatching within the mapper for more granular error handling if API contracts are not guaranteed.


27-45: LGTM!

The mapping functions for VotingItem and ModelStudentCandidates are straightforward field-to-field mappings with no transformation risks.

feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/ApplicationScreen.kt (1)

63-128: LGTM!

The ApplicationScreen composable is well-structured with a clean pager implementation. The tab synchronization with coroutineScope.launch and animateScrollToPage follows the recommended pattern. Using beyondViewportPageCount = 1 is good for preloading adjacent pages.

data/src/dev/kotlin/team.aliens.dms.android.data/voting/repository/VotingRepositoryImpl.kt (1)

12-34: LGTM!

The repository implementation is clean and consistent:

  • Proper use of @Inject for DI
  • All operations wrapped in runCatching for error handling
  • Consistent use of toModel() mappers for response transformation
  • internal visibility is appropriate for DI module binding

Note: The naming issues mentioned in VotingRepository.kt apply here as well (fetchCreateVotingItem and fetchDeleteVotingItem should use create/delete prefixes).

feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/component/ApplicationContent.kt (1)

14-21: LGTM!

The function signature follows Compose best practices with proper modifier-first parameter ordering and clear navigation callback names.

Comment on lines +61 to +62
inline fun <reified T> getResultState(resultKey: String = T::class.toString()) =
resultStateMap[resultKey]?.value as T
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

Unsafe cast can cause NPE or ClassCastException.

When resultKey doesn't exist in the map, resultStateMap[resultKey]?.value returns null, and casting null as T will fail for non-nullable types. This should return a nullable type or handle the missing key case.

πŸ”Ž Proposed fix
-    inline fun <reified T> getResultState(resultKey: String = T::class.toString()) =
-        resultStateMap[resultKey]?.value as T
+    inline fun <reified T> getResultState(resultKey: String = T::class.toString()): T? =
+        resultStateMap[resultKey]?.value as? T

Alternatively, if non-null is required, provide a version that throws with a clear message:

inline fun <reified T> getResultStateOrThrow(resultKey: String = T::class.toString()): T =
    resultStateMap[resultKey]?.value as? T
        ?: error("No result found for key: $resultKey")
πŸ“ 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
inline fun <reified T> getResultState(resultKey: String = T::class.toString()) =
resultStateMap[resultKey]?.value as T
inline fun <reified T> getResultState(resultKey: String = T::class.toString()): T? =
resultStateMap[resultKey]?.value as? T
πŸ€– Prompt for AI Agents
In
core/ui/src/dev/java/team/aliens/dms/android/core/ui/navigation/ResultStore.kt
around lines 61-62, the inline getResultState currently performs an unsafe cast
which can throw when the key is missing or the type mismatches; change the
function to return a nullable T using a safe cast (as?) so it returns null when
no value or wrong type is present, or alternatively add a new
getResultStateOrThrow that uses a safe cast and throws a clear error if the
result is absent; ensure the function signature reflects nullability for the
nullable variant and update call sites accordingly.

Comment on lines +54 to +60
ApplicationScreen(
state = state,
onNavigateRemainApplication = onNavigateRemainApplication,
onNavigateOutingApplication = { onShowSnackBar(DmsSnackBarType.SUCCESS, "쀀비쀑인 κΈ°λŠ₯μ΄μ—μš”") },
onNavigateVolunteerApplication = { onShowSnackBar(DmsSnackBarType.SUCCESS, "쀀비쀑인 κΈ°λŠ₯μ΄μ—μš”") },
onNavigateVote = onNavigateVote,
)
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

Unused navigation callbacks replaced with snackbar messages.

onNavigateOutingApplication and onNavigateVolunteerApplication are accepted as parameters (lines 34-35) but are never used. Instead, they're replaced with snackbar messages indicating "쀀비쀑인 κΈ°λŠ₯μ΄μ—μš”" (feature in preparation).

If these features are not yet implemented, consider either:

  1. Not accepting these callbacks as parameters and hardcoding the snackbar behavior
  2. Using the passed callbacks as intended once the features are ready

This creates a misleading API where callers provide callbacks that are silently ignored.

Comment on lines +18 to +20
class RemainApplicationViewModel @Inject constructor(
val remainsRepository: RemainsRepository
): BaseStateViewModel<RemainApplicationState, Unit>(RemainApplicationState()) {
Copy link

Choose a reason for hiding this comment

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

πŸ› οΈ Refactor suggestion | 🟠 Major

Make remainsRepository private.

The repository should not be exposed publicly from the ViewModel. This violates encapsulation and allows external code to bypass ViewModel logic.

πŸ”Ž Proposed fix
 @HiltViewModel
 class RemainApplicationViewModel @Inject constructor(
-   val remainsRepository: RemainsRepository
+   private val remainsRepository: RemainsRepository
 ): BaseStateViewModel<RemainApplicationState, Unit>(RemainApplicationState()) {
πŸ€– Prompt for AI Agents
In
feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt
around lines 18-20, the injected remainsRepository is declared public which
exposes the repository outside the ViewModel; make it private by changing its
visibility to private (e.g., private val remainsRepository: RemainsRepository)
so external code cannot access it directly and the ViewModel retains
encapsulation while leaving constructor injection intact.

Comment on lines 102 to 112
if (startDayValue == endDayValue) {
return currentDayOfWeek == startDayValue &&
currentTime >= startTime && currentTime <= endTime
}

return when {
currentDayOfWeek == startDayValue -> currentTime >= startTime
currentDayOfWeek == endDayValue -> currentTime <= endTime
currentDayOfWeek in (startDayValue + 1)..<endDayValue -> true
else -> false
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Week wraparound not handled in time validation.

The range check currentDayOfWeek in (startDayValue + 1)..<endDayValue fails when the application period spans across week boundaries (e.g., Friday to Monday). In such cases, startDayValue (5) > endDayValue (1), making the range empty.

πŸ”Ž Proposed fix for week wraparound
     if (startDayValue == endDayValue) {
         return currentDayOfWeek == startDayValue &&
                 currentTime >= startTime && currentTime <= endTime
     }

-    return when {
-        currentDayOfWeek == startDayValue -> currentTime >= startTime
-        currentDayOfWeek == endDayValue -> currentTime <= endTime
-        currentDayOfWeek in (startDayValue + 1)..<endDayValue -> true
-        else -> false
+    // Handle week wraparound (e.g., Friday to Monday)
+    val isWithinDayRange = if (startDayValue < endDayValue) {
+        currentDayOfWeek in startDayValue..endDayValue
+    } else {
+        currentDayOfWeek >= startDayValue || currentDayOfWeek <= endDayValue
+    }
+
+    if (!isWithinDayRange) return false
+
+    return when (currentDayOfWeek) {
+        startDayValue -> currentTime >= startTime
+        endDayValue -> currentTime <= endTime
+        else -> true
     }
πŸ€– Prompt for AI Agents
In
feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt
around lines 102 to 112, the weekday range check doesn't handle week wraparound
(when startDayValue > endDayValue), so the expression `currentDayOfWeek in
(startDayValue + 1)..<endDayValue` becomes empty; change the logic to detect
wraparound and, when startDayValue <= endDayValue keep the existing middle-range
check, but when startDayValue > endDayValue treat middle days as those >=
startDayValue OR <= endDayValue (i.e., use an OR condition to cover the
end-of-week continuation), ensuring the start-day and end-day branches remain
the same and preserve time comparisons for boundary days.

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: 11

♻️ Duplicate comments (9)
feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt (5)

18-20: Make repository private.

The injected repository should not be publicly accessible from the ViewModel, as this breaks encapsulation.

This issue was already flagged in previous reviews.


29-31: Avoid random UUID for missing selection.

Generating a random UUID when no option is applied creates a meaningless identifier. The state property selectRemainsOptionId is already nullable (line 118), so use null here.

This issue was already flagged in previous reviews.


61-62: Critical: Invalid UUID sent to server.

When selectRemainsOptionId is null, this code generates a random UUID and sends it to the server, which will fail or cause unintended behavior. Add an early return with error feedback.

This critical issue was already flagged in previous reviews.


64-79: Refactor to eliminate side effects in map.

Calling setState inside the map function (line 66) is an anti-pattern that causes side effects during iteration. The subsequent setState at lines 73-78 is also redundant. Consolidate into a single atomic state update.

This issue was already flagged in previous reviews.


107-112: Week wraparound not handled.

The range check fails when the application period spans week boundaries (e.g., Friday to Monday), because startDayValue > endDayValue makes the range on line 110 empty.

This issue was already flagged in previous reviews with a detailed fix.

feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/component/ApplicationContent.kt (1)

30-41: Hardcoded strings and magic string comparison.

The use of listOf(Triple(...)) with hardcoded Korean strings and the magic string comparison if (title == "μž”λ₯˜") reduces maintainability. This concern was already raised in a previous review suggesting a data class approach.

app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt (1)

3-3: Unused import.

android.util.Log is imported but not used in this file.

feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/ApplicationScreen.kt (2)

44-44: Define a constant for the result key.

The magic string "remain_application_result" is hardcoded here and in DmsApp.kt. This was previously flagged.


56-57: Unused navigation callbacks replaced with snackbar messages.

onNavigateOutingApplication and onNavigateVolunteerApplication parameters (lines 33-34) are accepted but replaced with snackbar messages. This was previously flagged.

🧹 Nitpick comments (11)
feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/viewmodel/VoteViewModel.kt (1)

49-80: Remove commented-out logging statements.

The commented-out Logger.a(...) calls on lines 54, 65, and 76 should either be removed or properly implemented. Leaving dead code reduces readability.

core/ui/src/dev/java/team/aliens/dms/android/core/ui/util/Date.kt (1)

5-7: Consider using DateTimeFormatter for consistent formatting.

The current implementation lacks zero-padding for single-digit months/days (e.g., "1μ›” 5일" vs "01μ›” 05일"). If consistent padding is desired, consider using DateTimeFormatter:

πŸ”Ž Alternative using DateTimeFormatter
import java.time.format.DateTimeFormatter

private val koreanDateFormatter = DateTimeFormatter.ofPattern("yyyyλ…„ MMμ›” dd일")

fun LocalDateTime.toDateString(): String = format(koreanDateFormatter)
feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/VoteItemContent.kt (1)

24-70: Consider removing unnecessary Column wrapper.

The outer Column wraps a when expression where each branch renders exactly one composable. The wrapper adds an unnecessary layout layer and doesn't provide additional structure.

πŸ”Ž Simplified implementation
-    Column(
-        modifier = modifier,
-    ) {
-        when (vote) {
-            Vote.OPTION_VOTE -> {
-                OptionContent(
-                    title = title,
-                    startTime = startTime,
-                    endTime = endTime,
-                    options = options,
-                    selectItem = selectItem,
-                    onSelect = onSelect,
-                )
-            }
-            // ... other cases
-        }
-    }
+    when (vote) {
+        Vote.OPTION_VOTE -> OptionContent(
+            modifier = modifier,
+            title = title,
+            startTime = startTime,
+            endTime = endTime,
+            options = options,
+            selectItem = selectItem,
+            onSelect = onSelect,
+        )
+        Vote.STUDENT_VOTE -> StudentContent(
+            modifier = modifier,
+            title = title,
+            startTime = startTime,
+            endTime = endTime,
+            students = students,
+            selectItem = selectItem,
+            onSelect = onSelect,
+        )
+        Vote.APPROVAL_VOTE -> ApprovalContent(
+            modifier = modifier,
+            title = title,
+            options = options,
+            selectItem = selectItem,
+            onSelect = onSelect,
+        )
+        Vote.MODEL_STUDENT_VOTE -> StudentContent(
+            modifier = modifier,
+            title = title,
+            startTime = startTime,
+            endTime = endTime,
+            students = modelStudents,
+            selectItem = selectItem,
+            onSelect = onSelect,
+        )
+    }
feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/VoteScreen.kt (2)

3-3: Unused import.

android.util.Log is imported but not used in this file.

πŸ”Ž Proposed fix
-import android.util.Log

41-66: Side effect collection inside LaunchedEffect(Unit) may miss emissions.

The sideEffect collection starts after initState() completes. If initState emits a side effect synchronously, it could be missed. Consider separating the initialization and side effect collection into different LaunchedEffect blocks.

πŸ”Ž Proposed fix
-    LaunchedEffect(Unit) {
-        viewModel.initState(title, startTime, endTime)
-        viewModel.sideEffect.collect {
+    LaunchedEffect(Unit) {
+        viewModel.sideEffect.collect {
             when (it) {
                 is VoteSideEffect.VoteSuccess -> onShowSnackBar(
                     DmsSnackBarType.SUCCESS,
                     "νˆ¬ν‘œλ₯Ό μ™„λ£Œν–ˆμ–΄μš”!",
                 )
                 // ... other cases
             }
         }
     }
+
+    LaunchedEffect(Unit) {
+        viewModel.initState(title, startTime, endTime)
+    }
feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/StudentContent.kt (2)

93-106: Consider memoizing filtered students to avoid repeated computation.

filteredStudents is recalculated on every recomposition. Using remember with appropriate keys would improve performance, especially for large student lists.

πŸ”Ž Proposed fix
-            val selectGrade = page + 1
-            LazyColumn(modifier = Modifier.fillMaxSize()) {
-                val filteredStudents = students.filter { it.gradeClassNumber.startsWith("$selectGrade") }
+            val selectGrade = page + 1
+            val filteredStudents = remember(students, selectGrade) {
+                students.filter { it.gradeClassNumber.startsWith("$selectGrade") }
+            }
+            LazyColumn(modifier = Modifier.fillMaxSize()) {

137-146: Add placeholder and error handling for AsyncImage.

If profileImageUrl is empty, null, or fails to load, the image area will be blank. Consider adding placeholder and error drawables for better UX.

πŸ”Ž Proposed fix
         AsyncImage(
             modifier = Modifier
                 .size(32.dp)
                 .clip(CircleShape),
             model = ImageRequest.Builder(context = LocalContext.current)
                 .data(profileImageUrl)
+                .crossfade(true)
                 .build(),
             contentDescription = null,
             contentScale = ContentScale.Crop,
+            placeholder = painterResource(R.drawable.ic_default_profile), // Add appropriate drawable
+            error = painterResource(R.drawable.ic_default_profile),
         )
app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt (2)

169-178: Navigation back creates new VoteScreenNav instance for removal.

The back navigation creates a new VoteScreenNav(voteNav.title, voteNav.startTime, voteNav.endTime) to remove from the backstack. While data class equality should make this work, consider using backStack.removeLastOrNull() for consistency with other navigation patterns, or directly reference the voteNav instance.

πŸ”Ž Proposed fix
-                            onNavigateBack = { backStack.remove(VoteScreenNav(voteNav.title, voteNav.startTime, voteNav.endTime)) },
+                            onNavigateBack = { backStack.removeLastOrNull() },

180-189: Define a constant for the result key "remain_application_result".

This magic string is used here and in ApplicationScreen.kt. Define a shared constant to prevent typos and improve maintainability.

// In a shared constants file or companion object
const val REMAIN_APPLICATION_RESULT_KEY = "remain_application_result"
feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/ApprovalContent.kt (1)

1-2: Remove leading blank line.

There's an unnecessary blank line before the package declaration.

πŸ”Ž Proposed fix
-
 package team.aliens.dms.android.feature.vote.ui.component
feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/ApplicationScreen.kt (1)

42-51: Consider null-safety when casting result.

The cast as? String? with nullable chaining is correct, but the snapshotFlow will emit on every read of resultStateMap, potentially causing unnecessary collections. Consider using distinctUntilChanged().

πŸ”Ž Proposed fix
     LaunchedEffect(Unit) {
         snapshotFlow {
             resultStore.resultStateMap["remain_application_result"]?.value as? String?
-        }.collect { result ->
+        }.distinctUntilChanged().collect { result ->
             if (result != null) {
                 viewModel.setRemainApplication(result)
                 resultStore.removeResult<String?>(resultKey = "remain_application_result")
             }
         }
     }

Add the import:

import kotlinx.coroutines.flow.distinctUntilChanged
πŸ“œ Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 6157fdc and 33f9f15.

πŸ“’ Files selected for processing (23)
  • app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt
  • core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/modifier/DmsShadowModifier.kt
  • core/design-system/src/dev/res/drawable/ic_approve.xml
  • core/design-system/src/dev/res/drawable/ic_oppose.xml
  • core/ui/src/dev/java/team/aliens/dms/android/core/ui/util/Date.kt
  • data/build.gradle.kts
  • data/src/dev/kotlin/team.aliens.dms.android.data/voting/mapper/VotingMapper.kt
  • data/src/dev/kotlin/team.aliens.dms.android.data/voting/model/AllVoteSearch.kt
  • data/src/dev/kotlin/team.aliens.dms.android.data/voting/repository/VotingRepository.kt
  • data/src/dev/kotlin/team.aliens.dms.android.data/voting/repository/VotingRepositoryImpl.kt
  • feature/build.gradle.kts
  • feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/ApplicationScreen.kt
  • feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/component/ApplicationContent.kt
  • feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/component/VoteContent.kt
  • feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt
  • feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/navigation/VoteRoute.kt
  • feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/VoteScreen.kt
  • feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/ApprovalContent.kt
  • feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/OptionContent.kt
  • feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/StudentContent.kt
  • feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/TitleContent.kt
  • feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/VoteItemContent.kt
  • feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/viewmodel/VoteViewModel.kt
🚧 Files skipped from review as they are similar to previous changes (3)
  • data/src/dev/kotlin/team.aliens.dms.android.data/voting/mapper/VotingMapper.kt
  • data/src/dev/kotlin/team.aliens.dms.android.data/voting/repository/VotingRepository.kt
  • feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/component/VoteContent.kt
🧰 Additional context used
🧬 Code graph analysis (10)
feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/VoteItemContent.kt (3)
feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/OptionContent.kt (1)
  • OptionContent (27-63)
feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/StudentContent.kt (1)
  • StudentContent (41-110)
feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/ApprovalContent.kt (1)
  • ApprovalContent (34-96)
feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/VoteScreen.kt (1)
feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/VoteItemContent.kt (1)
  • VoteItemContent (11-71)
data/src/dev/kotlin/team.aliens.dms.android.data/voting/repository/VotingRepositoryImpl.kt (1)
data/src/prod/kotlin/team/aliens/dms/android/data/voting/repository/VotingRepositoryImpl.kt (1)
  • networkVotingDataSource (12-31)
feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/OptionContent.kt (1)
feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/TitleContent.kt (1)
  • TitleContent (17-41)
feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/StudentContent.kt (3)
feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/TitleContent.kt (1)
  • TitleContent (17-41)
core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/tab/DmsTabRow.kt (1)
  • DmsTabRow (16-55)
core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/tab/DmsTab.kt (1)
  • DmsTab (14-44)
feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/ApplicationScreen.kt (4)
core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/tab/DmsTabRow.kt (1)
  • DmsTabRow (16-55)
core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/tab/DmsTab.kt (1)
  • DmsTab (14-44)
feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/component/ApplicationContent.kt (1)
  • ApplicationContent (13-43)
feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/component/VoteContent.kt (1)
  • VoteContent (16-48)
feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/component/ApplicationContent.kt (1)
core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/card/DmsApplicationCard.kt (1)
  • DmsApplicationCard (33-116)
feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt (1)
core/ui/src/dev/java/team/aliens/dms/android/core/ui/viewmodel/BaseStateViewModel.kt (1)
  • setState (29-31)
feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/navigation/VoteRoute.kt (1)
feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/VoteScreen.kt (1)
  • Vote (30-74)
feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/viewmodel/VoteViewModel.kt (1)
core/ui/src/dev/java/team/aliens/dms/android/core/ui/viewmodel/BaseStateViewModel.kt (2)
  • setState (29-31)
  • sendEffect (33-35)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
πŸ”‡ Additional comments (15)
core/design-system/src/dev/res/drawable/ic_approve.xml (1)

1-11: No action needed. The ic_approve drawable is correctly designed for its intended use as a selectable voting button in the approval voting UI (ApprovalItem component). The circle outline, which changes color on selection, is semantically appropriateβ€”it represents the "approve" option in the voting interface, with ic_oppose (an X symbol) as its counterpart. The naming and design are intentional and correct.

feature/build.gradle.kts (1)

73-74: No action required. Both network module dependencies serve distinct purposes and are actively used by the feature module. The ProjectPaths.Core.NETWORK dependency provides core exception types (e.g., ConflictException), while ProjectPaths.NETWORK provides feature-specific API services and build configuration. The dependency structure is correct.

feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt (1)

93-95: Verify permissive default when time window is unconfigured.

When startTime or endTime is empty, the function returns true, allowing applications at any time. Confirm this is the intended behavior rather than requiring a configured time window.

data/src/dev/kotlin/team.aliens.dms.android.data/voting/repository/VotingRepositoryImpl.kt (1)

15-33: The dev implementation is correct and matches its interface definition.

The dev VotingRepository interface declares all methods with Result<T> return types, and the VotingRepositoryImpl implementation correctly provides those return types using runCatching. This is not an inconsistencyβ€”the dev and prod flavors intentionally have different interface definitions:

  • Dev interface: Result<List<AllVoteSearch>>, Result<List<VotingItem>>, Result<Unit>
  • Dev implementation: Matches the dev interface βœ“
  • Prod interface: List<AllVoteSearch>, List<VotingItem>, Unit (no Result wrapper)
  • Prod implementation: Matches the prod interface βœ“

Each flavor's implementation correctly implements its own interface, so there is no compilation failure or error-handling inconsistency. The different return types between flavors are an intentional design choice.

Likely an incorrect or invalid review comment.

core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/modifier/DmsShadowModifier.kt (1)

58-59: LGTM!

The enum provides a clean API for selecting shadow presets with meaningful names indicating intensity levels.

core/design-system/src/dev/res/drawable/ic_oppose.xml (1)

1-9: LGTM!

The vector drawable is well-structured. The X-shaped path appropriately represents an "oppose" action. Consider using a theme color attribute (e.g., ?attr/colorError) instead of the hardcoded #FE0F0F for better dark/light theme consistency, but this is optional for icon resources.

feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/navigation/VoteRoute.kt (1)

8-23: LGTM!

The route wrapper follows a clean pattern for separating navigation concerns from screen implementation.

feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/TitleContent.kt (1)

17-41: LGTM!

Clean component implementation with proper theming and consistent layout structure. The date range display using toDateString() is clear and readable.

feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/component/ApplicationContent.kt (1)

13-43: LGTM on the component structure.

The composable correctly wires navigation callbacks to each card and properly forwards appliedTitle only to the "μž”λ₯˜" card. The layout with spacing and padding is well-structured.

feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/VoteScreen.kt (1)

76-123: LGTM!

The VoteScreen composable is well-structured with proper state-driven UI, including loading and enabled states for the submit button.

feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/StudentContent.kt (1)

41-110: Well-structured tabbed student selector.

The implementation correctly uses HorizontalPager with DmsTabRow synchronization via coroutine scope. The grade filtering and student selection logic is clear.

app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt (1)

116-200: Good integration of result store with composition local.

The CompositionLocalProvider pattern for sharing resultStore across navigation destinations is well-implemented, enabling cross-destination result passing.

feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/ApprovalContent.kt (1)

67-93: Hardcoded approve/oppose logic assumes option ordering.

The first option is always treated as "approve" and subsequent options as "oppose". This is fragile if the backend order changes. Consider using a property on VotingItem to determine the option type, or at minimum, add a comment documenting this assumption.

feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/OptionContent.kt (1)

65-101: LGTM!

The OptionItem composable follows a consistent pattern with StudentItem, providing good code uniformity across the voting UI components.

feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/ApplicationScreen.kt (1)

62-127: Well-structured tabbed application screen.

The ApplicationScreen properly implements a two-tab layout with HorizontalPager and correctly passes state and callbacks to child components.

Comment on lines +10 to +14
@Composable
fun Modifier.dmsShadowModifier(
dmsShadowType: DmsShadowType,
shape: Shape = CircleShape,
): Modifier {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's find where dmsDropShadow is defined
fd -t f "\.kt$" -x rg -l "fun.*dmsDropShadow" {}

Repository: team-aliens/DMS-Android

Length of output: 168


🏁 Script executed:

# Also check the full implementation of dmsShadowModifier
cat core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/modifier/DmsShadowModifier.kt

Repository: team-aliens/DMS-Android

Length of output: 1750


🏁 Script executed:

cat core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/modifier/DmsDropShadow.kt

Repository: team-aliens/DMS-Android

Length of output: 1382


Remove the @Composable annotation.

The function only delegates to dmsDropShadow, which is not a composable function and uses drawBehind under the hood. The @Composable annotation is unnecessary and adds overhead.

Additionally, CircleShape as the default is inappropriate for most use casesβ€”shadows are typically applied to cards and buttons with rounded corners, not circular components. Consider removing the default or using a more universally applicable shape.

πŸ€– Prompt for AI Agents
In
core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/modifier/DmsShadowModifier.kt
around lines 10 to 14, remove the unnecessary @Composable annotation from the
dmsShadowModifier function (since it only delegates to non-composable
dmsDropShadow which uses drawBehind) and change the default shape from
CircleShape to a more general RoundedCornerShape (or remove the default
entirely) by updating the function signature accordingly so it’s a plain
extension function returning Modifier and uses a neutral/default rounded corner
shape if a default is required.

alias(libs.plugins.hilt)
alias(libs.plugins.ksp)
alias(libs.plugins.ktlint.gradle)
alias(libs.plugins.serialization)
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

🏁 Script executed:

rg -n --type=kt '@Serializable' data/src/

Repository: team-aliens/DMS-Android

Length of output: 93


🏁 Script executed:

rg '@Serializable' data/src/ --include='*.kt'

Repository: team-aliens/DMS-Android

Length of output: 508


🏁 Script executed:

rg '@Serializable' data/src/

Repository: team-aliens/DMS-Android

Length of output: 49


🏁 Script executed:

fd . data/src/ --type f -e kt && rg -i 'serializ|json|moshi|gson' data/src/

Repository: team-aliens/DMS-Android

Length of output: 12022


Remove the unused serialization plugin.

The Kotlin serialization plugin was added but is not used anywhere in the data module. No @Serializable annotations or serialization-related imports were found across the data models. Remove alias(libs.plugins.serialization) from the build configuration unless it will be actively used.

πŸ€– Prompt for AI Agents
In data/build.gradle.kts around line 10, the Kotlin serialization plugin is
declared but unused; remove the line alias(libs.plugins.serialization) from the
file to avoid including an unnecessary plugin and keep the build configuration
clean. Ensure no other references to the serialization plugin remain in the data
module (build scripts or imports); if later needed, re-add the plugin where
serialization annotations/usages exist.

Comment on lines +7 to +15
data class AllVoteSearch(
val id: UUID = UUID.randomUUID(),
val topicName: String = "",
val description: String = "",
val startTime: LocalDateTime = now,
val endTime: LocalDateTime = now,
val voteType: Vote = Vote.STUDENT_VOTE,
val isVoted: Boolean = false,
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Remove or reconsider the UUID.randomUUID() default parameter.

Using UUID.randomUUID() as a default value for id is problematic because it generates a new random UUID for every instance created with the default. This breaks expected data class behavior:

val vote1 = AllVoteSearch()
val vote2 = AllVoteSearch()
// vote1 != vote2, even though all other fields are identical

This undermines equality checks and can cause subtle bugs in collections, caching, or comparison logic.

πŸ”Ž Recommended fix

Consider one of these alternatives:

Option 1: Remove the default (preferred if ID should always be provided)

 data class AllVoteSearch(
-    val id: UUID = UUID.randomUUID(),
+    val id: UUID,
     val topicName: String = "",
     val description: String = "",
     val startTime: LocalDateTime = now,
     val endTime: LocalDateTime = now,
     val voteType: Vote = Vote.STUDENT_VOTE,
     val isVoted: Boolean = false,
 )

Option 2: Use a sentinel value for "unassigned"

 data class AllVoteSearch(
-    val id: UUID = UUID.randomUUID(),
+    val id: UUID = UUID.fromString("00000000-0000-0000-0000-000000000000"),
     val topicName: String = "",
     val description: String = "",
     val startTime: LocalDateTime = now,
     val endTime: LocalDateTime = now,
     val voteType: Vote = Vote.STUDENT_VOTE,
     val isVoted: Boolean = false,
 )
πŸ€– Prompt for AI Agents
In
data/src/dev/kotlin/team.aliens.dms.android.data/voting/model/AllVoteSearch.kt
around lines 7 to 15, the default UUID.randomUUID() for the id field causes
every default-constructed AllVoteSearch to have a different id which breaks
equality and collection behavior; either remove the default so callers must
supply an id (preferred), or replace it with a stable sentinel (e.g., a fixed
ZERO UUID constant or nullable id) to represent "unassigned" and update
usages/tests accordingly to construct explicit ids when needed.

Comment on lines +97 to +98
val startTime = LocalTime.parse(applicationTime.startTime)
val endTime = LocalTime.parse(applicationTime.endTime)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add error handling for time parsing.

LocalTime.parse() will throw DateTimeParseException if the time strings from the repository are malformed. Wrap in a try-catch or validate format before parsing.

πŸ”Ž Proposed fix
-        val startTime = LocalTime.parse(applicationTime.startTime)
-        val endTime = LocalTime.parse(applicationTime.endTime)
+        val startTime = try {
+            LocalTime.parse(applicationTime.startTime)
+        } catch (e: Exception) {
+            return false
+        }
+        val endTime = try {
+            LocalTime.parse(applicationTime.endTime)
+        } catch (e: Exception) {
+            return false
+        }
πŸ“ 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
val startTime = LocalTime.parse(applicationTime.startTime)
val endTime = LocalTime.parse(applicationTime.endTime)
val startTime = try {
LocalTime.parse(applicationTime.startTime)
} catch (e: Exception) {
return false
}
val endTime = try {
LocalTime.parse(applicationTime.endTime)
} catch (e: Exception) {
return false
}

import androidx.compose.runtime.Composable
import team.aliens.dms.android.core.designsystem.snackbar.DmsSnackBarType
import team.aliens.dms.android.feature.vote.ui.Vote
import java.time.LocalDateTime
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

Remove unused import.

java.time.LocalDateTime is imported but not used in this file.

πŸ”Ž Proposed fix
 import team.aliens.dms.android.core.designsystem.snackbar.DmsSnackBarType
 import team.aliens.dms.android.feature.vote.ui.Vote
-import java.time.LocalDateTime
πŸ“ 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 java.time.LocalDateTime
import team.aliens.dms.android.core.designsystem.snackbar.DmsSnackBarType
import team.aliens.dms.android.feature.vote.ui.Vote
πŸ€– Prompt for AI Agents
In
feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/navigation/VoteRoute.kt
around line 6, the import java.time.LocalDateTime is unused; remove that import
line to clean up the file and avoid unused-import warnings, ensuring no other
code depends on LocalDateTime in this file before committing the change.

Comment on lines +50 to +61
LazyColumn {
items(
items = options,
key = { it.id },
) { option ->
OptionItem(
title = option.votingOptionName,
selected = option.id.toString() == selectItem,
onClick = { onSelect(option.id.toString()) },
)
}
}
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

LazyColumn lacks height constraints.

The LazyColumn doesn't have a Modifier.weight(1f) or fillMaxSize(). Inside a Column, this could cause layout issues where the list doesn't fill available space or becomes unbounded.

πŸ”Ž Proposed fix
-        LazyColumn {
+        LazyColumn(modifier = Modifier.weight(1f)) {
             items(
                 items = options,
                 key = { it.id },
πŸ“ 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
LazyColumn {
items(
items = options,
key = { it.id },
) { option ->
OptionItem(
title = option.votingOptionName,
selected = option.id.toString() == selectItem,
onClick = { onSelect(option.id.toString()) },
)
}
}
LazyColumn(modifier = Modifier.weight(1f)) {
items(
items = options,
key = { it.id },
) { option ->
OptionItem(
title = option.votingOptionName,
selected = option.id.toString() == selectItem,
onClick = { onSelect(option.id.toString()) },
)
}
}
πŸ€– Prompt for AI Agents
In
feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/OptionContent.kt
around lines 50 to 61, the LazyColumn has no height constraints which can cause
unbounded or incorrect layout inside a parent Column; update the LazyColumn call
to pass a Modifier that constrains its height (for example Modifier.weight(1f)
when inside a Column or Modifier.fillMaxSize()/fillMaxHeight() as appropriate),
ensuring the Modifier is imported and applied to the LazyColumn so the list
fills the available space.

VoteScreen(
state = state,
onNavigateBack = onNavigateBack,
onSelectItem = { selectedId -> viewModel.setSelectId(UUID.fromString(selectedId)) },
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

UUID.fromString can throw IllegalArgumentException on invalid input.

If selectedId is not a valid UUID string, this line will crash. Consider adding validation or error handling.

πŸ”Ž Proposed fix
-        onSelectItem = { selectedId -> viewModel.setSelectId(UUID.fromString(selectedId)) },
+        onSelectItem = { selectedId ->
+            runCatching { UUID.fromString(selectedId) }
+                .onSuccess { viewModel.setSelectId(it) }
+        },
πŸ“ 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
onSelectItem = { selectedId -> viewModel.setSelectId(UUID.fromString(selectedId)) },
onSelectItem = { selectedId ->
runCatching { UUID.fromString(selectedId) }
.onSuccess { viewModel.setSelectId(it) }
},
πŸ€– Prompt for AI Agents
In feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/VoteScreen.kt
around line 71, the call UUID.fromString(selectedId) can throw
IllegalArgumentException for invalid input; validate or guard the conversion and
only call viewModel.setSelectId when the string is a valid UUID (e.g., use a
try/catch or runCatching to parse, or pre-validate with a regex/UUID.parse
helper), and on failure either ignore the selection or log/report the invalid
value instead of letting the exception crash the UI.

Comment on lines +28 to +30
init {
fetchVotesByType()
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Potential timing issue: fetchVotesByType() called before state initialization.

The init block calls fetchVotesByType() which reads uiState.value.vote.voteType. At this point, the state contains the default AllVoteSearch() with its default voteType. If the actual vote type is set via initState() later, this fetch happens with the wrong type.

Consider deferring the fetch until after initState() is called, or passing the vote type as a constructor parameter.

πŸ€– Prompt for AI Agents
In
feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/viewmodel/VoteViewModel.kt
around lines 28-30, the init block calls fetchVotesByType() too early (it reads
uiState.value.vote.voteType before initState() may set the intended value);
remove the call from init and instead invoke fetchVotesByType() from initState()
after you update uiState.value (or alternatively accept the desired voteType as
a constructor parameter and use that when initializing), ensuring the fetch runs
only after the uiState has been set to the correct voteType (and if needed
dispatch it on the main/VM scope to maintain thread-safety).

Comment on lines +87 to +90
private fun setButtonEnabled() {
val isSelectIdNotNull = uiState.value.selectId != null
setState { uiState.value.copy(buttonEnabled = isSelectIdNotNull) }
}
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

Incorrect setState usage causes potential race condition.

Using uiState.value.copy() inside setState lambda reads stale state. The lambda parameter it represents the current state and should be used instead.

πŸ”Ž Proposed fix
 private fun setButtonEnabled() {
-    val isSelectIdNotNull = uiState.value.selectId != null
-    setState { uiState.value.copy(buttonEnabled = isSelectIdNotNull) }
+    setState { it.copy(buttonEnabled = it.selectId != null) }
 }
πŸ“ 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
private fun setButtonEnabled() {
val isSelectIdNotNull = uiState.value.selectId != null
setState { uiState.value.copy(buttonEnabled = isSelectIdNotNull) }
}
private fun setButtonEnabled() {
setState { it.copy(buttonEnabled = it.selectId != null) }
}
πŸ€– Prompt for AI Agents
In
feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/viewmodel/VoteViewModel.kt
around lines 87 to 90, replace the current setState usage that reads
uiState.value inside the lambda with a version that uses the lambda parameter;
specifically, change setState { uiState.value.copy(buttonEnabled =
uiState.value.selectId != null) } to use the provided state variable (it) so:
setState { it.copy(buttonEnabled = it.selectId != null) } to avoid reading stale
state.

Comment on lines 92 to 112
internal fun postVote() {
with(uiState.value) {
viewModelScope.launch(Dispatchers.IO) {
setState { uiState.value.copy(isLoading = true, buttonEnabled = false) }
voteRepository.fetchCreateVotingItem(
votingTopicId = uiState.value.vote.id,
selectedId = uiState.value.selectId ?: UUID.randomUUID(),
).onSuccess {
setState { uiState.value.copy(buttonEnabled = false, isLoading = false) }
sendEffect(VoteSideEffect.VoteSuccess)
}.onFailure {
setState { uiState.value.copy(isLoading = false, buttonEnabled = true) }
// Logger.a(it) { it.message.toString() }
when (it) {
is ConflictException -> sendEffect(VoteSideEffect.VoteConflict)
else -> sendEffect(VoteSideEffect.VoteFail)
}
}
}
}
}
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

Multiple issues in postVote function.

  1. with(uiState.value) on line 93 is unusedβ€”the receiver is never referenced inside the block.
  2. Lines 95, 100, 103 use uiState.value.copy() inside setStateβ€”should use it.copy() to avoid race conditions.
  3. Line 98: UUID.randomUUID() as a fallback for null selectId could submit invalid vote data. The vote should not proceed if no selection was made.
πŸ”Ž Proposed fix
 internal fun postVote() {
-    with(uiState.value) {
-        viewModelScope.launch(Dispatchers.IO) {
-            setState { uiState.value.copy(isLoading = true, buttonEnabled = false) }
-            voteRepository.fetchCreateVotingItem(
-                votingTopicId = uiState.value.vote.id,
-                selectedId = uiState.value.selectId ?: UUID.randomUUID(),
-            ).onSuccess {
-                setState { uiState.value.copy(buttonEnabled = false, isLoading = false) }
-                sendEffect(VoteSideEffect.VoteSuccess)
-            }.onFailure {
-                setState { uiState.value.copy(isLoading = false, buttonEnabled = true) }
-//                Logger.a(it) { it.message.toString() }
-                when (it) {
-                    is ConflictException -> sendEffect(VoteSideEffect.VoteConflict)
-                    else -> sendEffect(VoteSideEffect.VoteFail)
-                }
+    val currentState = uiState.value
+    val selectId = currentState.selectId ?: run {
+        sendEffect(VoteSideEffect.VoteFail)
+        return
+    }
+    viewModelScope.launch(Dispatchers.IO) {
+        setState { it.copy(isLoading = true, buttonEnabled = false) }
+        voteRepository.fetchCreateVotingItem(
+            votingTopicId = currentState.vote.id,
+            selectedId = selectId,
+        ).onSuccess {
+            setState { it.copy(buttonEnabled = false, isLoading = false) }
+            sendEffect(VoteSideEffect.VoteSuccess)
+        }.onFailure { error ->
+            setState { it.copy(isLoading = false, buttonEnabled = true) }
+            when (error) {
+                is ConflictException -> sendEffect(VoteSideEffect.VoteConflict)
+                else -> sendEffect(VoteSideEffect.VoteFail)
             }
         }
     }
 }
πŸ€– Prompt for AI Agents
In
feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/viewmodel/VoteViewModel.kt
around lines 92 to 112, the postVote function has three issues: remove the
unused with(uiState.value) wrapper, stop using uiState.value.copy() inside
setState (use the lambda's it.copy() to avoid race conditions), and do not
fallback to UUID.randomUUID() when selectId is null β€” instead check selectId
before launching the vote, return early (and emit a suitable side effect or
setState to re-enable the button) so no vote is submitted with an invalid
selection. Ensure setState calls use the provided state lambda (it) and that the
coroutine is only started when a non-null selection exists.

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: 4

Caution

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

⚠️ Outside diff range comments (1)
network/src/dev/kotlin/team.aliens.dms.android.network/voting/apiservice/VotingApiService.kt (1)

28-33: Retrofit does not natively support Result<T> return types.

Retrofit API methods must return Call<T>, Response<T>, or response objects directly. The dev flavor's Result<Unit>? return type will fail at runtime when Retrofit attempts to create the proxy implementation, as no custom CallAdapter.Factory for Result types is registered in the codebase.

The prod flavor correctly returns Response<Unit>?. Align the dev flavor with this approach and wrap the result in Result at the data source layer using runCatching:

 @POST("/votes/student/{voting-topic-id}")
 @RequiresAccessToken
 suspend fun fetchCreateVotingItem(
     @Path("voting-topic-id") votingTopicId: UUID,
     @Query("selected-id") selectedId: UUID,
-): Result<Unit>?
+): Response<Unit>?
♻️ Duplicate comments (1)
feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/VoteScreen.kt (1)

68-68: UUID.fromString can throw IllegalArgumentException on invalid input.

This issue was previously flagged. If selectedId is not a valid UUID string, this line will crash. Add validation or wrap in error handling as suggested in the earlier review.

πŸ”Ž Proposed fix
-        onSelectItem = { selectedId -> viewModel.setSelectId(UUID.fromString(selectedId)) },
+        onSelectItem = { selectedId ->
+            runCatching { UUID.fromString(selectedId) }
+                .onSuccess { viewModel.setSelectId(it) }
+        },
🧹 Nitpick comments (1)
core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/button/Button.kt (1)

369-369: Consider adding a default value for consistency.

The isLoading parameter lacks a default value, requiring callers to always specify it. For consistency with DmsButton (which defaults to false), consider adding a default.

πŸ”Ž Proposed change
-    isLoading: Boolean,
+    isLoading: Boolean = false,
πŸ“œ Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 33f9f15 and 55af2a7.

πŸ“’ Files selected for processing (8)
  • core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/button/Button.kt
  • data/src/dev/kotlin/team.aliens.dms.android.data/voting/repository/VotingRepositoryImpl.kt
  • feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/component/VoteContent.kt
  • feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/VoteScreen.kt
  • feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/viewmodel/VoteViewModel.kt
  • network/src/dev/kotlin/team.aliens.dms.android.network/voting/apiservice/VotingApiService.kt
  • network/src/dev/kotlin/team.aliens.dms.android.network/voting/datasource/NetworkVotingDataSource.kt
  • network/src/dev/kotlin/team.aliens.dms.android.network/voting/datasource/NetworkVotingDataSourceImpl.kt
🚧 Files skipped from review as they are similar to previous changes (3)
  • feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/component/VoteContent.kt
  • feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/viewmodel/VoteViewModel.kt
  • data/src/dev/kotlin/team.aliens.dms.android.data/voting/repository/VotingRepositoryImpl.kt
🧰 Additional context used
🧬 Code graph analysis (2)
network/src/dev/kotlin/team.aliens.dms.android.network/voting/datasource/NetworkVotingDataSourceImpl.kt (1)
core/network/src/main/java/team/aliens/dms/android/core/network/util/NetworkRequestHandler.kt (1)
  • handleNetworkRequest (15-50)
feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/VoteScreen.kt (2)
feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/VoteItemContent.kt (1)
  • VoteItemContent (11-71)
core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/button/Button.kt (1)
  • DmsLayeredButton (359-391)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
πŸ”‡ Additional comments (4)
feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/VoteScreen.kt (3)

27-36: LGTM! Clean ViewModel integration.

The function signature and ViewModel setup follow standard Compose and Hilt patterns correctly. State collection with lifecycle awareness is properly implemented.


73-88: Clean UI structure.

The VoteScreen layout is well-organized with proper background styling and top app bar integration. The function signature clearly defines all dependencies.


103-118: Excellent button implementation with proper state management.

The DmsLayeredButton is well-configured with:

  • Loading state to prevent duplicate submissions
  • Enabled state for validation feedback
  • Appropriate shape for bottom-anchored positioning
  • Clear call-to-action text

The state-driven approach ensures good UX during vote submission.

network/src/dev/kotlin/team.aliens.dms.android.network/voting/datasource/NetworkVotingDataSource.kt (1)

15-18: Verify consistency after fixing the API service layer.

The non-nullable Result<Unit> return type is appropriate at the data source interface level. However, the API service currently declares Result<Unit>? (nullable), creating a type mismatch.

Once the API service is corrected to return Response<Unit>? (per the previous comment), ensure this interface and its implementation properly handle the wrapping.

buttonType: ButtonType,
buttonColor: ButtonColor,
enabled: Boolean = true,
shape: RoundedCornerShape,
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

Shape parameter not forwarded to inner button despite comment intent.

The Korean comment on line 386-387 indicates the intention to pass a shape to the button, but the DmsButton call doesn't include a shape parameter. Additionally, the parameter is restricted to RoundedCornerShape while DmsButton accepts any Shape, making this inconsistently restrictive.

πŸ”Ž Proposed fix to forward shape parameter

If the intent is to pass the shape to the inner button:

         DmsButton(
             modifier = Modifier
                 .fillMaxWidth()
                 .align(Alignment.Center),
             text = text,
             buttonType = buttonType,
             buttonColor = buttonColor,
             enabled = enabled,
-            // 3. λ²„νŠΌμ—λ„ λ³„λ„μ˜ shapeλ₯Ό μ „λ‹¬ν•©λ‹ˆλ‹€.
+            shape = shape,
             isLoading = isLoading,
             onClick = onClick,
         )

Alternatively, if the outer shape should remain independent, consider removing the misleading comment and updating the parameter type to accept generic Shape:

-    shape: RoundedCornerShape,
+    shape: Shape,

Also applies to: 378-389

πŸ€– Prompt for AI Agents
In
core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/button/Button.kt
around line 366 (and similarly 378–389), the shape parameter is declared as
RoundedCornerShape but is not forwarded to the inner DmsButton call and the type
is unnecessarily restrictive; update the outer function to accept a generic
Shape (androidx.compose.ui.graphics.Shape) and pass that shape through to the
inner DmsButton shape parameter, or if outer shape should not be forwarded,
remove or update the misleading comment and change the parameter type to Shape
(or remove it entirely) so the signature and behavior are consistent.

Comment on lines +29 to +31
title: String,
startTime: String,
endTime: String,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's locate and examine the VoteScreen.kt file
find . -name "VoteScreen.kt" -type f

Repository: team-aliens/DMS-Android

Length of output: 145


🏁 Script executed:

# Once we find it, let's examine the relevant lines
cat -n feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/VoteScreen.kt | head -100

Repository: team-aliens/DMS-Android

Length of output: 4326


🏁 Script executed:

# Search for VoteViewModel to understand the initState implementation
find . -name "*VoteViewModel*" -type f

Repository: team-aliens/DMS-Android

Length of output: 155


🏁 Script executed:

# Look for date parsing related code in the vote feature
rg -n "LocalDateTime|parse|initState" feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/

Repository: team-aliens/DMS-Android

Length of output: 2338


🏁 Script executed:

# Find the toLocalDateTime extension function
find . -path "*/shared/date*" -name "*.kt" -type f

Repository: team-aliens/DMS-Android

Length of output: 651


🏁 Script executed:

# Search for the toLocalDateTime implementation
rg -n "fun.*toLocalDateTime" --type=kt

Repository: team-aliens/DMS-Android

Length of output: 93


🏁 Script executed:

# Get the full VoteViewModel implementation
cat -n ./feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/viewmodel/VoteViewModel.kt

Repository: team-aliens/DMS-Android

Length of output: 5634


🏁 Script executed:

# Get the Extensions.kt file which likely contains toLocalDateTime
cat -n ./shared/date/src/main/java/team/aliens/dms/android/shared/date/Extensions.kt

Repository: team-aliens/DMS-Android

Length of output: 1614


🏁 Script executed:

# Also check the test file to understand expected behavior
cat -n ./shared/date/src/test/java/team/aliens/dms/android/shared/date/junit/ExtensionsTest.kt

Repository: team-aliens/DMS-Android

Length of output: 3176


Add error handling for date string parsing in initState.

The String.toLocalDateTime() extension (line 19-20 in shared/date/Extensions.kt) calls LocalDateTime.parse() without catching DateTimeParseException. The initState function passes startTime and endTime strings directly to this function with no error handling. Invalid date formats will crash the application. Wrap the parsing in a try-catch block or use a safe parsing utility that returns a default value or null on parsing failure.

πŸ€– Prompt for AI Agents
In feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/VoteScreen.kt
around lines 29-31, the startTime and endTime strings are passed directly to
String.toLocalDateTime() which can throw DateTimeParseException; wrap those
parses in a try-catch (catching DateTimeParseException) or call a safe parsing
helper that returns null/default on failure, assign the parsed values to
nullable state (or a sensible default LocalDateTime) and update downstream
code/UI to handle null/defaults so the app does not crash on invalid date
formats.

Comment on lines +21 to +23
override suspend fun fetchCreateVotingItem(votingTopicId: UUID, selectedId: UUID): Result<Unit> = runCatching {
handleNetworkRequest { votingApiService.fetchCreateVotingItem(votingTopicId, selectedId) }
}
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

Logic error: double-wrapping and bypassed error handling.

The implementation has two critical issues:

  1. Double-wrapping: votingApiService.fetchCreateVotingItem returns Result<Unit>?, which is then passed to handleNetworkRequest (returning Result<Unit>?), and wrapped again with runCatching. This creates nested Result types rather than a single Result<Unit>.

  2. Bypassed error handling: handleNetworkRequest is designed to catch HttpException and map status codes to domain exceptions. If the API service returns Result<Unit>?, no exceptions are thrown, so handleNetworkRequest's error handling logic is completely bypassed.

πŸ”Ž Correct implementation

After fixing the API service to return Response<Unit>?:

-override suspend fun fetchCreateVotingItem(votingTopicId: UUID, selectedId: UUID): Result<Unit> = runCatching {
-    handleNetworkRequest { votingApiService.fetchCreateVotingItem(votingTopicId, selectedId) }
-}
+override suspend fun fetchCreateVotingItem(votingTopicId: UUID, selectedId: UUID): Result<Unit> =
+    runCatching {
+        handleNetworkRequest { votingApiService.fetchCreateVotingItem(votingTopicId, selectedId) }
+    }

This ensures:

  • API service returns Response<Unit>? (standard Retrofit)
  • handleNetworkRequest catches and maps HTTP errors to exceptions
  • runCatching wraps the entire operation in Result<Unit>

Committable suggestion skipped: line range outside the PR's diff.

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: 4

♻️ Duplicate comments (1)
core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/button/Button.kt (1)

364-364: Shape parameter not forwarded to inner button.

The shape parameter is accepted but not passed to the inner DmsButton call. This is the same issue flagged in the previous review. Additionally, the parameter type is restricted to RoundedCornerShape while DmsButton accepts any Shape, making this unnecessarily restrictive.

πŸ”Ž Proposed fix
 fun DmsLayeredButton(
     modifier: Modifier = Modifier,
     text: String,
     buttonType: ButtonType,
     buttonColor: ButtonColor,
     enabled: Boolean = true,
-    shape: RoundedCornerShape,
+    shape: Shape = RoundedCornerShape(16.dp),
     backgroundColor: Color = DmsTheme.colorScheme.surfaceVariant,
     layerOffset: Dp = 24.dp,
     isLoading: Boolean,
     onClick: () -> Unit,
 ) {
     Box(
         modifier = modifier
             .background(color = backgroundColor, shape = shape)
             .windowInsetsPadding(WindowInsets.navigationBars)
             .padding(layerOffset),
     ) {
         DmsButton(
             modifier = Modifier
                 .fillMaxWidth()
                 .align(Alignment.Center),
             text = text,
             buttonType = buttonType,
             buttonColor = buttonColor,
             enabled = enabled,
+            shape = shape,
             isLoading = isLoading,
             onClick = onClick,
         )
     }
 }

Also applies to: 376-386

🧹 Nitpick comments (5)
core/jwt/src/main/java/team/aliens/dms/android/core/jwt/JwtProviderImpl.kt (1)

115-124: Good fix for local token expiry, but consider adding logging for other failures.

The addition of CannotUseRefreshTokenException handling correctly addresses the inconsistency where remote 401 cleared caches but local expiry didn't. However, the empty else block still silently ignores network errors, 5xx responses, and other reissue failures, making debugging difficult.

πŸ”Ž Suggested improvement: Add logging to the else branch
                exception is CannotUseRefreshTokenException -> {
                    [email protected]()
                }
-                else -> {}
+                else -> {
+                    // Log unhandled reissue failures for debugging
+                    timber.log.Timber.w(exception, "Token reissue failed")
+                }
            }
app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt (2)

166-176: Simplify back navigation by using the captured nav key directly.

Creating a new VoteScreenNav instance for removal is unnecessary since voteNav is already available and data class equality ensures they match.

πŸ”Ž Proposed fix
                     entry<VoteScreenNav> { voteNav ->
                         VoteRoute(
                             title = voteNav.title,
                             startTime = voteNav.startTime,
                             endTime = voteNav.endTime,
-                            onNavigateBack = { backStack.remove(VoteScreenNav(voteNav.title, voteNav.startTime, voteNav.endTime)) },
+                            onNavigateBack = { backStack.remove(voteNav) },
                             onShowSnackBar = { snackBarType, message ->
                                 appState.showSnackBar(snackBarType, message)
                             }
                         )
                     }

177-187: Extract result key to a constant.

The magic string "remain_application_result" should be extracted to a named constant to prevent typos and ensure consistency when retrieving this result elsewhere.

πŸ”Ž Proposed fix

Define a constant at file or companion object level:

private const val REMAIN_APPLICATION_RESULT_KEY = "remain_application_result"

Then update the usage:

                     entry<RemainScreenNav> {
                         RemainApplicationRoute(
                             onNavigateBack = { title ->
-                                resultStore.setResult<String?>("remain_application_result", title)
+                                resultStore.setResult<String?>(REMAIN_APPLICATION_RESULT_KEY, title)
                                 backStack.remove(RemainScreenNav)
                             },
                             onShowSnackBar = { snackBarType, message ->
                                 appState.showSnackBar(snackBarType, message)
                             }
                         )
                     }
app/build.gradle.kts (1)

143-143: Remove the commented-out desugaring dependency.

With minSdk 26, core library desugaring is generally not required since Java 8+ APIs (such as java.time) are natively available. The commented-out line should either be removed entirely or, if there's a specific reason to keep it, documented with a clear explanation.

πŸ”Ž Proposed fix
-//    coreLibraryDesugaring(libs.desugar.jdk.libs)
feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/RemainApplicationScreen.kt (1)

80-80: Extract hardcoded Korean text to string resource.

The time window notice text is hardcoded directly in the composable, which makes internationalization and maintenance more difficult. Consider extracting it to a string resource with format arguments.

πŸ”Ž Example approach

In strings.xml:

<string name="remain_application_time_notice">μž”λ₯˜ μ‹ μ²­ μ‹œκ°„μ€ %1$s %2$s ~ %3$s %4$s κΉŒμ§€ μž…λ‹ˆλ‹€.</string>

Then in the composable:

text = stringResource(
    R.string.remain_application_time_notice,
    state.remainsApplicationTime.startDayOfWeek.toLocale(),
    state.remainsApplicationTime.startTime,
    state.remainsApplicationTime.endDayOfWeek.toLocale(),
    state.remainsApplicationTime.endTime
)
πŸ“œ Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 55af2a7 and f08f3d1.

πŸ“’ Files selected for processing (9)
  • app/build.gradle.kts
  • app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt
  • buildSrc/src/main/kotlin/ProjectProperties.kt
  • core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/button/Button.kt
  • core/jwt/src/main/java/team/aliens/dms/android/core/jwt/JwtProviderImpl.kt
  • data/src/dev/kotlin/team.aliens.dms.android.data/remain/model/RemainsOption.kt
  • feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/RemainApplicationScreen.kt
  • feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt
  • gradle/libs.versions.toml
🧰 Additional context used
🧬 Code graph analysis (3)
app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt (5)
core/ui/src/dev/java/team/aliens/dms/android/core/ui/navigation/ResultStore.kt (1)
  • rememberResultStore (39-44)
feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/navigation/ApplicationRoute.kt (1)
  • ApplicationRoute (9-24)
feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/navigation/VoteRoute.kt (1)
  • VoteRoute (8-23)
feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/navigation/RemainApplicationRoute.kt (1)
  • RemainApplicationRoute (7-16)
feature/src/dev/kotlin/team/aliens/dms/android/feature/meal/navigation/MealRoute.kt (1)
  • MealRoute (6-13)
feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/RemainApplicationScreen.kt (4)
feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/component/DmsFloatingNotice.kt (1)
  • DmsFloatingNotice (29-65)
core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/card/DmsApplicationCard.kt (1)
  • DmsApplicationCard (33-116)
feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt (1)
  • setSelectRemainsOption (52-54)
core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/button/Button.kt (1)
  • DmsButton (260-356)
feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt (1)
core/ui/src/dev/java/team/aliens/dms/android/core/ui/viewmodel/BaseStateViewModel.kt (1)
  • setState (29-31)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
πŸ”‡ Additional comments (8)
core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/button/Button.kt (1)

7-15: LGTM! Imports support the new layered button functionality.

The new imports are appropriate for implementing DmsLayeredButton. Note that offset and systemBarsPadding are imported but not used in the visible code; however, they may be needed for future enhancements or other parts of the file not shown.

Also applies to: 30-30

app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt (5)

11-35: LGTM!

The new imports for CompositionLocalProvider, LocalResultStore, rememberResultStore, and the new route components are all properly utilized in the file. The previously flagged unused android.util.Log import has been removed.


52-56: LGTM!

The new navigation keys follow the established pattern in the file. Using String for startTime and endTime simplifies serialization for navigation state persistence.


72-72: LGTM!

Proper initialization of the result store using rememberResultStore(), which is correctly provided via CompositionLocalProvider at line 113.


151-165: Verify time string format is suitable for display.

The startTime.toString() and endTime.toString() conversions will use the default LocalDateTime (or similar) string representation. Ensure the VoteRoute either parses these back or that the default format is acceptable for UI display.


191-195: LGTM!

Using backStack.remove(MealScreenNav) is appropriate since MealScreenNav is a data object (singleton), ensuring correct equality comparison.

gradle/libs.versions.toml (1)

42-42: LGTM!

The version catalog change is consistent with the MIN_SDK update in ProjectProperties.kt, maintaining proper synchronization across build configuration files.

feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt (1)

64-81: Good improvement in null handling.

The use of ?.let properly prevents the function from executing when selectRemainsOptionId is null, addressing the previous critical issue where a random UUID would be sent to the server. Since the UI button is disabled when no option is selected (line 117 in RemainApplicationScreen.kt), this defensive approach is appropriate.

object ProjectProperties {
const val COMPILE_SDK = 36
const val MIN_SDK = 24
const val MIN_SDK = 26
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

🏁 Script executed:

#!/bin/bash
# Description: Search for references to old minSdk values that might need updating

# Search for hardcoded references to API 24 or 25
rg -nP '\b(24|25)\b' --type=xml -g '**/AndroidManifest.xml' -C2

# Search for minSdkVersion references in XML files
rg -nP 'minSdkVersion.*["\']?(24|25)["\']?' --type=xml -C2

# Search for SDK_INT comparisons that might need adjustment
rg -nP 'SDK_INT\s*[<>=]+\s*(24|25|Build\.VERSION_CODES\.(N|N_MR1))' --type=kotlin --type=java -C3

Repository: team-aliens/DMS-Android

Length of output: 212


🏁 Script executed:

#!/bin/bash
# Description: Search for references to old minSdk values that might need updating

echo "=== Searching for minSdkVersion in XML files ==="
rg 'minSdkVersion' --type=xml -C2

echo ""
echo "=== Searching for hardcoded references to API 24 or 25 ==="
rg '\b(24|25)\b' --type=xml --type=kotlin --type=java -C1

echo ""
echo "=== Searching for SDK_INT comparisons with N or N_MR1 ==="
rg 'SDK_INT|Build\.VERSION_CODES\.(N|N_MR1)' --type=kotlin --type=java -C2

echo ""
echo "=== Checking current MIN_SDK definition ==="
rg 'MIN_SDK' --type=kotlin

Repository: team-aliens/DMS-Android

Length of output: 50380


🏁 Script executed:

#!/bin/bash
# More targeted search for minSdk configuration and version checks

echo "=== Searching for build.gradle files with minSdk ==="
fd -e gradle -e kts | head -20 | xargs grep -l minSdk 2>/dev/null || echo "No matches"

echo ""
echo "=== Checking actual minSdk values in gradle files ==="
fd -e gradle -e kts | xargs grep -n "minSdk" 2>/dev/null | head -20

echo ""
echo "=== Searching for SDK_INT version checks in code ==="
rg 'SDK_INT' --type=kotlin --type=java -A2 | head -40

echo ""
echo "=== Searching for Build.VERSION_CODES references ==="
rg 'Build\.VERSION_CODES\.(N|NOUGAT)' --type=kotlin --type=java | head -20

echo ""
echo "=== Verify ProjectProperties.kt content ==="
cat buildSrc/src/main/kotlin/ProjectProperties.kt

Repository: team-aliens/DMS-Android

Length of output: 5444


🏁 Script executed:

#!/bin/bash
# Check the TODO and look for any version checks that are now unnecessary

echo "=== MealWorker.kt context around TODO ==="
cat -n core/widget/src/prod/kotlin/team/aliens/dms/android/core/widget/MealWorker.kt | grep -A 10 -B 5 "TODO"

echo ""
echo "=== Searching for version checks <= 26 that are now always true or false ==="
rg 'SDK_INT\s*[<].*\s*(26|25|24|O|N)' --type=kotlin --type=java

echo ""
echo "=== Check if there are conditions that assume API < 26 ==="
rg 'SDK_INT\s*<\s*(26|Build\.VERSION_CODES\.O)' --type=kotlin --type=java

Repository: team-aliens/DMS-Android

Length of output: 1333


Remove unreachable code in MealWorker.kt related to the minSdk increase.

The change from minSdk 24 to 26 is complete with no residual references to APIs 24-25 found in the codebase. However, the else branch in MealWorker.kt (line 42) containing TODO("VERSION.SDK_INT < O") is now unreachable since Build.VERSION_CODES.O equals API level 26. Remove this dead code path and keep only the PeriodicWorkRequestBuilder branch.

πŸ€– Prompt for AI Agents
In buildSrc/src/main/kotlin/ProjectProperties.kt (minSdk changed to 26) and in
app/src/.../MealWorker.kt around line 42, remove the unreachable else branch
that contains TODO("VERSION.SDK_INT < O") and any dead references to
VERSION.SDK_INT checks; keep only the PeriodicWorkRequestBuilder branch (the
code path for API >= O), delete the else block and its TODO, and remove any
now-unused imports or variables introduced solely for that dead branch.

buttonColor: ButtonColor,
enabled: Boolean = true,
shape: RoundedCornerShape,
backgroundColor: Color = DmsTheme.colorScheme.surfaceVariant,
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

Critical: backgroundColor parameter is unused; hard-coded white breaks theming.

Two related issues:

  1. The backgroundColor parameter (line 365) is declared with a theme-aware default but never used in the function body.
  2. Line 372 hard-codes Color.White for the background, breaking theming and dark mode support.

This suggests an incomplete implementation where the developer likely intended to use the backgroundColor parameter instead of the hard-coded value.

πŸ”Ž Proposed fix
     Box(
         modifier = modifier
-            .background(color = Color.White, shape = shape)
+            .background(color = backgroundColor, shape = shape)
             .windowInsetsPadding(WindowInsets.navigationBars)
             .padding(layerOffset),
     ) {

Also applies to: 372-372

πŸ€– Prompt for AI Agents
In
core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/button/Button.kt
around lines 365 and 372, the backgroundColor parameter declared at line 365 is
never used and the composable hard-codes Color.White at line 372, breaking
theming and dark mode; fix this by replacing the hard-coded Color.White with the
backgroundColor parameter (or a resolved theme-aware color) so the
passed-in/default DmsTheme.colorScheme.surfaceVariant is actually applied, and
remove any unused imports/values if they become redundant.

@uson1004 uson1004 merged commit 3bf8910 into develop Dec 29, 2025
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat μƒˆλ‘œμš΄ κΈ°λŠ₯을 μΆ”κ°€ ν•  경우

Projects

None yet

Development

Successfully merging this pull request may close these issues.

μ‹ μ²­ νŽ˜μ΄μ§€ κ΅¬ν˜„

2 participants