Skip to content

Conversation

@chanubc
Copy link
Member

@chanubc chanubc commented Oct 25, 2025

โœ… ๐—–๐—ต๐—ฒ๐—ฐ๐—ธ-๐—Ÿ๐—ถ๐˜€๐˜

  • mergeํ•  ๋ธŒ๋žœ์น˜์˜ ์œ„์น˜๋ฅผ ํ™•์ธํ•ด ์ฃผ์„ธ์š”(mainโŒ/developโญ•)
  • ๋ฆฌ๋ทฐ๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ ๋ฆฌ๋ทฐ์–ด๋ฅผ ์ง€์ •ํ•ด ์ฃผ์„ธ์š”
  • P1 ๋‹จ๊ณ„์˜ ๋ฆฌ๋ทฐ๋Š” ํ•„์ˆ˜๋กœ ๋ฐ˜์˜ํ•ฉ๋‹ˆ๋‹ค.
  • Approve๋œ PR์€ assigner๊ฐ€ ๋จธ์ง€ํ•˜๊ณ , ์ˆ˜์ • ์š”์ฒญ์ด ์˜จ ๊ฒฝ์šฐ ์ˆ˜์ • ํ›„ ๋‹ค์‹œ push๋ฅผ ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“Œ ๐—œ๐˜€๐˜€๐˜‚๐—ฒ๐˜€

๐Ÿ“Ž๐—ช๐—ผ๐—ฟ๐—ธ ๐——๐—ฒ๐˜€๐—ฐ๐—ฟ๐—ถ๐—ฝ๐˜๐—ถ๐—ผ๐—ป

  • workmanager๋ฅผ ํ™œ์šฉํ•œ ์ž์ •์— quiz ์—ฌ๋ถ€ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์Šค์ผ€์ค„๋ง
  • quiz api ์—ฐ๊ฒฐ
  • quiz main ํ™”๋ฉด ํƒ€์ด๋จธ ๊ตฌํ˜„

๐Ÿ“ท ๐—ฆ๐—ฐ๐—ฟ๐—ฒ๐—ฒ๐—ป๐˜€๐—ต๐—ผ๐˜

KakaoTalk_20251026_031546946.mp4
image

๐Ÿ’ฌ ๐—ง๐—ผ ๐—ฅ๐—ฒ๐˜ƒ๐—ถ๐—ฒ๐˜„๐—ฒ๐—ฟ๐˜€

Summary by CodeRabbit

  • ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ
    • ์•ฑ์— ํ€ด์ฆˆ ๊ธฐ๋Šฅ์ด ์ถ”๊ฐ€๋˜์–ด ๋งค์ผ ํ€ด์ฆˆ๋ฅผ ํ’€๊ณ  ๊ฒฐ๊ณผ(ํš๋“ XP, ์ •๋‹ต๋ฅ , ์†Œ์š”์‹œ๊ฐ„)๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ์ž์ •๊นŒ์ง€ ๋‚จ์€ ์‹œ๊ฐ„์ด ์‹ค์‹œ๊ฐ„์œผ๋กœ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.
    • ํ€ด์ฆˆ ์ œ์ถœ ํ›„ ๊ฒฐ๊ณผ ํ™”๋ฉด๊ณผ ์˜ค๋ฅ˜ ์Šค๋‚ต๋ฐ”๊ฐ€ ๊ฐœ์„ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
    • ๋งค์ผ ์ž๋™์œผ๋กœ ํ€ด์ฆˆ ์™„๋ฃŒ ์ƒํƒœ๊ฐ€ ์ดˆ๊ธฐํ™”๋˜์–ด ๋‹ค์Œ ๋‚  ๋‹ค์‹œ ์ฐธ์—ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

# Conflicts:
#	core/data/src/main/java/com/teamwable/data/repository/UserInfoRepository.kt
#	core/data/src/main/java/com/teamwable/data/repositoryimpl/DefaultUserInfoRepository.kt
#	core/datastore/src/main/java/com/teamwable/datastore/datasource/DefaultWablePreferenceDatasource.kt
#	core/datastore/src/main/java/com/teamwable/datastore/datasource/WablePreferencesDataSource.kt
@coderabbitai
Copy link

coderabbitai bot commented Oct 25, 2025

Walkthrough

WorkManager ๊ธฐ๋ฐ˜ ์ผ์ผ ๋ฆฌ์…‹ ์Šค์ผ€์ค„๋Ÿฌ์™€ ํ€ด์ฆˆ ๋„คํŠธ์›Œํฌ/๋ฐ์ดํ„ฐ์Šคํ… ๊ณ„์ธต, ๊ด€๋ จ DIยท๋ทฐ๋ชจ๋ธยทUI๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  ์ผ๋ถ€ ๋””์ž์ธ์‹œ์Šคํ…œ ํŒŒ๋ผ๋ฏธํ„ฐ๋ช…๊ณผ ๋งค๋‹ˆํŽ˜์ŠคํŠธ provider ์„ ์–ธ์„ ์ •๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.

Changes

