-
Notifications
You must be signed in to change notification settings - Fork 1
[Feature/#192] quiz api & workmanger #194
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
โฆ data handling
# 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
WalkthroughWorkManager ๊ธฐ๋ฐ ์ผ์ผ ๋ฆฌ์ ์ค์ผ์ค๋ฌ์ ํด์ฆ ๋คํธ์ํฌ/๋ฐ์ดํฐ์คํ ๊ณ์ธต, ๊ด๋ จ DIยท๋ทฐ๋ชจ๋ธยทUI๋ฅผ ์ถ๊ฐํ๊ณ ์ผ๋ถ ๋์์ธ์์คํ ํ๋ผ๋ฏธํฐ๋ช ๊ณผ ๋งค๋ํ์คํธ provider ์ ์ธ์ ์ ๋ฆฌํ์ต๋๋ค. Changes
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()
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 ์ ๋ฌ)
Estimated code review effort๐ฏ 3 (Moderate) | โฑ๏ธ ~25 minutes ์ถ๊ฐ ๊ฒํ ํฌ์ธํธ:
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touchesโ Failed checks (1 warning)
โ Passed checks (4 passed)
โจ Finishing touches
๐งช Generate unit tests (beta)
๐ Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro ๐ Files selected for processing (3)
โ Files skipped from review due to trivial changes (1)
๐ Additional comments (2)
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. Comment |
There was a problem hiding this 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) : QuizStartSideEffectfeature/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 ๋์์ธ์์ ์๋ํ ๋์์ด ๋ง๋์ง ํ์ธํด ์ฃผ์ธ์.์ด๋ฏธ์ง๊ฐ ๋ค์ํ ์ข ํก๋น๋ฅผ ๊ฐ์ง ์ ์๋ค๋ฉด, ๊ณ ์ ๋์ด ๋์
aspectRatiomodifier ์ฌ์ฉ์ ๊ณ ๋ คํ ์ ์์ต๋๋ค.์์:
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
๐ 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: ํด์ฆ ์๋ฃ ์ํ ์ ์ฅ ๊ธฐ๋ฅ์ด ์ฌ๋ฐ๋ฅด๊ฒ ์ถ๊ฐ๋์์ต๋๋ค.๊ธฐ์กด ์ธํฐํ์ด์ค ํจํด๊ณผ ์ผ๊ด์ฑ ์๊ฒ
isQuizCompletedFlow์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() ์คํ์ผ๋ง ๊ฐ์ ํ๋ฉด ๋ฉ๋๋ค. ๋๋จธ์ง ์ฌํญ์ ํ์ธ ๋ถํ์ํฉ๋๋ค.์ฝ๋๋ฅผ ๊ฒํ ํ ๊ฒฐ๊ณผ:
Duration ํธํ์ฑ: minSdk๊ฐ 28์ด๊ณ ,
setInitialDelay(Duration)์ API 26+ ์ง์ํ๋ฏ๋ก ์์ ํ ํธํ๋ฉ๋๋ค. ๋ฒ์ ๊ฒํ ๋ TimeUnit ๋ณํ์ด ๋ถํ์ํฉ๋๋ค.๋คํธ์ํฌ ์ ์ฝ์ ๋ถํ์ํฉ๋๋ค:
ResetQuizStatusWorker๋ ๋ก์ปฌ DB๋ง ์์ ํฉ๋๋ค (userInfoRepository.saveQuizCompleted(false)). ์๋ฒ ํต์ ์ด ์์ผ๋ฏ๋กRequiredNetworkType.CONNECTED์ ์ฝ์ ์ถ๊ฐํ ์ด์ ๊ฐ ์์ต๋๋ค.๊ฐ์ ๊ฐ๋ฅํ ๋ถ๋ถ: ๋ค๋ง ์คํ์ผ์
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: IntQuizScoreModel.quizId: Long (๋คํธ์ํฌ DTORequestQuizScoreDto๋ ๋์ผํ๊ฒ Long)ํ์ฌ ๊ตฌํ์ ํ์ ๋ถ์ผ์น๋ฅผ ์ ์ ํ ์ฒ๋ฆฌํ๊ณ ์์ผ๋ฏ๋ก, ์ถ๊ฐ ์์ ์ด ํ์ํ์ง ์์ต๋๋ค.
Likely an incorrect or invalid review comment.
core/data/src/main/java/com/teamwable/data/worker/ResetQuizStatusWorker.kt
Show resolved
Hide resolved
core/designsystem/src/main/java/com/teamwable/designsystem/component/image/WableImage.kt
Show resolved
Hide resolved
core/domain/src/main/java/com/teamwable/domain/usecase/GetRemainingTimeUseCase.kt
Show resolved
Hide resolved
feature/quiz/src/main/java/com/teamwable/quiz/QuizMainFragment.kt
Outdated
Show resolved
Hide resolved
feature/quiz/src/main/java/com/teamwable/quiz/start/QuizStartViewModel.kt
Show resolved
Hide resolved
There was a problem hiding this 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
๐ 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์ ์ฑ ์ด ์ ์ ํ ์ ์์ง๋ง, ๋ค์์ ํ์ธํ์ธ์:
- ์ฑ ์ ๋ฐ์ดํธ ์ ์ค์ผ์ค๋ง ๋ก์ง ๋ณ๊ฒฝ์ด ํ์ํ ๊ฒฝ์ฐ
REPLACE๋๋UPDATE์ ์ฑ ์ ๊ณ ๋ คํ์ธ์- ๋๋ ๋ฒ์ ๋ณ๊ฒฝ ์ ๊ธฐ์กด ์์ ์ ๋ช ์์ ์ผ๋ก ์ทจ์ํ๋ ๋ง์ด๊ทธ๋ ์ด์ ๋ก์ง์ ์ถ๊ฐํ์ธ์
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.OLine 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 ์์ ํฉ๋๋ค.
| 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)) } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ํด์ฆ ์ ์ถ ์ ์ํ ๊ฒ์ฆ์ด ํ์ํฉ๋๋ค.
๋ค์๊ณผ ๊ฐ์ ์ค๋ํ ๋ฌธ์ ๋ค์ด ์์ต๋๋ค:
-
Line 54-55:
currentState.quizModel๊ณผcurrentState.userAnswer์ ๋ํ null/์ด๊ธฐํ ๊ฒ์ฆ์ด ์์ต๋๋ค. ํด์ฆ๊ฐ ๋ก๋๋๊ธฐ ์ ์ ์ ์ถ ๋ฒํผ์ ํด๋ฆญํ๋ฉด ํฌ๋์๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค. -
์ฌ์ฉ์ ๋ต๋ณ ๊ฒ์ฆ ๋๋ฝ: ์ฌ์ฉ์๊ฐ ๋ต๋ณ์ ์ ํํ์ง ์๊ณ ์ ์ถํ ์ ์์ต๋๋ค.
๋ค์๊ณผ ๊ฐ์ด ๊ฒ์ฆ ๋ก์ง์ ์ถ๊ฐํ์ธ์:
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.
| 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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
submit quiz๋ quiz๊ฐ ์ ํ ๋๊ณ enabled ๋ฒํผ์ ํตํด์ ์ ํ์ด ๋๋ ๊ตฌ์กฐ์ผ ํด์ฆ๊ฐ ์ ํ๋์ง ์์ผ๋ฉด ์ ํจ์ ์์ฒด๊ฐ ์คํ ๋์ง ์์
There was a problem hiding this comment.
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.
There was a problem hiding this 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
๐ 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
sohyun127
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
๊ณ ์ํ์ จ์ต๋๋ค!!
โ ๐๐ต๐ฒ๐ฐ๐ธ-๐๐ถ๐๐
๐ ๐๐๐๐๐ฒ๐
๐๐ช๐ผ๐ฟ๐ธ ๐๐ฒ๐๐ฐ๐ฟ๐ถ๐ฝ๐๐ถ๐ผ๐ป
๐ท ๐ฆ๐ฐ๐ฟ๐ฒ๐ฒ๐ป๐๐ต๐ผ๐
KakaoTalk_20251026_031546946.mp4
๐ฌ ๐ง๐ผ ๐ฅ๐ฒ๐๐ถ๐ฒ๐๐ฒ๐ฟ๐
Summary by CodeRabbit