์ฝ”ํ˜ธํŠธ / ํŒŒ์ผ(s) ๋ณ€๊ฒฝ ์š”์•ฝ
๋นŒ๋“œ ๋ฐ ๋ฒ„์ „ ์นดํƒˆ๋กœ๊ทธ
gradle/libs.versions.toml, app/build.gradle.kts, core/data/build.gradle.kts, feature/quiz/build.gradle.kts
WorkManager ๋ฐ Hilt-Work ์˜์กด์„ฑ ์ถ”๊ฐ€(๋ฒ„์ „ ํ‚ค ์ถ”๊ฐ€). ๋ชจ๋“ˆ ์˜์กด์„ฑ ์ผ๋ถ€ ๋ณ€๊ฒฝ(:core:common โ†’ :core:domain).
์•ฑ ๋งค๋‹ˆํŽ˜์ŠคํŠธ
app/src/main/AndroidManifest.xml
androidx.startup.InitializationProvider ์„ ์–ธ์„ tools:node="remove"๋กœ ์ œ๊ฑฐ.
์•ฑ ์ดˆ๊ธฐํ™” / WorkManager ํ†ตํ•ฉ
app/src/main/java/com/teamwable/wable/WableApp.kt
HiltWorkerFactory ์ฃผ์ž… ๋ฐ WorkManager ์ดˆ๊ธฐํ™” ํ˜ธ์ถœ, DailyTaskScheduler ์Šค์ผ€์ค„๋ง ํ˜ธ์ถœ ์ถ”๊ฐ€.
์Šค์ผ€์ค„๋Ÿฌ ์ธํ„ฐํŽ˜์ด์Šค
core/common/src/main/java/com/teamwable/common/intentprovider/DailyTaskScheduler.kt
DailyTaskScheduler ์ธํ„ฐํŽ˜์ด์Šค ์ถ”๊ฐ€ (scheduleDailyReset()).
์Šค์ผ€์ค„๋Ÿฌ ๊ตฌํ˜„ ๋ฐ ์›Œ์ปค
core/data/src/main/java/com/teamwable/data/worker/DailyTaskSchedulerImpl.kt, core/data/src/main/java/com/teamwable/data/worker/ResetQuizStatusWorker.kt
์ž์ • ๊ธฐ์ค€ ์ดˆ๊ธฐ ๋”œ๋ ˆ์ด ๊ณ„์‚ฐ ํ›„ 24์‹œ๊ฐ„ ์ฃผ๊ธฐ PeriodicWork ๋“ฑ๋ก, ResetQuizStatusWorker๋กœ ํ€ด์ฆˆ ์™„๋ฃŒ ์ƒํƒœ ์ดˆ๊ธฐํ™”(์žฌ์‹œ๋„ ์ฒ˜๋ฆฌ ํฌํ•จ).
DI ๋ชจ๋“ˆ
core/data/src/main/java/com/teamwable/data/di/SchedulerModule.kt, core/data/src/main/java/com/teamwable/data/di/RepositoryModule.kt
DailyTaskScheduler ๋ฐ”์ธ๋”ฉ ์ถ”๊ฐ€ ๋ฐ QuizRepository ๋ฐ”์ธ๋”ฉ ์ถ”๊ฐ€.
๋„คํŠธ์›Œํฌ ๊ณ„์ธต & DTO
core/network/.../QuizService.kt, .../dto/request/RequestQuizScoreDto.kt, .../dto/response/quiz/ResponseQuizDto.kt, .../ResponseQuizScoreDto.kt, core/network/src/main/java/com/teamwable/network/di/ServiceModule.kt
Retrofit ์„œ๋น„์Šค(GET /api/v1/quiz, PATCH /api/v1/quiz/grade) ์ถ”๊ฐ€ ๋ฐ DTO(์š”์ฒญ/์‘๋‹ต) ์ถ”๊ฐ€, DI ์ œ๊ณต์ž ๋“ฑ๋ก.
๋ฐ์ดํ„ฐ ๋ ˆ์ด์–ด: ๋ฆฌํฌ์ง€ํ† ๋ฆฌ/๋งคํผ/๊ตฌํ˜„
core/data/src/main/java/com/teamwable/data/repository/QuizRepository.kt, .../repositoryimpl/DefaultQuizRepository.kt, .../mapper/toModel/QuizMapper.kt
QuizRepository ์ธํ„ฐํŽ˜์ด์Šค ๋ฐ ๊ตฌํ˜„์ฒด ์ถ”๊ฐ€, DTOโ†”๋„๋ฉ”์ธ ๋งคํ•‘ ํ•จ์ˆ˜ ์ถ”๊ฐ€.
DataStore ๋ฐ ์‚ฌ์šฉ์ž ์ƒํƒœ
core/datastore/.../WablePreferencesDataSource.kt, .../DefaultWablePreferenceDatasource.kt, core/data/.../repository/UserInfoRepository.kt, .../DefaultUserInfoRepository.kt
isQuizCompleted Flow ๋ฐ updateIsQuizCompleted() ์ถ”๊ฐ€, UserInfoRepository์— get/save ํ€ด์ฆˆ ์™„๋ฃŒ ์ƒํƒœ ์ถ”๊ฐ€.
๋„๋ฉ”์ธ ์œ ์Šค์ผ€์ด์Šค
core/domain/src/main/java/com/teamwable/domain/usecase/GetRemainingTimeUseCase.kt
์ž์ •๊นŒ์ง€ ๋‚จ์€ ์‹œ๊ฐ„(์‹œ๊ฐ„, ๋ถ„)์„ ๋งค๋ถ„ ๊ฐฑ์‹ ํ•˜๋Š” Flow ์œ ์Šค์ผ€์ด์Šค ์ถ”๊ฐ€.
๋ชจ๋ธ
core/model/src/main/java/com/teamwable/model/quiz/QuizModel.kt, QuizResultModel.kt, QuizScoreModel.kt
ํ€ด์ฆˆ ๊ด€๋ จ ๋„๋ฉ”์ธ ๋ชจ๋ธ(QuizModel, QuizResultModel, QuizScoreModel) ์ถ”๊ฐ€.
ํ€ด์ฆˆ ํ”ผ์ฒ˜: ViewModel / ํ™”๋ฉด / ๋„ค๋น„๊ฒŒ์ด์…˜
feature/quiz/src/main/java/com/teamwable/quiz/** (์—ฌ๋Ÿฌ ํŒŒ์ผ, ์˜ˆ: QuizStartViewModel.kt, QuizStartScreen.kt, QuizMainViewModel.kt, QuizResultViewModel.kt, fragments, state/intent/sideeffect ๋ชจ๋ธ ๋“ฑ), feature/quiz/src/main/res/navigation/graph_quiz.xml
ํ€ด์ฆˆ ๋กœ๋“œ/์ œ์ถœ ํ๋ฆ„ ๊ตฌํ˜„, ์ธํ…ํŠธ/์‚ฌ์ด๋“œ์ดํŽ™ํŠธ ์•„ํ‚คํ…์ฒ˜ ์ ์šฉ, ๊ฒฐ๊ณผ ๋ชจ๋ธ ์ „๋‹ฌ์šฉ ๋„ค๋น„๊ฒŒ์ด์…˜ ์ธ์ž(quizResultModel) ์ถ”๊ฐ€, UI ์ปดํฌ์ €๋ธ” ์‹œ๊ทธ๋‹ˆ์ฒ˜ ๋ฐ ์ƒํƒœ ๊ธฐ๋ฐ˜ ๋ฆฌํŒฉํ„ฐ๋ง ๋“ฑ.
๋””์ž์ธ ์‹œ์Šคํ…œ
core/designsystem/src/main/java/.../WableGlideImage.kt, .../WableCustomCardWithStroke.kt, feature/onboarding/.../LckTeamItem.kt, feature/quiz/.../QuizButton.kt
Glide ์‹คํŒจ ์‹œ ํšŒ์ƒ‰ ๋ฐ•์Šค ํ”Œ๋ ˆ์ด์Šคํ™€๋” ์ถ”๊ฐ€. WableCustomCardWithStroke ํŒŒ๋ผ๋ฏธํ„ฐ๋ช… connerRadiusโ†’cornerRadius, backGroundColorโ†’backgroundColor๋กœ ๋ณ€๊ฒฝ ๋ฐ ํ˜ธ์ถœ๋ถ€ ๋Œ€์‘ ์ˆ˜์ •.

Sequence Diagram(s)

sequenceDiagram
    participant App as WableApp
    participant Scheduler as DailyTaskSchedulerImpl
    participant WM as WorkManager
    participant Worker as ResetQuizStatusWorker
    participant Repo as UserInfoRepository

    App->>Scheduler: initWorkManager() / scheduleDailyReset()
    Scheduler->>Scheduler: compute delay until midnight
    Scheduler->>WM: enqueueUniquePeriodicWork(ResetQuizStatusWorker, KEEP, 24h)
    Note over WM: ์ž์ • ๋„๋‹ฌ ์‹œ ์›Œ์ปค ์‹คํ–‰
    WM->>Worker: doWork()
    Worker->>Repo: saveQuizCompleted(false)
    Repo-->>Worker: ์™„๋ฃŒ
    Worker-->>WM: Result.success()
Loading
sequenceDiagram
    participant User as ์‚ฌ์šฉ์ž
    participant Start as QuizStartFragment
    participant VM as QuizStartViewModel
    participant Repo as QuizRepository
    participant API as QuizService
    participant Result as QuizResultFragment

    User->>Start: ํ™”๋ฉด ์ง„์ž…
    Start->>VM: LoadInitialData
    VM->>Repo: getQuiz()
    Repo->>API: GET /api/v1/quiz
    API-->>Repo: ResponseQuizDto
    Repo-->>VM: QuizModel
    VM-->>Start: ์ƒํƒœ ์—…๋ฐ์ดํŠธ (quizModel)
    User->>Start: O/X ์„ ํƒ ๋ฐ ์ œ์ถœ
    Start->>VM: ClickSubmitBtn
    VM->>Repo: patchQuizScore(QuizScoreModel)
    Repo->>API: PATCH /api/v1/quiz/grade
    API-->>Repo: ResponseQuizScoreDto
    Repo-->>VM: QuizResultModel
    VM-->>Start: NavigateToResult(model)
    Start->>Result: ๋„ค๋น„๊ฒŒ์ด์…˜ (quizResultModel ์ „๋‹ฌ)
Loading

Estimated code review effort

๐ŸŽฏ 3 (Moderate) | โฑ๏ธ ~25 minutes

์ถ”๊ฐ€ ๊ฒ€ํ†  ํฌ์ธํŠธ:

  • WorkManager ์ดˆ๊ธฐ ๋”œ๋ ˆ์ด(์ž์ • ๊ณ„์‚ฐ) ๋ฐ ์ค‘๋ณต ๋“ฑ๋ก(KEEP) ๋™์ž‘ ํ™•์ธ: DailyTaskSchedulerImpl.kt
  • Hilt + WorkManager ์ฃผ์ž… ์„ค์ •๊ณผ HiltWorkerFactory ์ดˆ๊ธฐํ™”: WableApp.kt ๋ฐ ResetQuizStatusWorker.kt
  • ํ€ด์ฆˆ ์ œ์ถœ/์‘๋‹ต ํ๋ฆ„์˜ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ์™€ ์‹œ๊ฐ„ ๊ณ„์‚ฐ(๊ฒฝ๊ณผ ์‹œ๊ฐ„): QuizStartViewModel.kt, DefaultQuizRepository.kt
  • Safe Args๋กœ ์ „๋‹ฌ๋˜๋Š” QuizResultModel ์ง๋ ฌํ™”/ํŒจ์Šค ํ™•์ธ: graph_quiz.xml, fragment ์ธ์ž ์ฒ˜๋ฆฌ
  • ์ƒํƒœ ๊ธฐ๋ฐ˜ UI๋กœ์˜ ์ „ํ™˜์—์„œ nullable ์ฒ˜๋ฆฌ ๋ฐ ์ดˆ๊ธฐ๊ฐ’ ์ผ๊ด€์„ฑ: ์—ฌ๋Ÿฌ composable/ViewModel ํŒŒ์ผ

Possibly related PRs

Suggested reviewers

  • sohyun127

Poem

"๋‚˜๋Š” ์ž‘์€ ํ† ๋ผ, ์ƒˆ๋ฒฝ์„ ์ง€ํ‚ค๋„ค ๐Ÿ‡
์ž์ •์— ๊นจ์–ด ๋ฆฌ์…‹์„ ๋ˆ„๋ฅด๊ณ ,
์„œ๋ฒ„์™€ ๋Œ€ํ™”ํ•ด ํ€ด์ฆˆ๋ฅผ ๋ถˆ๋Ÿฌ์™€,
๋ฐ์ดํ„ฐ๋Š” ์ €์žฅํ•˜๊ณ  ์‹œ๊ฐ„์€ ์„ผ๋‹ค โ€”
๋‚ด์ผ ๋˜ ๋‹ฌ๋ฆด ์ค€๋น„ ๋!"

Pre-merge checks and finishing touches

โŒ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage โš ๏ธ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
โœ… Passed checks (4 passed)
Check name Status Explanation
Title Check โœ… Passed The pull request title "[Feature/#192] quiz api & workmanger"๋Š” ๋ณ€๊ฒฝ ์‚ฌํ•ญ์˜ ์ฃผ์š” ๋‘ ๊ฐ€์ง€ ๊ธฐ๋Šฅ์ธ Quiz API ํ†ตํ•ฉ๊ณผ WorkManager๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ์ „๋‹ฌํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ œ๋ชฉ์€ ๊ฐ„๊ฒฐํ•˜๊ณ  ๋ช…ํ™•ํ•˜๋ฉฐ ๋ณ€๊ฒฝ ์‚ฌํ•ญ๊ณผ ์ถฉ๋ถ„ํžˆ ๊ด€๋ จ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋งŒ "workmanger"๋Š” "workmanager"์˜ ์˜คํƒ€์ด์ง€๋งŒ, ์˜๋„๋Š” ๋ช…ํ™•ํ•˜๊ฒŒ ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค.
Linked Issues Check โœ… Passed Issue #192์˜ ๋ชจ๋“  ํ•ต์‹ฌ ์š”๊ตฌ์‚ฌํ•ญ์ด ์ถฉ์กฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์„œ๋ฒ„ ํ†ต์‹  ์š”๊ตฌ์‚ฌํ•ญ์€ QuizService, DefaultQuizRepository, ๊ทธ๋ฆฌ๊ณ  ๊ด€๋ จ DTO/Model ํด๋ž˜์Šค๋“ค์„ ํ†ตํ•ด ๊ตฌํ˜„๋˜์—ˆ์Šต๋‹ˆ๋‹ค [#192]. WorkManager ์š”๊ตฌ์‚ฌํ•ญ์€ ResetQuizStatusWorker, DailyTaskSchedulerImpl, DailyTaskScheduler ์ธํ„ฐํŽ˜์ด์Šค ๋ฐ WableApp์˜ ์ดˆ๊ธฐํ™”๋ฅผ ํ†ตํ•ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค [#192]. DataStore ์ €์žฅ ์š”๊ตฌ์‚ฌํ•ญ์€ IsQuizCompleted ์„ ํ˜ธ๋„ ํ‚ค, WablePreferencesDataSource ์ธํ„ฐํŽ˜์ด์Šค ์—…๋ฐ์ดํŠธ ๋ฐ DefaultWablePreferenceDatasource ๊ตฌํ˜„์„ ํ†ตํ•ด ๋‹ฌ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค [#192]. ์ถ”๊ฐ€๋กœ GetRemainingTimeUseCase์™€ QuizMainViewModel์„ ํ†ตํ•ด ํƒ€์ด๋จธ ๊ธฐ๋Šฅ๋„ ๊ตฌํ˜„๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
Out of Scope Changes Check โœ… Passed ๋Œ€๋ถ€๋ถ„์˜ ๋ณ€๊ฒฝ์‚ฌํ•ญ์€ Issue #192์˜ ๋ฒ”์œ„ ๋‚ด์— ์žˆ์ง€๋งŒ, WableCustomCardWithStroke์™€ WableGlideImage์˜ ์„ค๊ณ„ ์‹œ์Šคํ…œ ๋ณ€๊ฒฝ์€ ์•ฝ๊ฐ„์˜ ๊ณ ๋ ค๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๋ณ€๊ฒฝ์‚ฌํ•ญ๋“ค์€ QuizButton ๋“ฑ Quiz ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด ํ•„์š”ํ•œ ์ง€์› ์ธํ”„๋ผ ๋ณ€๊ฒฝ์ด๋ฉฐ ์ง์ ‘์ ์œผ๋กœ Quiz ๊ธฐ๋Šฅ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. LckTeamItem (Onboarding ๊ธฐ๋Šฅ)์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋ช… ์ˆ˜์ •์€ ์„ค๊ณ„ ์‹œ์Šคํ…œ ๋ณ€๊ฒฝ์— ๋Œ€์‘ํ•˜๋Š” ๊ฒƒ์œผ๋กœ, ๋‹ค๋ฅธ ๋ชจ๋“ˆ์— ์žˆ์ง€๋งŒ ํ•„์š”ํ•œ ์—…๋ฐ์ดํŠธ์ž…๋‹ˆ๋‹ค.
Description Check โœ… Passed PR ์„ค๋ช…์ด ์ €์žฅ์†Œ์˜ ํ…œํ”Œ๋ฆฟ์„ ์ž˜ ๋”ฐ๋ฅด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. โœ… Check-List, ๐Ÿ“Œ Issues (closed #192), ๐Ÿ“Ž Work Description (WorkManager ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์Šค์ผ€์ค„๋ง, Quiz API ์—ฐ๊ฒฐ, ํƒ€์ด๋จธ ๊ตฌํ˜„)์ด ๋ชจ๋‘ ํฌํ•จ๋˜์–ด ์žˆ์œผ๋ฉฐ, ๐Ÿ“ท Screenshots๋„ ์ œ๊ณต๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ’ฌ To Reviewers ์„น์…˜๋งŒ ๋น„์–ด์žˆ์ง€๋งŒ ์ด๋Š” ์„ ํƒ์‚ฌํ•ญ์ž…๋‹ˆ๋‹ค.
โœจ 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/#192-quiz-api

๐Ÿ“œ Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

๐Ÿ“ฅ Commits

Reviewing files that changed from the base of the PR and between 4ffcd3c and d932860.

๐Ÿ“’ Files selected for processing (3)
  • core/designsystem/src/main/java/com/teamwable/designsystem/component/card/WableCustomCardWithStroke.kt (1 hunks)
  • feature/onboarding/src/main/java/com/teamwable/onboarding/selectlckteam/component/LckTeamItem.kt (1 hunks)
  • feature/quiz/src/main/java/com/teamwable/quiz/component/QuizButton.kt (1 hunks)
โœ… Files skipped from review due to trivial changes (1)
  • feature/onboarding/src/main/java/com/teamwable/onboarding/selectlckteam/component/LckTeamItem.kt
๐Ÿ”‡ Additional comments (2)
feature/quiz/src/main/java/com/teamwable/quiz/component/QuizButton.kt (1)

36-38: LGTM! API ๋ณ€๊ฒฝ์‚ฌํ•ญ์— ๋งž์ถฐ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์—…๋ฐ์ดํŠธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

WableCustomCardWithStroke์˜ ์ƒˆ๋กœ์šด ํŒŒ๋ผ๋ฏธํ„ฐ๋ช…(cornerRadius, backgroundColor)์„ ์ •ํ™•ํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

core/designsystem/src/main/java/com/teamwable/designsystem/component/card/WableCustomCardWithStroke.kt (1)

18-31: QuizButton.kt์—์„œ ๋งค๊ฐœ๋ณ€์ˆ˜๋ช… ์—…๋ฐ์ดํŠธ๊ฐ€ ๋ˆ„๋ฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๊ฒ€์ฆ ๊ฒฐ๊ณผ feature/quiz/src/main/java/com/teamwable/quiz/component/QuizButton.kt์—์„œ ๋‹ค์Œ ํ•ญ๋ชฉ์„ ํ™•์ธํ–ˆ์Šต๋‹ˆ๋‹ค:

  • Line 64: val backGroundColor: Color โ€” ์—ฌ์ „ํžˆ ์˜ค๋ž˜๋œ ๋ช…๋ช…๊ทœ์น™ ์‚ฌ์šฉ
  • Lines 37-38: type.backGroundColor ์ฐธ์กฐ โ€” ์ƒˆ๋กœ์šด backgroundColor ๋งค๊ฐœ๋ณ€์ˆ˜ ์ „๋‹ฌ ์‹œ ์—ฌ์ „ํžˆ ์˜ค๋ž˜๋œ ๋‚ด๋ถ€ ์†์„ฑ๋ช… ์‚ฌ์šฉ

WableCustomCardWithStroke ๋งค๊ฐœ๋ณ€์ˆ˜๋ช… ๋ณ€๊ฒฝ์ด ์™„์ „ํ•˜๋ ค๋ฉด ์ด ํŒŒ์ผ๋„ ์ผ๊ด€๋˜๊ฒŒ ์—…๋ฐ์ดํŠธ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด ํŒŒ์ผ์ด ํ˜„์žฌ PR์— ํฌํ•จ๋˜๋Š”์ง€, ๊ทธ๋ฆฌ๊ณ  ๋ฆฌํŒฉํ† ๋ง ๋ฒ”์œ„๊ฐ€ ๋ชจ๋“  ํ˜ธ์ถœ์ฒ˜๊นŒ์ง€ ํฌํ•จํ•˜๋Š”์ง€ ํ™•์ธํ•ด์ฃผ์„ธ์š”.


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.

@chanubc chanubc added โญ [FEAT] ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ ๊ตฌํ˜„ ๐Ÿป ์ฐฌ์šฐ ์ฐจ์€์šฐ โ˜• [COMPOSE] ์ปดํฌ์ฆˆ ui labels Oct 25, 2025
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: 5

๐Ÿงน Nitpick comments (16)
core/model/src/main/java/com/teamwable/model/quiz/QuizModel.kt (1)

3-8: ๊ธฐ๋ณธ๊ฐ’ ์‚ฌ์šฉ์— ๋Œ€ํ•œ ๊ณ ๋ ค์‚ฌํ•ญ

๋ชจ๋“  ์†์„ฑ์— ๊ธฐ๋ณธ๊ฐ’์ด ์„ค์ •๋˜์–ด ์žˆ์–ด ๋ฐ์ดํ„ฐ ๋ˆ„๋ฝ ์‹œ์—๋„ ๊ฐ์ฒด ์ƒ์„ฑ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ํŠนํžˆ quizId = -1๊ณผ ๊ฐ™์€ ์„ผํ‹ฐ๋„ ๊ฐ’์€ ์œ ํšจํ•œ ๋ฐ์ดํ„ฐ์ธ์ง€ ๊ตฌ๋ณ„ํ•˜๊ธฐ ์–ด๋ ค์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•„์ˆ˜ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ˆ„๋ฝ๋œ ๊ฒฝ์šฐ ๋น ๋ฅด๊ฒŒ ์‹คํŒจํ•˜๋„๋ก nullable ํƒ€์ž…์„ ๊ณ ๋ คํ•ด๋ณด๋Š” ๊ฒƒ๋„ ์ข‹์Šต๋‹ˆ๋‹ค.

core/data/src/main/java/com/teamwable/data/worker/ResetQuizStatusWorker.kt (1)

18-26: ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋ฒ”์œ„๋ฅผ ๊ตฌ์ฒดํ™”ํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด๋ณด์„ธ์š”.

ํ˜„์žฌ ๋ชจ๋“  Exception์„ catchํ•˜๊ณ  ์žˆ์–ด์„œ ์žฌ์‹œ๋„๊ฐ€ ๋ถˆ๊ฐ€๋Šฅํ•œ ์—๋Ÿฌ(์˜ˆ: IllegalStateException, SecurityException ๋“ฑ)์— ๋Œ€ํ•ด์„œ๋„ ๋ฌดํ•œ์ • ์žฌ์‹œ๋„๋ฅผ ์‹œ๋„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŠน์ • ์˜ˆ์™ธ ํƒ€์ž…์— ๋Œ€ํ•ด์„œ๋Š” Result.failure()๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์ด ๋” ์ ์ ˆํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ๊ณผ ๊ฐ™์ด ์žฌ์‹œ๋„ ๊ฐ€๋Šฅํ•œ ์˜ˆ์™ธ์™€ ๊ทธ๋ ‡์ง€ ์•Š์€ ์˜ˆ์™ธ๋ฅผ ๊ตฌ๋ถ„ํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด๋ณด์„ธ์š”:

 override suspend fun doWork(): Result {
     return try {
         userInfoRepository.saveQuizCompleted(false)
         Result.success()
-    } catch (e: Exception) {
+    } catch (e: IOException) {
+        Timber.e(e, "Network error during quiz reset")
+        Result.retry()
+    } catch (e: Exception) {
         Timber.e(e)
-        Result.retry()
+        Result.failure()
     }
 }
core/data/src/main/java/com/teamwable/data/worker/DailyTaskSchedulerImpl.kt (2)

23-28: ์ž์ • ์ •๋ ฌ ํ•„์š” ์‹œ Periodic(24h)๋งŒ์œผ๋กœ๋Š” ์„œ๋จธํƒ€์ž„(DST)์—์„œ ๋“œ๋ฆฌํ”„ํŠธ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

PeriodicWorkRequest๋Š” โ€œ์ด์ „ ์‹คํ–‰ ์‹œ์  + 24hโ€๋กœ ๋ฐ˜๋ณต๋˜๋ฏ€๋กœ DST ์ „ํ›„์— ์ž์ •์—์„œ ยฑ1h์”ฉ ์–ด๊ธ‹๋‚  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ž์ • ์ •๋ ฌ์ด ์ค‘์š”ํ•˜๋ฉด ๋‹ค์Œ ํŒจํ„ด์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค:

  • OneTimeWorkRequest๋กœ โ€œ๋‹ค์Œ ์ž์ •โ€๋งŒ ์˜ˆ์•ฝ
  • ์›Œ์ปค ์‹คํ–‰ ๋์—์„œ ๋‹ค์‹œ โ€œ๋‹ค์Œ ์ž์ •โ€ OneTimeWork๋ฅผ ์˜ˆ์•ฝ

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋งค์ผ ์ž์ •(๋กœ์ปฌ ํƒ€์ž„์กด)์— ์žฌ์ •๋ ฌ๋ฉ๋‹ˆ๋‹ค. ๊ธฐ์กด ์ฃผ๊ธฐ๋ฅผ ์œ ์ง€ํ•ด๋„ ๋˜์ง€๋งŒ โ€œ์ž์ • ์ •ํ™•๋„โ€ ์š”๊ตฌ๊ฐ€ ์žˆ์œผ๋ฉด ์ „ํ™˜์„ ๊ณ ๋ คํ•ด ์ฃผ์„ธ์š”.


19-22: now๋ฅผ ๋‘ ๋ฒˆ ํ˜ธ์ถœํ•˜์—ฌ ๊ฒฝ๊ณ„ ์‹œ์ ์— ๋ฏธ์„ธํ•œ ์˜ค์ฐจ ๊ฐ€๋Šฅ. ๋™์ผ ๊ธฐ์ค€์œผ๋กœ ๊ณ„์‚ฐํ•˜์„ธ์š”.

๋™์ผํ•œ now ๊ธฐ์ค€์„ ์‚ฌ์šฉํ•˜๋ฉด ์•ˆ์ „ํ•ฉ๋‹ˆ๋‹ค. Zone ์ฒ˜๋ฆฌ๊นŒ์ง€ ํฌํ•จํ•˜๋ฉด ๋” ๊ฒฌ๊ณ ํ•ฉ๋‹ˆ๋‹ค.

-        val now = LocalDateTime.now()
-        val nextMidnight = LocalDateTime.now().plusDays(1).with(LocalTime.MIDNIGHT)
-        val initialDelay = Duration.between(now, nextMidnight)
+        val now = java.time.ZonedDateTime.now()
+        val nextMidnight = now.toLocalDate().plusDays(1).atStartOfDay(now.zone)
+        val initialDelay = java.time.Duration.between(now, nextMidnight)
core/data/src/main/java/com/teamwable/data/repositoryimpl/DefaultUserInfoRepository.kt (1)

41-43: ํ€ด์ฆˆ ์™„๋ฃŒ ํ”Œ๋ž˜๊ทธ ์—ฐ๋™์€ ์ ์ ˆํ•ฉ๋‹ˆ๋‹ค. ๋„ค์ด๋ฐ/๊ธฐ๋ณธ๊ฐ’๋งŒ ํ™•์ธํ•ด ์ฃผ์„ธ์š”.

  • ๊ตฌํ˜„์€ DataSource์— ์ž˜ ์œ„์ž„๋˜์—ˆ์Šต๋‹ˆ๋‹ค. LGTM.
  • ๋ฉ”์„œ๋“œ ๋„ค์ด๋ฐ์€ ๊ธฐ์กด getIsPushAlarmAllowed()์™€์˜ ์ผ๊ด€์„ฑ์„ ์œ„ํ•ด getIsQuizCompleted()๋กœ ๋งž์ถ”๋Š” ๊ฒƒ๋„ ๊ณ ๋ คํ•ด ๋ณด์„ธ์š”. ์„ ํƒ ์‚ฌํ•ญ์ž…๋‹ˆ๋‹ค.
  • ์‹ ๊ทœ ํ‚ค์˜ ๊ธฐ๋ณธ๊ฐ’(false ๋“ฑ)๊ณผ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜(๊ธฐ์กด ์‚ฌ์šฉ์ž)์— ๋ฌธ์ œ๊ฐ€ ์—†๋Š”์ง€ ํ•œ ๋ฒˆ๋งŒ ์ ๊ฒ€ ๋ถ€ํƒ๋“œ๋ฆฝ๋‹ˆ๋‹ค.

Also applies to: 92-94

core/model/src/main/java/com/teamwable/model/quiz/QuizScoreModel.kt (1)

3-7: ํ•„๋“œ ๋‹จ์œ„์™€ ๋ฒ”์œ„๋ฅผ ๋ช…์‹œํ•ด ๋ชจ๋ธ ๊ณ„์•ฝ์„ ๋ช…ํ™•ํžˆ ํ•ด์ฃผ์„ธ์š”.

  • quizTime: Int์˜ ๋‹จ์œ„(์ดˆ/๋ฐ€๋ฆฌ์ดˆ)์™€ ํ—ˆ์šฉ ๋ฒ”์œ„๋ฅผ ๋ฌธ์„œํ™”ํ•˜๊ฑฐ๋‚˜ @IntRange๋กœ ์ œํ•œํ•˜๋ฉด ์‚ฌ์šฉ์ฒ˜ ์˜ค๋ฅ˜๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ด๋ฆ„๋„ elapsedSeconds ๋“ฑ ๋‹จ์œ„๊ฐ€ ๋“œ๋Ÿฌ๋‚˜๊ฒŒ ํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. ์„ ํƒ ์‚ฌํ•ญ์ž…๋‹ˆ๋‹ค.
core/model/src/main/java/com/teamwable/model/quiz/QuizResultModel.kt (1)

6-12: ํ”„๋กœํผํ‹ฐ ์˜๋ฏธ๋ฅผ ๋” ๋ถ„๋ช…ํžˆ: ๋ฒ”์œ„ ์–ด๋…ธํ…Œ์ด์…˜๊ณผ ๋„ค์ด๋ฐ ๊ฐœ์„  ์ œ์•ˆ.

  • userPercent๋Š” [0..100]๊ฐ€ ์˜ˆ์ƒ๋ฉ๋‹ˆ๋‹ค. @IntRange(from = 0, to = 100)๋กœ ๋ช…์‹œํ•ด ์ฃผ์„ธ์š”.
  • continueNumber๋Š” ์˜๋ฏธ๊ฐ€ ๋ชจํ˜ธํ•ฉ๋‹ˆ๋‹ค. streakCount/consecutiveDays ๋“ฑ ๋„๋ฉ”์ธ์ด ๋“œ๋Ÿฌ๋‚˜๋Š” ์ด๋ฆ„์„ ๊ณ ๋ คํ•ด ์ฃผ์„ธ์š”.
feature/quiz/src/main/java/com/teamwable/quiz/start/model/QuizStartSideEffect.kt (1)

11-11: UI์— Throwable์„ ์ง์ ‘ ์ „๋‹ฌํ•˜์ง€ ๋งˆ์„ธ์š”. ์‚ฌ์šฉ์ž ๋ฉ”์‹œ์ง€/๋ฆฌ์†Œ์Šค๋กœ ๋ณ€ํ™˜ํ•˜์„ธ์š”.

  • ShowSnackBar(val message: Throwable)๋Š” ๋‚ด๋ถ€ ๊ตฌํ˜„/์Šคํƒ ๋ฉ”์‹œ์ง€๋ฅผ ๋…ธ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • String ํ˜น์€ @StringRes/์—๋Ÿฌ์ฝ”๋“œ๋กœ ์ „๋‹ฌํ•˜๊ณ , ViewModel์—์„œ ๋งคํ•‘ํ•˜๋Š” ํŒจํ„ด์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.
-    data class ShowSnackBar(val message: Throwable) : QuizStartSideEffect
+    data class ShowSnackBar(val message: String) : QuizStartSideEffect
+    // ๋˜๋Š”
+    // data class ShowSnackBar(@StringRes val messageRes: Int) : QuizStartSideEffect
feature/quiz/src/main/java/com/teamwable/quiz/result/model/QuizResultSideEffect.kt (1)

8-8: UI๋กœ Throwable ์ „๋‹ฌ ์ง€์–‘ + ๋ช…๋ช… ํ†ต์ผ ์ œ์•ˆ(ShowSnackbar).
UI ๊ณ„์ธต์œผ๋กœ ์˜ˆ์™ธ ๊ฐ์ฒด๋ฅผ ๋„˜๊ธฐ๋ฉด ์ง๋ ฌํ™”/๋ณต์›, ํ…Œ์ŠคํŠธ, ๋ฉ”์‹œ์ง€ ๊ฒฐ์ • ์ฑ…์ž„์ด ๋’ค์„ž์ž…๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž ๋ฉ”์‹œ์ง€(String ๋˜๋Š” UiText ๊ณ„์—ด)๋งŒ ์ „๋‹ฌํ•˜๊ณ  ์ด๋ฆ„์„ Material ํ‘œ๊ธฐ์ธ Snackbar๋กœ ํ†ต์ผ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

์ ์šฉ ์˜ˆ์‹œ:

-    data class ShowSnackBar(val message: Throwable) : QuizResultSideEffect
+    data class ShowSnackbar(val message: String) : QuizResultSideEffect

์ ์šฉ ์‹œ ํ˜ธ์ถœ๋ถ€/when ๋ถ„๊ธฐ์—์„œ ํด๋ž˜์Šค๋ช…๊ณผ ํŒŒ๋ผ๋ฏธํ„ฐ ํƒ€์ž… ์—…๋ฐ์ดํŠธ ํ•„์š”.

feature/quiz/src/main/java/com/teamwable/quiz/result/QuizResultViewModel.kt (1)

33-37: ํƒ€์ด๋จธ ํฌ๋งท ์•ˆ์ „์„ฑ ๋ณด๊ฐ•(์Œ์ˆ˜ ๋ฐฉ์ง€, Locale).
์˜ˆ๊ธฐ์น˜ ์•Š์€ ์Œ์ˆ˜ ์ž…๋ ฅ ๋ฐฉ์ง€์™€ ์ˆซ์ž ํฌ๋งท์˜ ์ง€์—ญ ์˜์กด์„ฑ ์ œ๊ฑฐ๋ฅผ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

-    private fun formatTime(seconds: Int): String {
-        val minutes = seconds / 60
-        val remainingSeconds = seconds % 60
-        return String.format(Locale.KOREA, "%d:%02d", minutes, remainingSeconds)
-    }
+    private fun formatTime(totalSeconds: Int): String {
+        val s = totalSeconds.coerceAtLeast(0)
+        val minutes = s / 60
+        val remainingSeconds = s % 60
+        return "%d:%02d".format(Locale.ROOT, minutes, remainingSeconds)
+    }
core/network/src/main/java/com/teamwable/network/dto/request/RequestQuizScoreDto.kt (1)

6-11: ํ•„๋“œ ๋‹จ์œ„ ๋ช…์‹œ ๋ฐ ์–ด๋…ธํ…Œ์ด์…˜ ๊ฐ„์†Œํ™” ์ œ์•ˆ.

  • quizTime์˜ ๋‹จ์œ„๋ฅผ KDoc์œผ๋กœ ๋ช…์‹œํ•ด ํ˜ผ๋™ ๋ฐฉ์ง€.
  • JSON ํ‚ค๊ฐ€ ํ”„๋กœํผํ‹ฐ๋ช…๊ณผ ๋™์ผํ•˜๋ฉด @SerialName์€ ์ƒ๋žต ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค(ํŒ€ ์ปจ๋ฒค์…˜์— ๋”ฐ๋ผ ์œ ์ง€ ๊ฐ€๋Šฅ).

KDoc ์ถ”๊ฐ€ ์˜ˆ์‹œ:

+/**
+ * ํ€ด์ฆˆ ์ ์ˆ˜ ์ „์†ก ์š”์ฒญ.
+ * @param quizTime ๋‹จ์œ„: ์ดˆ(second)
+ */
 @Serializable
 data class RequestQuizScoreDto(
-    @SerialName("quizId") val quizId: Long,
-    @SerialName("userAnswer") val userAnswer: Boolean,
-    @SerialName("quizTime") val quizTime: Int,
+    @SerialName("quizId") val quizId: Long,
+    @SerialName("userAnswer") val userAnswer: Boolean,
+    @SerialName("quizTime") val quizTime: Int,
 )
feature/quiz/src/main/java/com/teamwable/quiz/QuizMainFragment.kt (1)

46-51: ์ค‘๋ณต ๋‚ด๋น„๊ฒŒ์ด์…˜ ๋ฐฉ์ง€ ์˜ต์…˜ ๊ฒ€ํ† .
์—ฐ์† ํด๋ฆญ/์ค‘๋ณต ์ด๋ฒคํŠธ์— ๋Œ€๋น„ํ•ด setLaunchSingleTop(true) ์ถ”๊ฐ€๋ฅผ ๊ณ ๋ คํ•ด ์ฃผ์„ธ์š”.

 val navOptions = NavOptions.Builder()
-    .setPopUpTo(R.id.graph_quiz, true)
+    .setPopUpTo(R.id.graph_quiz, true)
+    .setLaunchSingleTop(true)
     .build()
feature/quiz/src/main/java/com/teamwable/quiz/result/model/QuizResultState.kt (1)

15-16: XP ๊ฐ’์„ ์ƒ์ˆ˜๋กœ ์ถ”์ถœํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•˜์„ธ์š”.

์„ฑ๊ณต/์‹คํŒจ์— ๋Œ€ํ•œ XP ๊ฐ’์ด ํ•˜๋“œ์ฝ”๋”ฉ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ํ–ฅํ›„ XP ๋ณด์ƒ ์ฒด๊ณ„๊ฐ€ ๋ณ€๊ฒฝ๋  ๊ฒฝ์šฐ๋ฅผ ๋Œ€๋น„ํ•˜์—ฌ companion object์˜ ์ƒ์ˆ˜๋กœ ์ถ”์ถœํ•˜๋Š” ๊ฒƒ์ด ์œ ์ง€๋ณด์ˆ˜์— ์œ ๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ฆฌํŒฉํ† ๋งํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

 @Stable
 data class QuizResultState(
     val scoreTime: String = "0:00",
     val quizResult: Boolean = false,
     val userPercent: Int = 0,
 ) : BaseState {
+    companion object {
+        private const val XP_SUCCESS = 8
+        private const val XP_FAIL = 3
+    }
+
     val resultType: QuizResultType
         get() = if (quizResult) QuizResultType.SUCCESS else QuizResultType.FAIL
     val xp: Int
-        get() = if (quizResult) 8 else 3
+        get() = if (quizResult) XP_SUCCESS else XP_FAIL
 }
feature/quiz/src/main/java/com/teamwable/quiz/QuizMainViewModel.kt (1)

19-32: ์ˆซ์ž ๋ฆฌํ„ฐ๋Ÿด ํฌ๋งท์˜ ์ผ๊ด€์„ฑ์„ ๊ฐœ์„ ํ•˜์„ธ์š”.

Line 22์—์„œ๋Š” 5000์„, Line 30์—์„œ๋Š” 5_000์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ€๋…์„ฑ์„ ์œ„ํ•ด ์ผ๊ด€๋œ ํฌ๋งท์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ†ต์ผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

     val isQuizCompleted: StateFlow<Boolean?> = userInfoRepository.getQuizCompleted()
         .stateIn(
             scope = viewModelScope,
-            started = SharingStarted.WhileSubscribed(5000),
+            started = SharingStarted.WhileSubscribed(5_000),
             initialValue = null,
         )
feature/quiz/src/main/java/com/teamwable/quiz/start/QuizStartViewModel.kt (1)

69-73: ๊ฒฝ๊ณผ ์‹œ๊ฐ„ ๊ณ„์‚ฐ์— ๋ฐฉ์–ด ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•˜์„ธ์š”.

์‹œ์Šคํ…œ ์‹œ๊ณ„๊ฐ€ ๋’ค๋กœ ์กฐ์ •๋˜๋Š” ๊ฒฝ์šฐ(๋“œ๋ฌธ ๊ฒฝ์šฐ์ด์ง€๋งŒ) ์Œ์ˆ˜ ๊ฐ’์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฐฉ์–ด ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

 private fun getElapsedTime(): Int {
     val endTimeMillis = System.currentTimeMillis()
     val elapsedSeconds = (endTimeMillis - startTimeMillis) / 1000
-    return elapsedSeconds.toInt()
+    return elapsedSeconds.coerceAtLeast(0).toInt()
 }
feature/quiz/src/main/java/com/teamwable/quiz/start/QuizStartScreen.kt (1)

96-103: ์ด๋ฏธ์ง€ ๋ ˆ์ด์•„์›ƒ ์ œ์•ฝ์„ ํ™•์ธํ•˜์„ธ์š”.

fillMaxWidth()์™€ ๊ณ ์ • ๋†’์ด 168.dp๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ContentScale.Fit์ด ์ข…ํšก๋น„๋ฅผ ์œ ์ง€ํ•˜๋ฏ€๋กœ ์ด๋ฏธ์ง€ ๋น„์œจ์— ๋”ฐ๋ผ ๋นˆ ๊ณต๊ฐ„์ด ์ƒ๊ธธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. UI ๋””์ž์ธ์—์„œ ์˜๋„ํ•œ ๋™์ž‘์ด ๋งž๋Š”์ง€ ํ™•์ธํ•ด ์ฃผ์„ธ์š”.

์ด๋ฏธ์ง€๊ฐ€ ๋‹ค์–‘ํ•œ ์ข…ํšก๋น„๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋‹ค๋ฉด, ๊ณ ์ • ๋†’์ด ๋Œ€์‹  aspectRatio modifier ์‚ฌ์šฉ์„ ๊ณ ๋ คํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์˜ˆ์‹œ:

 WableGlideImage(
     imageUrl = state.quizModel.quizImage,
     contentScale = ContentScale.Fit,
     modifier = Modifier
         .fillMaxWidth()
         .padding(horizontal = 20.dp)
-        .height(168.dp),
+        .aspectRatio(16f / 9f),  // ์›ํ•˜๋Š” ๋น„์œจ๋กœ ์กฐ์ •
 )
๐Ÿ“œ Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

๐Ÿ“ฅ Commits

Reviewing files that changed from the base of the PR and between 855580b and c56d87f.

๐Ÿ“’ Files selected for processing (44)
  • app/build.gradle.kts (1 hunks)
  • app/src/main/AndroidManifest.xml (1 hunks)
  • app/src/main/java/com/teamwable/wable/WableApp.kt (3 hunks)
  • core/common/src/main/java/com/teamwable/common/intentprovider/DailyTaskScheduler.kt (1 hunks)
  • core/data/build.gradle.kts (1 hunks)
  • core/data/src/main/java/com/teamwable/data/di/RepositoryModule.kt (3 hunks)
  • core/data/src/main/java/com/teamwable/data/di/SchedulerModule.kt (1 hunks)
  • core/data/src/main/java/com/teamwable/data/mapper/toModel/QuizMapper.kt (1 hunks)
  • core/data/src/main/java/com/teamwable/data/repository/QuizRepository.kt (1 hunks)
  • core/data/src/main/java/com/teamwable/data/repository/UserInfoRepository.kt (2 hunks)
  • core/data/src/main/java/com/teamwable/data/repositoryimpl/DefaultQuizRepository.kt (1 hunks)
  • core/data/src/main/java/com/teamwable/data/repositoryimpl/DefaultUserInfoRepository.kt (2 hunks)
  • core/data/src/main/java/com/teamwable/data/worker/DailyTaskSchedulerImpl.kt (1 hunks)
  • core/data/src/main/java/com/teamwable/data/worker/ResetQuizStatusWorker.kt (1 hunks)
  • core/datastore/src/main/java/com/teamwable/datastore/datasource/DefaultWablePreferenceDatasource.kt (3 hunks)
  • core/datastore/src/main/java/com/teamwable/datastore/datasource/WablePreferencesDataSource.kt (2 hunks)
  • core/designsystem/src/main/java/com/teamwable/designsystem/component/image/WableImage.kt (3 hunks)
  • core/domain/src/main/java/com/teamwable/domain/usecase/GetRemainingTimeUseCase.kt (1 hunks)
  • core/model/src/main/java/com/teamwable/model/quiz/QuizModel.kt (1 hunks)
  • core/model/src/main/java/com/teamwable/model/quiz/QuizResultModel.kt (1 hunks)
  • core/model/src/main/java/com/teamwable/model/quiz/QuizScoreModel.kt (1 hunks)
  • core/network/src/main/java/com/teamwable/network/datasource/QuizService.kt (1 hunks)
  • core/network/src/main/java/com/teamwable/network/di/ServiceModule.kt (2 hunks)
  • core/network/src/main/java/com/teamwable/network/dto/request/RequestQuizScoreDto.kt (1 hunks)
  • core/network/src/main/java/com/teamwable/network/dto/response/quiz/ResponseQuizDto.kt (1 hunks)
  • core/network/src/main/java/com/teamwable/network/dto/response/quiz/ResponseQuizScoreDto.kt (1 hunks)
  • feature/quiz/build.gradle.kts (1 hunks)
  • feature/quiz/src/main/java/com/teamwable/quiz/QuizMainFragment.kt (1 hunks)
  • feature/quiz/src/main/java/com/teamwable/quiz/QuizMainScreen.kt (3 hunks)
  • feature/quiz/src/main/java/com/teamwable/quiz/QuizMainViewModel.kt (1 hunks)
  • feature/quiz/src/main/java/com/teamwable/quiz/result/QuizResultFragment.kt (3 hunks)
  • feature/quiz/src/main/java/com/teamwable/quiz/result/QuizResultScreen.kt (4 hunks)
  • feature/quiz/src/main/java/com/teamwable/quiz/result/QuizResultViewModel.kt (1 hunks)
  • feature/quiz/src/main/java/com/teamwable/quiz/result/model/QuizResultIntent.kt (1 hunks)
  • feature/quiz/src/main/java/com/teamwable/quiz/result/model/QuizResultSideEffect.kt (1 hunks)
  • feature/quiz/src/main/java/com/teamwable/quiz/result/model/QuizResultState.kt (1 hunks)
  • feature/quiz/src/main/java/com/teamwable/quiz/start/QuizStartFragment.kt (2 hunks)
  • feature/quiz/src/main/java/com/teamwable/quiz/start/QuizStartScreen.kt (6 hunks)
  • feature/quiz/src/main/java/com/teamwable/quiz/start/QuizStartViewModel.kt (1 hunks)
  • feature/quiz/src/main/java/com/teamwable/quiz/start/model/QuizStartIntent.kt (1 hunks)
  • feature/quiz/src/main/java/com/teamwable/quiz/start/model/QuizStartSideEffect.kt (1 hunks)
  • feature/quiz/src/main/java/com/teamwable/quiz/start/model/QuizStartState.kt (1 hunks)
  • feature/quiz/src/main/res/navigation/graph_quiz.xml (2 hunks)
  • gradle/libs.versions.toml (2 hunks)
๐Ÿงฐ Additional context used
๐Ÿงฌ Code graph analysis (6)
feature/quiz/src/main/java/com/teamwable/quiz/start/QuizStartViewModel.kt (1)
core/common/src/main/java/com/teamwable/common/base/BaseViewModel.kt (2)
  • postSideEffect (41-47)
  • intent (37-39)
feature/quiz/src/main/java/com/teamwable/quiz/result/QuizResultViewModel.kt (1)
core/common/src/main/java/com/teamwable/common/base/BaseViewModel.kt (2)
  • postSideEffect (41-47)
  • intent (37-39)
feature/quiz/src/main/java/com/teamwable/quiz/result/QuizResultScreen.kt (2)
feature/quiz/src/main/java/com/teamwable/quiz/result/QuizResultFragment.kt (1)
  • navigateToMain (37-40)
feature/quiz/src/main/java/com/teamwable/quiz/component/QuizComponent.kt (1)
  • QuizStatBox (21-53)
feature/quiz/src/main/java/com/teamwable/quiz/QuizMainFragment.kt (2)
feature/quiz/src/main/java/com/teamwable/quiz/QuizMainScreen.kt (1)
  • QuizMainRoute (31-42)
core/ui/src/main/java/com/teamwable/ui/extensions/FragmentExt.kt (1)
  • openUri (49-54)
core/data/src/main/java/com/teamwable/data/repositoryimpl/DefaultQuizRepository.kt (1)
core/data/src/main/java/com/teamwable/data/util/runSuspendCatching.kt (1)
  • runHandledCatching (19-24)
feature/quiz/src/main/java/com/teamwable/quiz/start/QuizStartScreen.kt (2)
feature/quiz/src/main/java/com/teamwable/quiz/start/QuizStartFragment.kt (1)
  • navigateToResult (35-38)
feature/quiz/src/main/java/com/teamwable/quiz/component/QuizButton.kt (1)
  • QuizOXButton (27-50)
๐Ÿ”‡ Additional comments (36)
core/designsystem/src/main/java/com/teamwable/designsystem/component/image/WableImage.kt (1)

3-4: ์ž„ํฌํŠธ ์ถ”๊ฐ€๊ฐ€ ์ ์ ˆํ•ฉ๋‹ˆ๋‹ค.

์‹คํŒจ UI ๊ตฌํ˜„์— ํ•„์š”ํ•œ ์ž„ํฌํŠธ๋“ค์ด ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

Also applies to: 16-16

feature/quiz/build.gradle.kts (1)

13-13: ๋„๋ฉ”์ธ ๋ ˆ์ด์–ด๋กœ์˜ ์˜์กด์„ฑ ๋ณ€๊ฒฝ์ด ์ ์ ˆํ•ฉ๋‹ˆ๋‹ค.

:core:common์—์„œ :core:domain์œผ๋กœ ๋ณ€๊ฒฝํ•˜์—ฌ ๋„๋ฉ”์ธ ์ฃผ๋„ ์•„ํ‚คํ…์ฒ˜๋ฅผ ๋”ฐ๋ฅด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ƒˆ๋กœ ์ถ”๊ฐ€๋œ GetRemainingTimeUseCase ๊ฐ™์€ ๋„๋ฉ”์ธ ์œ ์ฆˆ์ผ€์ด์Šค๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ์˜ฌ๋ฐ”๋ฅธ ์ ‘๊ทผ์ž…๋‹ˆ๋‹ค.

core/datastore/src/main/java/com/teamwable/datastore/datasource/WablePreferencesDataSource.kt (1)

16-16: ํ€ด์ฆˆ ์™„๋ฃŒ ์ƒํƒœ ์ €์žฅ ๊ธฐ๋Šฅ์ด ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๊ธฐ์กด ์ธํ„ฐํŽ˜์ด์Šค ํŒจํ„ด๊ณผ ์ผ๊ด€์„ฑ ์žˆ๊ฒŒ isQuizCompleted Flow์™€ updateIsQuizCompleted ๋ฉ”์„œ๋“œ๊ฐ€ ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

Also applies to: 38-38

app/src/main/AndroidManifest.xml (1)

66-69: InitializationProvider ์ œ๊ฑฐ๊ฐ€ ์ ์ ˆํ•ฉ๋‹ˆ๋‹ค.

Hilt ๊ธฐ๋ฐ˜ WorkManager ์ดˆ๊ธฐํ™”๋กœ ์ „ํ™˜ํ•˜๋ฉด์„œ androidx.startup์˜ InitializationProvider๋ฅผ ์ œ๊ฑฐํ•œ ๊ฒƒ์ด ์˜ฌ๋ฐ”๋ฅธ ์ ‘๊ทผ์ž…๋‹ˆ๋‹ค. ์ด๋Š” ์ปค์Šคํ…€ ์ดˆ๊ธฐํ™” ๋กœ์ง๊ณผ์˜ ์ถฉ๋Œ์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.

core/data/build.gradle.kts (1)

15-15: WorkManager ํ†ตํ•ฉ์„ ์œ„ํ•œ ์˜์กด์„ฑ์ด ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

WorkManager ๋Ÿฐํƒ€์ž„, Hilt Work ํ†ตํ•ฉ, ๊ทธ๋ฆฌ๊ณ  ํ•„์š”ํ•œ ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ๋ชจ๋‘ ํฌํ•จ๋˜์–ด DI ๊ธฐ๋ฐ˜ ์Šค์ผ€์ค„๋ง ๊ตฌํ˜„์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

Also applies to: 22-27

gradle/libs.versions.toml (1)

88-90: WorkManager์™€ Hilt Work ๋ฒ„์ „์ด ์ ์ ˆํ•˜๊ฒŒ ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

WorkManager 2.11.0๊ณผ Hilt Work 1.3.0์€ ์•ˆ์ •์ ์ธ ๋ฒ„์ „์ด๋ฉฐ, ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ ์–ธ๋„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๊ตฌ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

Also applies to: 122-124

app/build.gradle.kts (1)

107-107: Hilt Work ์˜์กด์„ฑ์ด ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

์•ฑ ๋ชจ๋“ˆ์— Hilt Work ํ†ตํ•ฉ์„ ์œ„ํ•œ ์˜์กด์„ฑ์ด ์ถ”๊ฐ€๋˜์–ด HiltWorkerFactory๋ฅผ ์‚ฌ์šฉํ•œ WorkManager ์ดˆ๊ธฐํ™”๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

core/data/src/main/java/com/teamwable/data/repository/UserInfoRepository.kt (1)

26-26: LGTM!

ํ€ด์ฆˆ ์™„๋ฃŒ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๋ฉ”์„œ๋“œ๋“ค์ด ๊ธฐ์กด repository ํŒจํ„ด๊ณผ ์ผ๊ด€๋˜๊ฒŒ ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค. Flow๋ฅผ ์‚ฌ์šฉํ•œ ์ฝ๊ธฐ์™€ suspend๋ฅผ ์‚ฌ์šฉํ•œ ์“ฐ๊ธฐ ๊ตฌ์กฐ๊ฐ€ ์ ์ ˆํ•ฉ๋‹ˆ๋‹ค.

Also applies to: 52-52

feature/quiz/src/main/res/navigation/graph_quiz.xml (1)

32-34: LGTM!

Navigation argument๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์„ ์–ธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. QuizResultModel ํƒ€์ž…์ด ์ ์ ˆํ•˜๊ฒŒ ์ง€์ •๋˜์–ด ์žˆ๊ณ , Safe Args๋ฅผ ํ†ตํ•ด ํƒ€์ž… ์•ˆ์ „์„ฑ์ด ๋ณด์žฅ๋ฉ๋‹ˆ๋‹ค.

core/common/src/main/java/com/teamwable/common/intentprovider/DailyTaskScheduler.kt (1)

1-5: LGTM!

์ผ์ผ ์ž‘์—… ์Šค์ผ€์ค„๋ง์„ ์œ„ํ•œ ๊ฐ„๊ฒฐํ•œ ์ธํ„ฐํŽ˜์ด์Šค์ž…๋‹ˆ๋‹ค. ๋‹จ์ผ ์ฑ…์ž„ ์›์น™์„ ์ž˜ ๋”ฐ๋ฅด๊ณ  ์žˆ์œผ๋ฉฐ, WorkManager ๊ตฌํ˜„ ์„ธ๋ถ€์‚ฌํ•ญ์„ ์ถ”์ƒํ™”ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

feature/quiz/src/main/java/com/teamwable/quiz/result/model/QuizResultIntent.kt (1)

1-10: LGTM!

MVI ํŒจํ„ด์— ๋งž๋Š” Intent ์ •์˜์ž…๋‹ˆ๋‹ค. data object์™€ data class๋ฅผ ์ ์ ˆํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜์—ฌ ์ƒํƒœ๊ฐ€ ์—†๋Š” ์•ก์…˜๊ณผ ๋ฐ์ดํ„ฐ๋ฅผ ํฌํ•จํ•˜๋Š” ์•ก์…˜์„ ๊ตฌ๋ถ„ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

core/network/src/main/java/com/teamwable/network/di/ServiceModule.kt (1)

11-11: LGTM!

QuizService๊ฐ€ ๊ธฐ์กด ์„œ๋น„์Šค๋“ค๊ณผ ๋™์ผํ•œ ํŒจํ„ด์œผ๋กœ DI ๋ชจ๋“ˆ์— ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค. @WableRetrofit ํ•œ์ •์ž์™€ ์‹ฑ๊ธ€ํ†ค ์Šค์ฝ”ํ”„๊ฐ€ ์ ์ ˆํ•˜๊ฒŒ ์‚ฌ์šฉ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

Also applies to: 77-81

core/data/src/main/java/com/teamwable/data/mapper/toModel/QuizMapper.kt (1)

10-27: LGTM!

DTO์™€ ๋„๋ฉ”์ธ ๋ชจ๋ธ ๊ฐ„์˜ ๋งคํ•‘ ํ•จ์ˆ˜๋“ค์ด ๋ช…ํ™•ํ•˜๊ฒŒ ๊ตฌํ˜„๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋‹จ์ˆœํ•œ ์†์„ฑ ๋งคํ•‘์œผ๋กœ ์ถฉ๋ถ„ํ•˜๋ฉฐ, internal ๊ฐ€์‹œ์„ฑ์œผ๋กœ ์ ์ ˆํ•˜๊ฒŒ ์บก์Аํ™”๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

feature/quiz/src/main/java/com/teamwable/quiz/result/QuizResultFragment.kt (1)

16-16: LGTM!

Navigation Safe Args๋ฅผ ํ†ตํ•œ ์ธ์ž ์ „๋‹ฌ๊ณผ ์—๋Ÿฌ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ Snackbar ์ฝœ๋ฐฑ์ด ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๊ตฌํ˜„๋˜์—ˆ์Šต๋‹ˆ๋‹ค. Fragment์™€ Composable ๊ฐ„์˜ ํ†ต์‹  ํŒจํ„ด์ด ๋ช…ํ™•ํ•˜๊ณ  ์ผ๊ด€์„ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

Also applies to: 29-30, 42-44

app/src/main/java/com/teamwable/wable/WableApp.kt (1)

25-25: ์›๋ณธ ๋ฆฌ๋ทฐ ๋Œ“๊ธ€์˜ ์šฐ๋ ค์‚ฌํ•ญ์ด ์ฝ”๋“œ๋ฒ ์ด์Šค ์ƒํ™ฉ๊ณผ ๋งž์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

AndroidManifest.xml์—์„œ androidx.startup.InitializationProvider๊ฐ€ tools:node="remove"๋กœ ๋ช…์‹œ์ ์œผ๋กœ ์ œ๊ฑฐ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” WorkManagerInitializer๋ฅผ ํฌํ•จํ•œ ๋ชจ๋“  ์ž๋™ ์ดˆ๊ธฐํ™”๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ:

  • ๊ธฐ๋ณธ WorkManagerInitializer ContentProvider๊ฐ€ ์‹คํ–‰๋˜์ง€ ์•Š์Œ
  • WableApp.onCreate()์˜ ์ˆ˜๋™ WorkManager.initialize() ํ˜ธ์ถœ์€ ์•ˆ์ „ํ•จ
  • "WorkManager is already initialized" ์ถฉ๋Œ ์œ„ํ—˜์ด ์—†์Œ
  • ํ˜„์žฌ ๊ตฌํ˜„์€ ์ •์ƒ ์ž‘๋™ํ•จ

Configuration.Provider ํŒจํ„ด์€ ์œ ํšจํ•œ ๋Œ€์•ˆ์ด์ง€๋งŒ, ์—ฌ๊ธฐ์„œ๋Š” ํ•„์ˆ˜ ๋ฆฌํŒฉํ† ๋ง์ด ์•„๋‹™๋‹ˆ๋‹ค.

Likely an incorrect or invalid review comment.

core/data/src/main/java/com/teamwable/data/worker/DailyTaskSchedulerImpl.kt (1)

23-34: Companion.getInstance() ์Šคํƒ€์ผ๋งŒ ๊ฐœ์„ ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ๋‚˜๋จธ์ง€ ์‚ฌํ•ญ์€ ํ™•์ธ ๋ถˆํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

์ฝ”๋“œ๋ฅผ ๊ฒ€ํ† ํ•œ ๊ฒฐ๊ณผ:

  1. Duration ํ˜ธํ™˜์„ฑ: minSdk๊ฐ€ 28์ด๊ณ , setInitialDelay(Duration)์€ API 26+ ์ง€์›ํ•˜๋ฏ€๋กœ ์™„์ „ํžˆ ํ˜ธํ™˜๋ฉ๋‹ˆ๋‹ค. ๋ฒ„์ „ ๊ฒ€ํ† ๋‚˜ TimeUnit ๋ณ€ํ™˜์ด ๋ถˆํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

  2. ๋„คํŠธ์›Œํฌ ์ œ์•ฝ์€ ๋ถˆํ•„์š”ํ•ฉ๋‹ˆ๋‹ค: ResetQuizStatusWorker๋Š” ๋กœ์ปฌ DB๋งŒ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค (userInfoRepository.saveQuizCompleted(false)). ์„œ๋ฒ„ ํ†ต์‹ ์ด ์—†์œผ๋ฏ€๋กœ RequiredNetworkType.CONNECTED ์ œ์•ฝ์„ ์ถ”๊ฐ€ํ•  ์ด์œ ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

  3. ๊ฐœ์„  ๊ฐ€๋Šฅํ•œ ๋ถ€๋ถ„: ๋‹ค๋งŒ ์Šคํƒ€์ผ์ƒ WorkManager.getInstance(context)๋กœ ๋‹จ์ˆœํ™”ํ•˜๋ฉด ๋” ๋ช…ํ™•ํ•ฉ๋‹ˆ๋‹ค.

-        WorkManager.Companion.getInstance(context).enqueueUniquePeriodicWork(
+        WorkManager.getInstance(context).enqueueUniquePeriodicWork(

Likely an incorrect or invalid review comment.

feature/quiz/src/main/java/com/teamwable/quiz/result/model/QuizResultSideEffect.kt (1)

6-6: ๋„ค๋น„๊ฒŒ์ด์…˜ ์‚ฌ์ด๋“œ์ดํŽ™ํŠธ ์ถ”๊ฐ€, ์ข‹์Šต๋‹ˆ๋‹ค.
๋‹จ์ˆœยท๋ช…ํ™•ํ•œ ์ด๋ฒคํŠธ๋กœ ์ ์ ˆํ•ฉ๋‹ˆ๋‹ค.

feature/quiz/src/main/java/com/teamwable/quiz/result/QuizResultViewModel.kt (1)

16-21: ์ธํ…ํŠธ ์ฒ˜๋ฆฌ์™€ ์‚ฌ์ด๋“œ์ดํŽ™ํŠธ ํฌ์ŠคํŠธ ๊ตฌ์กฐ, ์ ์ ˆํ•ฉ๋‹ˆ๋‹ค.
BaseViewModel ํŒจํ„ด๊ณผ ์ผ๊ด€๋ฉ๋‹ˆ๋‹ค.

core/datastore/src/main/java/com/teamwable/datastore/datasource/DefaultWablePreferenceDatasource.kt (1)

32-33: ํ€ด์ฆˆ ์™„๋ฃŒ ์ƒํƒœ ํ”Œ๋ž˜๊ทธ ์ถ”๊ฐ€, ํŒจํ„ด ์ผ๊ด€ยท์•ˆ์ „(default=false).
Datastore ์ ‘๊ทผ/์—๋Ÿฌ ์ฒ˜๋ฆฌ ์ปจ๋ฒค์…˜๊ณผ ์ผ์น˜ํ•ฉ๋‹ˆ๋‹ค.

๋กœ๊ทธ์•„์›ƒ/๊ณ„์ • ์ „ํ™˜ ์‹œ ํ”Œ๋ž˜๊ทธ ์œ ์ง€ ์—ฌ๋ถ€ ์ •์ฑ… ํ™•์ธ ๋ถ€ํƒ๋“œ๋ฆฝ๋‹ˆ๋‹ค. ํ•„์š” ์‹œ clear()/clearForRefreshToken ๋ฒ”์œ„์— ํฌํ•จ ์—ฌ๋ถ€๋ฅผ ๊ฒฐ์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Also applies to: 88-93, 160-164

core/data/src/main/java/com/teamwable/data/di/SchedulerModule.kt (1)

11-19: LGTM! ์ž˜ ๊ตฌํ˜„๋œ DI ๋ชจ๋“ˆ์ž…๋‹ˆ๋‹ค.

DailyTaskScheduler๋ฅผ ์œ„ํ•œ Hilt ๋ชจ๋“ˆ์ด ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๊ตฌํ˜„๋˜์—ˆ์Šต๋‹ˆ๋‹ค. Singleton ์Šค์ฝ”ํ”„์™€ @BINDS๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํšจ์œจ์ ์œผ๋กœ ๋ฐ”์ธ๋”ฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

feature/quiz/src/main/java/com/teamwable/quiz/start/model/QuizStartIntent.kt (1)

6-14: LGTM! MVI ํŒจํ„ด์„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋”ฐ๋ฅด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

Sealed interface๋ฅผ ์‚ฌ์šฉํ•œ Intent ์ •์˜๊ฐ€ ๋ช…ํ™•ํ•˜๊ณ , data object๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ์ ์œผ๋กœ ๊ตฌํ˜„๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

core/data/src/main/java/com/teamwable/data/repository/QuizRepository.kt (1)

7-11: LGTM! ๊น”๋”ํ•œ Repository ์ธํ„ฐํŽ˜์ด์Šค์ž…๋‹ˆ๋‹ค.

Result ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜์—ฌ ์—๋Ÿฌ ํ•ธ๋“ค๋ง์„ ๋ช…์‹œ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, suspend ํ•จ์ˆ˜๋กœ ๋น„๋™๊ธฐ ์ž‘์—…์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

core/data/src/main/java/com/teamwable/data/di/RepositoryModule.kt (1)

106-110: LGTM! DI ๋ฐ”์ธ๋”ฉ์ด ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

QuizRepository ๋ฐ”์ธ๋”ฉ์ด ๋ชจ๋“ˆ์˜ ๊ธฐ์กด ํŒจํ„ด์„ ์ผ๊ด€๋˜๊ฒŒ ๋”ฐ๋ฅด๊ณ  ์žˆ์œผ๋ฉฐ, Singleton ์Šค์ฝ”ํ”„๊ฐ€ ์ ์ ˆํ•˜๊ฒŒ ์ ์šฉ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

feature/quiz/src/main/java/com/teamwable/quiz/QuizMainScreen.kt (3)

32-42: LGTM! ์ž˜ ๊ตฌ์กฐํ™”๋œ Composable ์•„ํ‚คํ…์ฒ˜์ž…๋‹ˆ๋‹ค.

Route์™€ Screen์„ ๋ถ„๋ฆฌํ•˜์—ฌ ViewModel ์˜์กด์„ฑ์„ ๊ฒฉ๋ฆฌํ•˜๊ณ , collectAsStateWithLifecycle์„ ์‚ฌ์šฉํ•˜์—ฌ ์ƒ๋ช…์ฃผ๊ธฐ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ๊ด€๋ฆฌํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. Hilt ํ†ตํ•ฉ๋„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๊ตฌํ˜„๋˜์—ˆ์Šต๋‹ˆ๋‹ค.


45-48: ํŒŒ๋ผ๋ฏธํ„ฐ ๊ธฐ๋ณธ๊ฐ’ ์ œ๊ณต์œผ๋กœ ํ”„๋ฆฌ๋ทฐ ์ง€์›์ด ์šฐ์ˆ˜ํ•ฉ๋‹ˆ๋‹ค.

๊ธฐ๋ณธ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ œ๊ณตํ•˜์—ฌ ํ”„๋ฆฌ๋ทฐ์™€ ํ…Œ์ŠคํŠธ๋ฅผ ์šฉ์ดํ•˜๊ฒŒ ํ•˜๋ฉด์„œ๋„, ์‹ค์ œ ์‚ฌ์šฉ ์‹œ ๋™์  ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ์ž…ํ•  ์ˆ˜ ์žˆ๋„๋ก ์œ ์—ฐํ•˜๊ฒŒ ์„ค๊ณ„๋˜์—ˆ์Šต๋‹ˆ๋‹ค.


82-82: ๋™์  ํƒ€์ด๋จธ ํ‘œ์‹œ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๊ตฌํ˜„๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

ํ•˜๋“œ์ฝ”๋”ฉ๋œ ๊ฐ’์—์„œ ViewModel์˜ ์ƒํƒœ๋ฅผ ๋ฐ˜์˜ํ•˜๋„๋ก ๋ณ€๊ฒฝ๋˜์–ด ์‹ค์‹œ๊ฐ„ ํƒ€์ด๋จธ ๊ธฐ๋Šฅ์ด ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

core/network/src/main/java/com/teamwable/network/dto/response/quiz/ResponseQuizScoreDto.kt (1)

6-14: LGTM! DTO๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ •์˜๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

Kotlinx Serialization์„ ์‚ฌ์šฉํ•œ DTO ์ •์˜๊ฐ€ ์ ์ ˆํ•˜๋ฉฐ, ๊ธฐ๋ณธ๊ฐ’ ์ œ๊ณต์œผ๋กœ ์—ญ์ง๋ ฌํ™” ์•ˆ์ •์„ฑ์ด ํ™•๋ณด๋˜์—ˆ์Šต๋‹ˆ๋‹ค. SerialName ์–ด๋…ธํ…Œ์ด์…˜์„ ํ†ตํ•ด API ๊ณ„์•ฝ๊ณผ์˜ ๋งคํ•‘๋„ ๋ช…ํ™•ํ•ฉ๋‹ˆ๋‹ค.

core/network/src/main/java/com/teamwable/network/datasource/QuizService.kt (1)

11-19: OkHttp ํƒ€์ž„์•„์›ƒ ์„ค์ •์ด ์ ์ ˆํ•˜๊ฒŒ ๊ตฌ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

core/network/src/main/java/com/teamwable/network/di/NetworkModule.kt์—์„œ OkHttpClient ๋นŒ๋”์— connectTimeout๊ณผ readTimeout์ด ๊ฐ๊ฐ 5์ดˆ๋กœ ์„ค์ •๋˜์–ด ์žˆ์œผ๋ฉฐ, ํ† ํฐ ์ธํ„ฐ์…‰ํ„ฐ์™€ ๋กœ๊น… ์ธํ„ฐ์…‰ํ„ฐ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ํ†ตํ•ฉ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค๋งŒ ๋ช…์‹œ์ ์ธ ์žฌ์‹œ๋„ ๋กœ์ง(retry policy) ๊ตฌ์„ฑ์ด ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ์—์„œ ํ™•์ธ๋˜์ง€ ์•Š์•˜์œผ๋ฏ€๋กœ, ์žฌ์‹œ๋„ ๋ฉ”์ปค๋‹ˆ์ฆ˜์ด ์ธํ„ฐ์…‰ํ„ฐ ๋‚ด๋ถ€์— ๊ตฌํ˜„๋˜์–ด ์žˆ๊ฑฐ๋‚˜ ๋ณ„๋„๋กœ ์„ค์ •๋˜์–ด ์žˆ๋Š”์ง€ ์ˆ˜๋™์œผ๋กœ ๊ฒ€์ฆํ•ด ์ฃผ์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.

feature/quiz/src/main/java/com/teamwable/quiz/start/model/QuizStartState.kt (1)

13-16: Now let me get the actual context from the codebase around those usage points:

๊ณ„์‚ฐ๋œ ์†์„ฑ์˜ ๋กœ์ง ๊ฒ€์ฆ ํ•„์š”

userAnswer ์†์„ฑ์ด oxType == OXType.O๋กœ ๊ตฌํ˜„๋˜์–ด ์žˆ์–ด, oxType์ด null์ผ ๋•Œ์™€ OXType.X์ผ ๋•Œ ๋ชจ๋‘ false๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ "์•„์ง ์„ ํƒํ•˜์ง€ ์•Š์Œ"๊ณผ "X๋ฅผ ์„ ํƒํ•จ"์„ ๊ตฌ๋ถ„ํ•  ์ˆ˜ ์—†๋‹ค๋Š” ์ ์ด ํ™•์ธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

์ฝ”๋“œ๋ฒ ์ด์Šค ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ์—์„œ userAnswer๋Š” ๋‹ค์Œ 5๊ฐœ ์œ„์น˜์—์„œ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค:

  • RequestQuizScoreDto.kt:9 (DTO)
  • QuizScoreModel.kt:5 (Model)
  • QuizStartState.kt:15 (ํ˜„์žฌ ๊ฒ€ํ†  ๋Œ€์ƒ)
  • QuizMapper.kt:25 (๋งคํ•‘)
  • QuizStartViewModel.kt:53 (ViewModel)

ํ•˜์ง€๋งŒ ์ •ํ™•ํ•œ ์ปจํ…์ŠคํŠธ ์—†์ด๋Š” userAnswer๊ฐ€ ์‹ค์ œ๋กœ enabled == true ์กฐ๊ฑด์—์„œ๋งŒ ์ ‘๊ทผ๋˜๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ ์‚ฌํ•ญ๋“ค์„ ์ˆ˜๋™์œผ๋กœ ๊ฒ€์ฆํ•˜์„ธ์š”:

  • QuizStartViewModel.kt์˜ line 53 ์ฃผ๋ณ€ ์ฝ”๋“œ์—์„œ currentState.userAnswer ์ ‘๊ทผ ์‹œ enabled ๊ฐ€๋“œ ํ™•์ธ
  • QuizMapper.kt์˜ line 25 ์ฃผ๋ณ€์—์„œ userAnswer ๋งคํ•‘ ๋กœ์ง ๊ฒ€์ฆ
  • ํ•„์š”ํ•œ ๊ฒฝ์šฐ userAnswer๋ฅผ nullable Boolean์œผ๋กœ ๋ณ€๊ฒฝํ•˜๊ฑฐ๋‚˜ ์‚ฌ์šฉ ์‹œ์ ์—์„œ enabled == true ์ฒดํฌ ์ถ”๊ฐ€ ๊ฒ€ํ† 
core/data/src/main/java/com/teamwable/data/repositoryimpl/DefaultQuizRepository.kt (1)

14-26: LGTM!

๋ฆฌํฌ์ง€ํ† ๋ฆฌ ๊ตฌํ˜„์ด ๊น”๋”ํ•˜๊ณ  ์ผ๊ด€๋œ ์—๋Ÿฌ ์ฒ˜๋ฆฌ ํŒจํ„ด์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. mapper๋ฅผ ํ†ตํ•œ DTO-Model ๋ณ€ํ™˜๋„ ์ ์ ˆํ•˜๊ฒŒ ๋ถ„๋ฆฌ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

feature/quiz/src/main/java/com/teamwable/quiz/start/QuizStartFragment.kt (1)

28-42: LGTM!

์—๋Ÿฌ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ Snackbar ํ†ตํ•ฉ๊ณผ QuizResultModel์„ ์‚ฌ์šฉํ•œ ๋„ค๋น„๊ฒŒ์ด์…˜ ์—…๋ฐ์ดํŠธ๊ฐ€ ์ ์ ˆํ•˜๊ฒŒ ๊ตฌํ˜„๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

feature/quiz/src/main/java/com/teamwable/quiz/QuizMainViewModel.kt (1)

26-27: ์‹œ๊ฐ„ ํฌ๋งท์ด ์˜๋„ํ•œ ๋Œ€๋กœ์ธ์ง€ ํ™•์ธํ•˜์„ธ์š”.

ํ˜„์žฌ ํฌ๋งท ๋ฌธ์ž์—ด "%d:%02d"๋Š” ์‹œ๊ฐ„์ด ํ•œ ์ž๋ฆฟ์ˆ˜์ผ ๋•Œ ์•ž์— 0์„ ๋ถ™์ด์ง€ ์•Š์Šต๋‹ˆ๋‹ค (์˜ˆ: "9:59"). ์ผ๋ฐ˜์ ์ธ ์‹œ๊ณ„ ํ‘œ์‹œ๋Š” "09:59"์™€ ๊ฐ™์ด ๋‘ ์ž๋ฆฟ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. UI ๋””์ž์ธ์—์„œ ์˜๋„ํ•œ ํฌ๋งท์ด ๋งž๋Š”์ง€ ํ™•์ธํ•ด ์ฃผ์„ธ์š”.

๋‘ ์ž๋ฆฟ์ˆ˜ ์‹œ๊ฐ„ ํฌ๋งท์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ:

-    val remainingTime: StateFlow<String> = getRemainingTimeUseCase.invoke()
-        .map { (hours, minutes) -> "%d:%02d".format(hours, minutes) }
+    val remainingTime: StateFlow<String> = getRemainingTimeUseCase.invoke()
+        .map { (hours, minutes) -> "%02d:%02d".format(hours, minutes) }
         .stateIn(
core/network/src/main/java/com/teamwable/network/dto/response/quiz/ResponseQuizDto.kt (1)

6-16: LGTM!

DTO ๊ตฌํ˜„์ด ํ‘œ์ค€ ํŒจํ„ด์„ ๋”ฐ๋ฅด๊ณ  ์žˆ์œผ๋ฉฐ, ๋ชจ๋“  ํ•„๋“œ์— ์ ์ ˆํ•œ ๊ธฐ๋ณธ๊ฐ’๊ณผ ์ง๋ ฌํ™” ์–ด๋…ธํ…Œ์ด์…˜์ด ์„ค์ •๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

feature/quiz/src/main/java/com/teamwable/quiz/result/QuizResultScreen.kt (1)

39-67: LGTM!

ViewModel ํ†ตํ•ฉ์ด ์ ์ ˆํ•˜๊ฒŒ ๊ตฌํ˜„๋˜์—ˆ์œผ๋ฉฐ, lifecycle-awareํ•œ ์ƒํƒœ ์ˆ˜์ง‘๊ณผ side effect ์ฒ˜๋ฆฌ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

feature/quiz/src/main/java/com/teamwable/quiz/start/QuizStartScreen.kt (1)

49-58: LGTM!

Side effect ์ฒ˜๋ฆฌ๊ฐ€ lifecycle-awareํ•˜๊ฒŒ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๊ตฌํ˜„๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

feature/quiz/src/main/java/com/teamwable/quiz/start/QuizStartViewModel.kt (1)

50-55: ๋ฆฌ๋ทฐ ์ฝ”๋ฉ˜ํŠธ ๋ถ€์ •ํ™•์„ฑ ํ™•์ธ - ์ฝ”๋“œ๋Š” ์˜ฌ๋ฐ”๋ฆ…๋‹ˆ๋‹ค

๊ฒ€์ฆ ๊ฒฐ๊ณผ, ๋ฆฌ๋ทฐ ์ฝ”๋ฉ˜ํŠธ์˜ ์„ค๋ช…์ด ์‹ค์ œ ์ฝ”๋“œ ์ƒํ™ฉ๊ณผ ๋งž์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ฝ”๋“œ๋Š” ์ด๋ฏธ quizId.toLong()์œผ๋กœ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋ณ€ํ™˜ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค(toInt()๊ฐ€ ์•„๋‹˜).

ํƒ€์ž… ์ฐจ์ด๋Š” ์„ค๊ณ„์ƒ ์˜๋„๋œ ๊ฒƒ์œผ๋กœ ํ™•์ธ๋ฉ๋‹ˆ๋‹ค:

  • QuizModel.quizId: Int
  • QuizScoreModel.quizId: Long (๋„คํŠธ์›Œํฌ DTO RequestQuizScoreDto๋„ ๋™์ผํ•˜๊ฒŒ Long)

ํ˜„์žฌ ๊ตฌํ˜„์€ ํƒ€์ž… ๋ถˆ์ผ์น˜๋ฅผ ์ ์ ˆํžˆ ์ฒ˜๋ฆฌํ•˜๊ณ  ์žˆ์œผ๋ฏ€๋กœ, ์ถ”๊ฐ€ ์ˆ˜์ •์ด ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

Likely an incorrect or invalid review comment.

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

๐Ÿงน Nitpick comments (8)
core/domain/src/main/java/com/teamwable/domain/usecase/GetRemainingTimeUseCase.kt (1)

16-26: ์ด์ „ ๋ฆฌ๋ทฐ์˜ ๋ฐ€๋ฆฌ์ดˆ ๊ณ„์‚ฐ ๋ฌธ์ œ๊ฐ€ ์ˆ˜์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

Line 18์—์„œ now.nano / 1_000_000L์„ ํฌํ•จํ•˜์—ฌ ์ •ํ™•ํ•œ ์ดˆ๊ธฐ ์ง€์—ฐ ์‹œ๊ฐ„์„ ๊ณ„์‚ฐํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ํƒ€์ด๋จธ ์ •ํ™•๋„ ๋ฌธ์ œ๊ฐ€ ํ•ด๊ฒฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

์„ ํƒ์ ์œผ๋กœ, ๊ฐ€๋…์„ฑ์„ ์œ„ํ•ด ๋งค์ง ๋„˜๋ฒ„ 60_000L์„ ์ƒ์ˆ˜๋กœ ์ถ”์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

companion object {
    private const val MINUTE_IN_MILLIS = 60_000L
}
core/data/src/main/java/com/teamwable/data/worker/DailyTaskSchedulerImpl.kt (2)

20-22: LocalDateTime.now() ์ค‘๋ณต ํ˜ธ์ถœ์„ ์ œ๊ฑฐํ•˜์„ธ์š”.

Line 21์—์„œ LocalDateTime.now()๋ฅผ ๋‹ค์‹œ ํ˜ธ์ถœํ•˜๋Š” ๋Œ€์‹  Line 20์˜ now ๋ณ€์ˆ˜๋ฅผ ์žฌ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ˜„์žฌ ์ฝ”๋“œ๋Š” ๋‘ ํ˜ธ์ถœ ์‚ฌ์ด์— ์‹œ๊ฐ„์ด ํ๋ฅด๋ฉด ์ด๋ก ์ ์œผ๋กœ ๋ถˆ์ผ์น˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜์ •ํ•˜์„ธ์š”:

 val now = LocalDateTime.now()
-val nextMidnight = LocalDateTime.now().plusDays(1).with(LocalTime.MIDNIGHT)
+val nextMidnight = now.plusDays(1).with(LocalTime.MIDNIGHT)
 val initialDelay = Duration.between(now, nextMidnight)

32-36: ๋ถˆํ•„์š”ํ•œ .Companion ๋ช…์‹œ๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.

Kotlin์—์„œ๋Š” companion object์˜ ๋ฉค๋ฒ„์— ์ ‘๊ทผํ•  ๋•Œ .Companion์„ ๋ช…์‹œ์ ์œผ๋กœ ์ž‘์„ฑํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ฐ„์†Œํ™”ํ•˜์„ธ์š”:

-WorkManager.Companion.getInstance(context).enqueueUniquePeriodicWork(
-    ResetQuizStatusWorker.Companion.WORK_NAME,
+WorkManager.getInstance(context).enqueueUniquePeriodicWork(
+    ResetQuizStatusWorker.WORK_NAME,
     ExistingPeriodicWorkPolicy.KEEP,
     dailyResetRequest,
 )
feature/quiz/src/main/java/com/teamwable/quiz/start/QuizStartViewModel.kt (2)

38-47: ํƒ€์ด๋จธ ์žฌ์‹œ์ž‘ ๋™์ž‘์„ ์žฌ๊ณ ํ•˜์„ธ์š”.

Line 42์—์„œ ํ€ด์ฆˆ ๋กœ๋“œ ์„ฑ๊ณต ์‹œ๋งˆ๋‹ค startTimeMillis๊ฐ€ ์žฌ์„ค์ •๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๋‹ค์Œ ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ ํƒ€์ด๋จธ๊ฐ€ ์žฌ์‹œ์ž‘๋˜์–ด ๋ถ€์ •ํ™•ํ•œ ์ธก์ •์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

  • ๋„คํŠธ์›Œํฌ ์‹คํŒจ ํ›„ ์žฌ์‹œ๋„
  • ํ™”๋ฉด ์žฌ๊ตฌ์„ฑ์œผ๋กœ ์ธํ•œ ์žฌ๋กœ๋“œ

ํƒ€์ด๋จธ๊ฐ€ ์ฒซ ๋กœ๋“œ ์‹œ์—๋งŒ ์‹œ์ž‘๋˜๋„๋ก ๋ณด์žฅํ•˜๋ ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ฐ€๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•˜์„ธ์š”:

 .onSuccess { quizModel ->
-    startTimeMillis = System.currentTimeMillis()
+    if (startTimeMillis == 0L) {
+        startTimeMillis = System.currentTimeMillis()
+    }
     intent { copy(quizModel = quizModel) }
 }

์˜๋„๋œ ๋™์ž‘์ด๋ผ๋ฉด ๋ฌด์‹œํ•˜์…”๋„ ๋ฉ๋‹ˆ๋‹ค.


59-59: ์‹œ๊ฐ„ ๋ณต์‚ฌ๊ฐ€ ์ค‘๋ณต๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Line 59์—์„œ model.copy(time = time)์„ ์ˆ˜ํ–‰ํ•˜๋Š”๋ฐ, ์ด๋ฏธ ๊ณ„์‚ฐ๋œ time ๊ฐ’์„ ๋ชจ๋ธ์— ๋ฎ์–ด์“ฐ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์„œ๋ฒ„ ์‘๋‹ต์˜ model์— ์ด๋ฏธ ์˜ฌ๋ฐ”๋ฅธ ์‹œ๊ฐ„์ด ํฌํ•จ๋˜์–ด ์žˆ๋‹ค๋ฉด ์ด ์ž‘์—…์€ ๋ถˆํ•„์š”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์„œ๋ฒ„ ์‘๋‹ต์— ์‹œ๊ฐ„์ด ํฌํ•จ๋˜์ง€ ์•Š๊ฑฐ๋‚˜ ํด๋ผ์ด์–ธํŠธ ์ธก ์‹œ๊ฐ„์„ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ๋ช…ํ™•ํ•œ ์ด์œ ๊ฐ€ ์žˆ๋‹ค๋ฉด ๋ฌด์‹œํ•˜์…”๋„ ๋ฉ๋‹ˆ๋‹ค.

feature/quiz/src/main/java/com/teamwable/quiz/QuizMainFragment.kt (3)

24-32: ์ค‘๋ณต ๋„ค๋น„๊ฒŒ์ด์…˜/์ค‘๋ณต setContent ๋ฐฉ์ง€: distinctUntilChanged() ์ถ”๊ฐ€ ๊ถŒ์žฅ

๋™์ผ ๊ฐ’ ์žฌ๋ฐฉ์ถœ(๋˜๋Š” STARTED ์žฌ์ง„์ž…) ์‹œ ํ˜„์žฌ ๋กœ์ง์€ ๋งค๋ฒˆ initComposeView()/navigateToStart()๋ฅผ ๋‹ค์‹œ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. flicker๋‚˜ ๋ถˆํ•„์š” ์žฌ๋„ค๋น„๊ฒŒ์ด์…˜์„ ์ค„์ด๊ธฐ ์œ„ํ•ด ์ค‘๋ณต ์ƒํƒœ๋ฅผ ํ•„ํ„ฐ๋งํ•˜์„ธ์š”. ํ•„์š” ์‹œ ์ตœ์ดˆ 1ํšŒ ์ฒ˜๋ฆฌ๋ผ๋ฉด take(1)๋„ ๊ณ ๋ ค ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ ์ตœ์†Œ ๋ณ€๊ฒฝ์„ ์ œ์•ˆํ•ฉ๋‹ˆ๋‹ค:

-                viewModel.isQuizCompleted
-                    .filterNotNull()
-                    .collect { isCompleted ->
+                viewModel.isQuizCompleted
+                    .filterNotNull()
+                    .distinctUntilChanged()
+                    .collect { isCompleted ->

40-42: Fragment์˜ ViewModel๊ณผ Compose์˜ hiltViewModel ์ด์ค‘ํ™” ๊ฐ€๋Šฅ์„ฑ โ€” ๋ช…์‹œ ์ „๋‹ฌ ๊ถŒ์žฅ

Fragment์—์„œ by viewModels()๋กœ ๋ณด์œ ํ•œ ์ธ์Šคํ„ด์Šค์™€ Composable์˜ hiltViewModel()๊ฐ€ ๋™์ผ ์˜ค๋„ˆ๋ฅผ ๋ณด์ง€ ์•Š์œผ๋ฉด ์„œ๋กœ ๋‹ค๋ฅธ ์ธ์Šคํ„ด์Šค๊ฐ€ ์ƒ์„ฑ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ช…์‹œ์ ์œผ๋กœ ์ „๋‹ฌํ•ด ์˜คํ•ด์™€ ๋””๋ฒ„๊น… ๋น„์šฉ์„ ์ค„์ด์„ธ์š”. (QuizMainRoute ์‹œ๊ทธ๋‹ˆ์ฒ˜๊ฐ€ viewModel ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ํ—ˆ์šฉํ•จ โ€” ์ œ๊ณต๋œ ์Šค๋‹ˆํŽซ ๊ธฐ์ค€)

-                    QuizMainRoute(
-                        onBtnClick = ::navigateToGoogleForm,
-                    )
+                    QuizMainRoute(
+                        viewModel = viewModel,
+                        onBtnClick = ::navigateToGoogleForm,
+                    )

๊ฒ€์ฆ: ์‹ค์ œ๋กœ ๋™์ผ ์ธ์Šคํ„ด์Šค์ธ์ง€ ํ™•์ธ ๋ถ€ํƒ๋“œ๋ฆฝ๋‹ˆ๋‹ค(๋„ค๋น„๊ฒŒ์ด์…˜/Compose ์˜ค๋„ˆ ๋ณ€๊ฒฝ ์‹œ ์ด์Šˆ ๊ฐ€๋Šฅ).


49-54: popUpTo(graph, inclusive=true) ๋ฐ˜๋ณต ํ˜ธ์ถœ ์‹œ ๊นœ๋นก์ž„ ๊ฐ€๋Šฅ โ€” ํ˜„์žฌ ๋ชฉ์ ์ง€ ๊ฐ€๋“œ ๋ฐ DSL ์ œ์•ˆ

์ด๋ฏธ navigation_quiz_start์— ์žˆ์„ ๋•Œ๋„ ์žฌ๋‚ด๋น„๊ฒŒ์ด์…˜ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ€๋“œ๋ฅผ ๋‘๋ฉด ๋ถˆํ•„์š” ์ž‘์—…๊ณผ ์ž ์žฌ์  ์˜ˆ์™ธ๋ฅผ ์ค„์ž…๋‹ˆ๋‹ค. ๋˜ํ•œ navOptions DSL๋กœ ๊ฐ„๊ฒฐํ™” ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

     private fun navigateToStart() {
-        val navOptions = NavOptions.Builder()
-            .setPopUpTo(R.id.graph_quiz, true)
-            .build()
-
-        findNavController().navigate(R.id.navigation_quiz_start, null, navOptions)
+        if (findNavController().currentDestination?.id == R.id.navigation_quiz_start) return
+        val options = NavOptions.Builder()
+            .setPopUpTo(R.id.graph_quiz, true)
+            .build()
+        findNavController().navigate(R.id.navigation_quiz_start, null, options)
     }

๋Œ€์•ˆ(์„ ํ˜ธ, KTX ์‚ฌ์šฉ ์‹œ):

+import androidx.navigation.navOptions
...
-        val options = NavOptions.Builder()
-            .setPopUpTo(R.id.graph_quiz, true)
-            .build()
+        val options = navOptions { popUpTo(R.id.graph_quiz) { inclusive = true } }
๐Ÿ“œ Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

๐Ÿ“ฅ Commits

Reviewing files that changed from the base of the PR and between c56d87f and 2044dba.

๐Ÿ“’ Files selected for processing (5)
  • core/data/src/main/java/com/teamwable/data/worker/DailyTaskSchedulerImpl.kt (1 hunks)
  • core/designsystem/src/main/java/com/teamwable/designsystem/component/image/WableImage.kt (3 hunks)
  • core/domain/src/main/java/com/teamwable/domain/usecase/GetRemainingTimeUseCase.kt (1 hunks)
  • feature/quiz/src/main/java/com/teamwable/quiz/QuizMainFragment.kt (1 hunks)
  • feature/quiz/src/main/java/com/teamwable/quiz/start/QuizStartViewModel.kt (1 hunks)
๐Ÿšง Files skipped from review as they are similar to previous changes (1)
  • core/designsystem/src/main/java/com/teamwable/designsystem/component/image/WableImage.kt
๐Ÿงฐ Additional context used
๐Ÿงฌ Code graph analysis (2)
feature/quiz/src/main/java/com/teamwable/quiz/QuizMainFragment.kt (2)
feature/quiz/src/main/java/com/teamwable/quiz/QuizMainScreen.kt (1)
  • QuizMainRoute (31-42)
core/ui/src/main/java/com/teamwable/ui/extensions/FragmentExt.kt (1)
  • openUri (49-54)
feature/quiz/src/main/java/com/teamwable/quiz/start/QuizStartViewModel.kt (1)
core/common/src/main/java/com/teamwable/common/base/BaseViewModel.kt (2)
  • postSideEffect (41-47)
  • intent (37-39)
๐Ÿ”‡ Additional comments (12)
core/domain/src/main/java/com/teamwable/domain/usecase/GetRemainingTimeUseCase.kt (3)

1-11: ํ•„์š”ํ•œ import๊ฐ€ ๋ชจ๋‘ ํฌํ•จ๋˜์–ด ์žˆ๊ณ  ์ ์ ˆํ•ฉ๋‹ˆ๋‹ค.

kotlinx.coroutines Flow API์™€ java.time์„ ์‚ฌ์šฉํ•œ ์‹œ๊ฐ„ ๊ณ„์‚ฐ์ด ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๊ตฌ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.


12-14: Use case ๊ตฌํ˜„์ด ์ ์ ˆํ•ฉ๋‹ˆ๋‹ค.

operator fun์„ ์‚ฌ์šฉํ•˜์—ฌ ํ˜ธ์ถœ ๊ฐ€๋Šฅํ•œ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ œ๊ณตํ•˜๊ณ , onStart๋กœ ์ดˆ๊ธฐ๊ฐ’์„ ์ฆ‰์‹œ emitํ•˜๋Š” ๊ฒƒ์ด ์˜ฌ๋ฐ”๋ฅธ ํŒจํ„ด์ž…๋‹ˆ๋‹ค.


28-37: ์ž์ •๊นŒ์ง€ ๋‚จ์€ ์‹œ๊ฐ„ ๊ณ„์‚ฐ์ด ์ •ํ™•ํ•ฉ๋‹ˆ๋‹ค.

Duration.between()์„ ์‚ฌ์šฉํ•œ ์‹œ๊ฐ„ ๊ณ„์‚ฐ๊ณผ ์‹œ/๋ถ„ ์ถ”์ถœ ๋กœ์ง์ด ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๊ตฌํ˜„๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

core/data/src/main/java/com/teamwable/data/worker/DailyTaskSchedulerImpl.kt (3)

24-30: WorkRequest ์ œ์•ฝ ์กฐ๊ฑด์ด ์˜๋„์ ์œผ๋กœ ์ƒ๋žต๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.

ํ˜„์žฌ WorkRequest์— ์ œ์•ฝ ์กฐ๊ฑด(๋„คํŠธ์›Œํฌ, ๋ฐฐํ„ฐ๋ฆฌ, ์ถฉ์ „ ์ƒํƒœ ๋“ฑ)์ด ์„ค์ •๋˜์–ด ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์ž์ • ๋ฆฌ์…‹ ์ž‘์—…์ด ๋ชจ๋“  ์กฐ๊ฑด์—์„œ ์‹คํ–‰๋˜์–ด์•ผ ํ•œ๋‹ค๋Š” ์˜๋„์ผ ์ˆ˜ ์žˆ์ง€๋งŒ, ๋ฐฐํ„ฐ๋ฆฌ ์ตœ์ ํ™”๋‚˜ Doze ๋ชจ๋“œ์—์„œ์˜ ๋™์ž‘์„ ๊ณ ๋ คํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

ํ•„์š”์— ๋”ฐ๋ผ ์ œ์•ฝ ์กฐ๊ฑด ์ถ”๊ฐ€๋ฅผ ๊ณ ๋ คํ•˜์„ธ์š”:

val dailyResetRequest = PeriodicWorkRequestBuilder<ResetQuizStatusWorker>(
    repeatInterval = 24,
    repeatIntervalTimeUnit = TimeUnit.HOURS,
)
    .setInitialDelay(initialDelay)
    .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 1, TimeUnit.MINUTES)
    .setConstraints(
        Constraints.Builder()
            .setRequiresBatteryNotLow(true) // ํ•„์š”์‹œ
            .build()
    )
    .build()

32-36: KEEP ์ •์ฑ…์˜ ์˜ํ–ฅ์„ ๊ฒ€ํ† ํ•˜์„ธ์š”.

ExistingPeriodicWorkPolicy.KEEP ์ •์ฑ…์€ ์ด๋ฏธ ์ž‘์—…์ด ์Šค์ผ€์ค„๋˜์–ด ์žˆ์œผ๋ฉด ์ƒˆ ์š”์ฒญ์„ ๋ฌด์‹œํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๋‹ค์Œ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค:

  • ์•ฑ์ด ์žฌ์‹œ์ž‘๋˜์–ด๋„ ๊ธฐ์กด ์ž‘์—…์ด ์œ ์ง€๋ฉ๋‹ˆ๋‹ค (์ข‹์Œ)
  • ํ•˜์ง€๋งŒ ์Šค์ผ€์ค„๋ง ๋กœ์ง์„ ๋ณ€๊ฒฝํ•ด๋„ (์˜ˆ: ๋‹ค๋ฅธ ์‹œ๊ฐ„, ๋‹ค๋ฅธ ์ œ์•ฝ ์กฐ๊ฑด) ๊ธฐ์กด ์ž‘์—…์ด ์ทจ์†Œ๋˜๊ธฐ ์ „๊นŒ์ง€๋Š” ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ์ ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค
  • Line 22์˜ initialDelay ๊ณ„์‚ฐ์€ KEEP ์ •์ฑ…์œผ๋กœ ์ธํ•ด ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ ์‚ฌ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค

์ผ์ผ ํ€ด์ฆˆ ๋ฆฌ์…‹์˜ ๊ฒฝ์šฐ KEEP ์ •์ฑ…์ด ์ ์ ˆํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ๋‹ค์Œ์„ ํ™•์ธํ•˜์„ธ์š”:

  1. ์•ฑ ์—…๋ฐ์ดํŠธ ์‹œ ์Šค์ผ€์ค„๋ง ๋กœ์ง ๋ณ€๊ฒฝ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ REPLACE ๋˜๋Š” UPDATE ์ •์ฑ…์„ ๊ณ ๋ คํ•˜์„ธ์š”
  2. ๋˜๋Š” ๋ฒ„์ „ ๋ณ€๊ฒฝ ์‹œ ๊ธฐ์กด ์ž‘์—…์„ ๋ช…์‹œ์ ์œผ๋กœ ์ทจ์†Œํ•˜๋Š” ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๋กœ์ง์„ ์ถ”๊ฐ€ํ•˜์„ธ์š”

10-12: ๊ฒ€ํ†  ์˜๊ฒฌ์„ ๋ฌด์‹œํ•˜์„ธ์š”.

ํ”„๋กœ์ ํŠธ์˜ minSdk๋Š” gradle/libs.versions.toml์—์„œ 28๋กœ ์„ค์ •๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. java.time.* API๋Š” API 26๋ถ€ํ„ฐ ์ง€์›๋˜๋ฏ€๋กœ, minSdk 28์—์„œ๋Š” ์™„์ „ํžˆ ๋„ค์ดํ‹ฐ๋ธŒ๋กœ ์ง€์›๋˜๋ฉฐ desugaring์ด ๋ถˆํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์ฝ”๋“œ์— API ํ˜ธํ™˜์„ฑ ๋ฌธ์ œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

Likely an incorrect or invalid review comment.

feature/quiz/src/main/java/com/teamwable/quiz/start/QuizStartViewModel.kt (5)

1-22: ํด๋ž˜์Šค ๊ตฌ์กฐ์™€ ์˜์กด์„ฑ ์ฃผ์ž…์ด ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๊ตฌ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

Hilt์™€ BaseViewModel์„ ํ™œ์šฉํ•œ ๊ตฌ์กฐ๊ฐ€ ์ ์ ˆํ•˜๋ฉฐ, ํ•„์š”ํ•œ ๋ฆฌํฌ์ง€ํ† ๋ฆฌ๋“ค์ด constructor injection์„ ํ†ตํ•ด ์ฃผ์ž…๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.


25-27: ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ๊ตฌ์กฐ๊ฐ€ ๊ฐœ์„ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

์ด์ „ ๋ฆฌ๋ทฐ ์ฝ”๋ฉ˜ํŠธ์—์„œ ์ง€์ ๋œ ํƒ€์ด๋จธ ์‹œ์ž‘ ์‹œ์  ๋ฌธ์ œ๊ฐ€ ํ•ด๊ฒฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ํƒ€์ด๋จธ๊ฐ€ ์ดˆ๊ธฐํ™” ์‹œ์ ์ด ์•„๋‹Œ ํ€ด์ฆˆ ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์„ฑ๊ณต ํ›„(Line 42)์— ์‹œ์ž‘๋˜๋„๋ก ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.


64-69: ํ€ด์ฆˆ ์™„๋ฃŒ ์ฒ˜๋ฆฌ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๊ตฌํ˜„๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

DataStore์— ์™„๋ฃŒ ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜๊ณ  ๊ฒฐ๊ณผ ํ™”๋ฉด์œผ๋กœ ๋„ค๋น„๊ฒŒ์ด์…˜ํ•˜๋Š” ๋กœ์ง์ด ์ ์ ˆํ•˜๊ฒŒ ๊ตฌํ˜„๋˜์—ˆ์Šต๋‹ˆ๋‹ค.


71-75: ๊ฒฝ๊ณผ ์‹œ๊ฐ„ ๊ณ„์‚ฐ์ด ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๊ตฌํ˜„๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๋ฐ€๋ฆฌ์ดˆ๋ฅผ ์ดˆ ๋‹จ์œ„๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๋กœ์ง์ด ์ •ํ™•ํ•˜๋ฉฐ, ์ผ๋ฐ˜์ ์ธ ํ€ด์ฆˆ ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ ์•ˆ์ „ํ•˜๊ฒŒ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.


29-36: ์›๋ณธ ๋ฆฌ๋ทฐ ๋Œ“๊ธ€์€ ๊ทผ๊ฑฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

userAnswer๋Š” ๋ณ„๋„์˜ ์†์„ฑ์ด ์•„๋‹ˆ๋ผ, oxType์—์„œ ์ž๋™์œผ๋กœ ํŒŒ์ƒ๋˜๋Š” ๊ณ„์‚ฐ๋œ ์†์„ฑ์ž…๋‹ˆ๋‹ค (QuizStartState.kt 15-16ํ–‰):

val userAnswer: Boolean
    get() = oxType == OXType.O

Line 34์—์„œ oxType์„ ์—…๋ฐ์ดํŠธํ•˜๋ฉด userAnswer๋Š” ์ž๋™์œผ๋กœ ์˜ฌ๋ฐ”๋ฅธ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ, ๋ณ„๋„์˜ ์ˆ˜์ •์ด๋‚˜ ๊ฒ€์ฆ์ด ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ฝ”๋“œ๋Š” ์˜๋„๋Œ€๋กœ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.

Likely an incorrect or invalid review comment.

feature/quiz/src/main/java/com/teamwable/quiz/QuizMainFragment.kt (1)

56-57: LGTM โ€” ์™ธ๋ถ€ ๋งํฌ ์—ด๊ธฐ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ์ ์ ˆ

openUri๋กœ ์˜ˆ์™ธ ํ† ์ŠคํŠธ ์ฒ˜๋ฆฌ๋˜์–ด UX ์•ˆ์ „ํ•ฉ๋‹ˆ๋‹ค.

Comment on lines +49 to +62
private fun submitQuiz() {
val time = getElapsedTime()
viewModelScope.launch {
quizRepository.patchQuizScore(
QuizScoreModel(
quizId = currentState.quizModel.quizId.toLong(),
userAnswer = currentState.userAnswer,
quizTime = time,
),
)
.onSuccess { model -> setQuizCompleted(model.copy(time = time)) }
.onFailure { postSideEffect(QuizStartSideEffect.ShowSnackBar(it)) }
}
}
Copy link

@coderabbitai coderabbitai bot Oct 25, 2025

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue | ๐Ÿ”ด Critical

ํ€ด์ฆˆ ์ œ์ถœ ์ „ ์ƒํƒœ ๊ฒ€์ฆ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ค‘๋Œ€ํ•œ ๋ฌธ์ œ๋“ค์ด ์žˆ์Šต๋‹ˆ๋‹ค:

  1. Line 54-55: currentState.quizModel๊ณผ currentState.userAnswer์— ๋Œ€ํ•œ null/์ดˆ๊ธฐํ™” ๊ฒ€์ฆ์ด ์—†์Šต๋‹ˆ๋‹ค. ํ€ด์ฆˆ๊ฐ€ ๋กœ๋“œ๋˜๊ธฐ ์ „์— ์ œ์ถœ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด ํฌ๋ž˜์‹œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  2. ์‚ฌ์šฉ์ž ๋‹ต๋ณ€ ๊ฒ€์ฆ ๋ˆ„๋ฝ: ์‚ฌ์šฉ์ž๊ฐ€ ๋‹ต๋ณ€์„ ์„ ํƒํ•˜์ง€ ์•Š๊ณ  ์ œ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ฒ€์ฆ ๋กœ์ง์„ ์ถ”๊ฐ€ํ•˜์„ธ์š”:

 private fun submitQuiz() {
+    val quizModel = currentState.quizModel ?: run {
+        postSideEffect(QuizStartSideEffect.ShowSnackBar(Throwable("ํ€ด์ฆˆ๋ฅผ ๋กœ๋”ฉํ•˜๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.")))
+        return
+    }
+    
+    if (currentState.oxType == null) {
+        postSideEffect(QuizStartSideEffect.ShowSnackBar(Throwable("๋‹ต๋ณ€์„ ์„ ํƒํ•ด์ฃผ์„ธ์š”.")))
+        return
+    }
+    
     val time = getElapsedTime()
     viewModelScope.launch {
         quizRepository.patchQuizScore(
             QuizScoreModel(
-                quizId = currentState.quizModel.quizId.toLong(),
+                quizId = quizModel.quizId.toLong(),
                 userAnswer = currentState.userAnswer,
                 quizTime = time,
             ),
         )
             .onSuccess { model -> setQuizCompleted(model.copy(time = time)) }
             .onFailure { postSideEffect(QuizStartSideEffect.ShowSnackBar(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
private fun submitQuiz() {
val time = getElapsedTime()
viewModelScope.launch {
quizRepository.patchQuizScore(
QuizScoreModel(
quizId = currentState.quizModel.quizId.toLong(),
userAnswer = currentState.userAnswer,
quizTime = time,
),
)
.onSuccess { model -> setQuizCompleted(model.copy(time = time)) }
.onFailure { postSideEffect(QuizStartSideEffect.ShowSnackBar(it)) }
}
}
private fun submitQuiz() {
val quizModel = currentState.quizModel ?: run {
postSideEffect(QuizStartSideEffect.ShowSnackBar(Throwable("ํ€ด์ฆˆ๋ฅผ ๋กœ๋”ฉํ•˜๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.")))
return
}
if (currentState.oxType == null) {
postSideEffect(QuizStartSideEffect.ShowSnackBar(Throwable("๋‹ต๋ณ€์„ ์„ ํƒํ•ด์ฃผ์„ธ์š”.")))
return
}
val time = getElapsedTime()
viewModelScope.launch {
quizRepository.patchQuizScore(
QuizScoreModel(
quizId = quizModel.quizId.toLong(),
userAnswer = currentState.userAnswer,
quizTime = time,
),
)
.onSuccess { model -> setQuizCompleted(model.copy(time = time)) }
.onFailure { postSideEffect(QuizStartSideEffect.ShowSnackBar(it)) }
}
}
๐Ÿค– Prompt for AI Agents
In feature/quiz/src/main/java/com/teamwable/quiz/start/QuizStartViewModel.kt
around lines 49 to 62, add pre-submit validation to avoid crashes and empty
submissions: check that currentState.quizModel is non-null and initialized and
that currentState.userAnswer is non-null/non-empty before calling
quizRepository.patchQuizScore; if validation fails, return early and invoke
postSideEffect(QuizStartSideEffect.ShowSnackBar(...)) with an appropriate
message (e.g., "Quiz not loaded" or "Please select an answer"); also guard the
quizId conversion (toLong()) by ensuring the quizId is present/parsable before
using it to build QuizScoreModel.

Copy link
Member Author

Choose a reason for hiding this comment

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

submit quiz๋Š” quiz๊ฐ€ ์„ ํƒ ๋˜๊ณ  enabled ๋ฒ„ํŠผ์„ ํ†ตํ•ด์„œ ์„ ํƒ์ด ๋˜๋Š” ๊ตฌ์กฐ์•ผ ํ€ด์ฆˆ๊ฐ€ ์„ ํƒ๋˜์ง€ ์•Š์œผ๋ฉด ์ € ํ•จ์ˆ˜ ์ž์ฒด๊ฐ€ ์‹คํ–‰ ๋˜์ง€ ์•Š์•„

Copy link

Choose a reason for hiding this comment

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

๐Ÿงฉ Analysis chain

๐Ÿ Script executed:

#!/bin/bash
# Check how the submit button is wired in QuizStartScreen
ast-grep --pattern $'BoardRequestButton(
  $$$
  enabled = $ENABLED
  $$$
)'

# Also check the enabled property in QuizStartState
ast-grep --pattern $'val enabled$$$get() = $$$'

Length of output: 0


๐Ÿ Script executed:

#!/bin/bash
# Read QuizStartScreen to see button wiring
cat feature/quiz/src/main/java/com/teamwable/quiz/start/QuizStartScreen.kt | grep -A5 -B5 "enabled"

# Read QuizStartState to confirm enabled property
cat feature/quiz/src/main/java/com/teamwable/quiz/start/model/QuizStartState.kt

Length of output: 796


@chanubc, ํ™•์ธํ–ˆ์Šต๋‹ˆ๋‹ค! ๋ง์”€ํ•˜์‹  ๋Œ€๋กœ QuizStartScreen์—์„œ ๋ฒ„ํŠผ์ด enabled = state.enabled๋กœ ์„ค์ •๋˜์–ด ์žˆ๊ณ , QuizStartState์˜ enabled ์†์„ฑ์ด oxType != null์„ ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ, ์‚ฌ์šฉ์ž๊ฐ€ O/X๋ฅผ ์„ ํƒํ•˜๊ธฐ ์ „๊นŒ์ง€๋Š” ์ œ์ถœ ๋ฒ„ํŠผ์ด ๋น„ํ™œ์„ฑํ™”๋˜์–ด ์žˆ๋„ค์š”. UI ๋ ˆ๋ฒจ์—์„œ ์ด๋ฏธ ๊ฒ€์ฆ์ด ๋˜๊ณ  ์žˆ์–ด์„œ ์ œ๊ฐ€ ์ œ์•ˆํ•œ ์ถ”๊ฐ€ ๊ฒ€์ฆ์€ ๋ถˆํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ๐Ÿ‘

(ใ…‡ใ……ใ…‡)โ™ช


โœ๏ธ Learnings added
Learnt from: chanubc
PR: Team-Wable/WABLE-ANDROID#194
File: feature/quiz/src/main/java/com/teamwable/quiz/start/QuizStartViewModel.kt:49-62
Timestamp: 2025-10-25T19:46:32.144Z
Learning: In feature/quiz QuizStartViewModel.kt, the submitQuiz() function does not need null-check validation for oxType or quizModel because the submit button in QuizStartScreen is only enabled when state.enabled is true (which means oxType != null). The UI enforces this constraint, preventing submitQuiz() from being called when no answer is selected.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

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

๐Ÿงน Nitpick comments (1)
core/designsystem/src/main/java/com/teamwable/designsystem/component/image/WableGlideImage.kt (1)

35-41: ์‹คํŒจ ์ƒํƒœ์—์„œ modifier ์žฌ์‚ฌ์šฉ์„ ํ”ผํ•˜์„ธ์š”.

์ด๋ฏธ์ง€ ๋กœ๋“œ ์‹คํŒจ ์‹œ ํšŒ์ƒ‰ ๋ฐฐ๊ฒฝ์„ ํ‘œ์‹œํ•˜๋Š” ๊ฒƒ์€ ์ข‹์€ UX ๊ฐœ์„ ์ž…๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ failure ๋ธ”๋ก ๋‚ด์—์„œ ๋ถ€๋ชจ modifier๋ฅผ ์žฌ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฌธ์ œ๋ฅผ ์ผ์œผํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

  • failure ์ปดํฌ์ €๋ธ”์€ ์ด๋ฏธ GlideImage์˜ bounds ๋‚ด์—์„œ ๋ Œ๋”๋ง๋˜๋ฏ€๋กœ, ๋ถ€๋ชจ์˜ ํฌ๊ธฐ ์ œ์•ฝ์„ ์ƒ์†๋ฐ›์Šต๋‹ˆ๋‹ค
  • ๋ถ€๋ชจ modifier์—๋Š” ์ด๋ฏธ noRippleThrottleClickable๊ณผ ํฌ๊ธฐ ์ œ์•ฝ์ด ํฌํ•จ๋˜์–ด ์žˆ์–ด ๋ถˆํ•„์š”ํ•˜๊ฒŒ ์ค‘๋ณต๋ฉ๋‹ˆ๋‹ค
  • fillMaxSize()๋Š” ๋ถ€๋ชจ์˜ ํฌ๊ธฐ ์ œ์•ฝ๊ณผ ์ถฉ๋Œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค

๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜์ •ํ•˜์—ฌ ์ƒˆ๋กœ์šด Modifier๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค:

         failure = {
             Box(
-                modifier = modifier
+                modifier = Modifier
                     .fillMaxSize()
                     .background(WableTheme.colors.gray100),
             )
         },
๐Ÿ“œ Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

๐Ÿ“ฅ Commits

Reviewing files that changed from the base of the PR and between 2044dba and 4ffcd3c.

๐Ÿ“’ Files selected for processing (1)
  • core/designsystem/src/main/java/com/teamwable/designsystem/component/image/WableGlideImage.kt (3 hunks)
โฐ 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: PR Checker
๐Ÿ”‡ Additional comments (1)
core/designsystem/src/main/java/com/teamwable/designsystem/component/image/WableGlideImage.kt (1)

3-5: LGTM!

์‹คํŒจ ์ƒํƒœ UI๋ฅผ ์œ„ํ•œ ํ•„์š”ํ•œ import๊ฐ€ ์ ์ ˆํ•˜๊ฒŒ ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

Also applies to: 17-17

Copy link
Collaborator

@sohyun127 sohyun127 left a comment

Choose a reason for hiding this comment

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

๊ณ ์ƒํ•˜์…จ์Šต๋‹ˆ๋‹ค!!

@chanubc chanubc merged commit 95e5479 into develop Oct 26, 2025
2 checks passed
@chanubc chanubc deleted the feature/#192-quiz-api branch October 26, 2025 08:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

โ˜• [COMPOSE] ์ปดํฌ์ฆˆ ui โญ [FEAT] ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ ๊ตฌํ˜„ ๐Ÿป ์ฐฌ์šฐ ์ฐจ์€์šฐ

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] : ํ€ด์ฆˆ ํ™”๋ฉด ์„œ๋ฒ„ํ†ต์‹  / workmanager๋„์ž…

3 participants