Skip to content

Conversation

@kangyuri1114
Copy link
Member

@kangyuri1114 kangyuri1114 commented Aug 18, 2025

개요

  • close #issue 번호

작업 사항

  • 더 이상 사용하지 않는 Challenge 생성/업데이트 관련 Repository 메서드 제거
  • ChallengeDao/Entity는 그대로 두고, 참조하지 않는 메서드만 삭제
  • 서버 연동 코드(challengeService 등) 의존성 제거

주의사항

  • Entity 구조(테이블 스키마)는 변경하지 않음 -> 나중에 한번에 하는 게 좋을 거 같아서,,?
  • 따라서 Room DB version 업데이트 및 Migration은 필요하지 않음

변경 사항(optional)

  • 내용을 적어주세요.

스크린샷(optional)

  • 내용을 적어주세요.
hmh.mp4

Summary by CodeRabbit

  • New Features

    • Introduced a streamlined App Settings screen to add and manage per-app usage goals.
    • App locking now bases timing solely on each app’s individual goal.
  • Refactor

    • Replaced the former Challenge screen with App Settings; navigation and tab title updated accordingly.
    • Simplified UI: removed calendar and “New Challenge” flow; list and add-app actions remain.
    • Home screen no longer shows a total goal time; visuals adjusted to match per-app focus.
  • Chores

    • Removed unused challenge-related services, screens, and strings.

@kangyuri1114 kangyuri1114 requested a review from a team as a code owner August 18, 2025 15:08
@coderabbitai
Copy link

coderabbitai bot commented Aug 18, 2025

Walkthrough

Replaced UsageGoal/Challenge concepts with AppUsageGoal and removed challenge-related networking, models, and UI. Simplified repositories and use cases to local, per-app goal management. Updated ViewModels, adapters, and fragments to reflect app settings focus. Adjusted locking logic to per-app timing. Updated navigation, strings, and layouts accordingly.

Changes

Cohort / File(s) Summary
Domain model refactor
core/domain/.../usagegoal/model/AppUsageGoal.kt, core/domain/.../usagegoal/model/ChallengeStatus.kt, domain/challenge/.../model/Challenge.kt, domain/challenge/.../model/ChallengeWithUsageInput.kt, domain/challenge/.../model/NewChallenge.kt
Introduced AppUsageGoal with nested App; removed ChallengeStatus enum and Challenge/NewChallenge models; exposed top-level AppGoal for challenges.
UsageGoals repository API
core/domain/.../usagegoal/repository/UsageGoalsRepository.kt
Switched interface to AppUsageGoal types for flow and add methods.
Network challenge removal
core/network/.../challenge/AppCodeRequest.kt, .../ChallengeService.kt, `.../model/*(AppsRequest
Challenge*
Lock manager simplification
core/service/.../AppLockManger.kt
Removed total-usage logic and dependencies; APIs now per-app (AppUsageGoal.App); timer uses appRemainingTime only.
Main ViewModel/UI state
core/viewmodel/main/.../HomeItem.kt, MainState.kt, MainViewModel.kt
Removed challenge fields/flows; added AppUsageGoal to state; updated TotalModel signature; initialization focuses on user info, usage goals, and stats.
Data: challenge layer cleanup
data/challenge/.../mapper/*, data/challenge/.../repository/DefaultChallengeRepository.kt
Removed mappers; repository now local-only, drops remote methods and stubs network calls with logs.
Data: usage goals (local/remote)
data/usagestats/.../datasource/local/UsageGoalsLocalDataSource.kt, .../datasource/remote/UsageGoalsRemoteDataSource.kt, .../mapper/UsageGoalMapper.kt, .../repository/DefaultUsageGoalsRepository.kt
Local DS returns Flow; removed total-goal DAO usage and remote DS; repository uses AppUsageGoal and logs on update. Added entity↔AppUsageGoal mappings.
UsageStats domain use cases
`domain/usagestats/.../usecase/(GetTotalUsageGoalUseCase
GetTotalUsageStatsUseCase
Challenge domain repo API
domain/challenge/.../repository/ChallengeRepository.kt, domain/challenge/.../usecase/AddUsageGoalsUseCase.kt, domain/challenge/.../usecase/NewChallengeUseCase.kt
Pruned challenge methods; AddUsageGoalsUseCase maps to AppUsageGoal.App; removed NewChallengeUseCase.
Feature: challenge → app settings UI
feature/challenge/.../AppUsageGoalsFragment.kt, .../goals/AppUsageGoalsAdapter.kt, .../goals/AppUsageGoalsViewHolder.kt, .../appadd/AppAddViewPagerAdapter.kt, .../appadd/time/SetAppGoalTimeFragment.kt
Renamed fragment and adapter/viewholder; removed calendar/new-challenge flows; updated pager to SetAppGoalTimeFragment.
Removed calendar/new-challenge UI
feature/challenge/.../calendar/*, .../newchallenge/*, `.../res/layout/(activity_new_challenge
fragment_period_selection
Main feature wiring
feature/main/.../MainAdapter.kt, .../res/navigation/nav_graph.xml, .../res/menu/bottom_nav_menu.xml, .../res/values/strings.xml, feature/main/.../MainActivity.kt
Switched navigation and titles to AppUsageGoalsFragment/app_setting; replaced unlock action with logging.
Main UI adjustments
feature/main/.../home/UsageStaticsTotalViewHolder.kt, feature/main/.../res/layout/item_usagestatic_total.xml
Removed total goal time display; black hole level selection no longer depends on challengeSuccess.

Sequence Diagram(s)

sequenceDiagram
  participant VM as MainViewModel
  participant Repo as UsageGoalsRepository
  participant Local as UsageGoalsLocalDataSource
  participant Stats as UsageStatsProvider

  VM->>Repo: getUsageGoals()
  Repo->>Local: getUsageGoal()
  Local-->>Repo: Flow<AppUsageGoal>
  Repo-->>VM: Flow<AppUsageGoal>
  VM->>Stats: getUsageStatForPackages(AppUsageGoal.appGoals)
  Stats-->>VM: Usage stats
  VM->>UI: Render app goals and usage
Loading
sequenceDiagram
  participant Svc as AppLockManger
  participant Sys as SystemUsage
  participant Timer as Timer

  Sys-->>Svc: usageStats(package)
  Svc->>Svc: find current AppUsageGoal.App
  Svc->>Svc: calc appRemainingTime
  alt remainingTime > 0
    Svc->>Timer: startTimer(appRemainingTime, package)
    Timer-->>Svc: timeout
    Svc->>Sys: lock(package)
  else
    Svc->>Sys: lock(package)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

🔎refact

Poem

A hare with specs and tidy goals,
Hops past calendars, trims old roles.
Per-app carrots, neatly stacked—
No grand quests, just facts compact.
Tick-tock timers guard the trail,
Swift refactors, cotton-tail. 🥕✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.


📜 Recent review details

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

💡 Knowledge Base configuration:

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

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d49a74e and 8ddaa48.

📒 Files selected for processing (1)
  • feature/main/src/main/java/com/hmh/hamyeonham/feature/main/home/UsageStaticsTotalViewHolder.kt (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • feature/main/src/main/java/com/hmh/hamyeonham/feature/main/home/UsageStaticsTotalViewHolder.kt
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: PR Checker
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/remove_challenge_feature

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

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

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

CodeRabbit Commands (Invoked using PR/Issue comments)

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

Other keywords and placeholders

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

CodeRabbit Configuration File (.coderabbit.yaml)

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

Status, Documentation and Community

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

@kangyuri1114 kangyuri1114 self-assigned this Aug 18, 2025
@kangyuri1114 kangyuri1114 added the ✂️delete 불필요한 코드 삭제 label Aug 18, 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: 7

🧹 Nitpick comments (42)
feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/appadd/time/SetAppGoalTimeFragment.kt (2)

41-46: Use TimeUnit for millis conversion to avoid potential Int overflow and improve readability.

Current math uses Int intermediates; if the hour range increases later, overflow risk appears. Prefer TimeUnit and early Long conversion.

Apply this diff:

-        binding.npUseTimeGoalHour.setOnValueChangedListener { _, _, newTime ->
-            viewModel.setGoalHour((newTime * 60 * 60 * 1000).toLong())
-        }
+        binding.npUseTimeGoalHour.setOnValueChangedListener { _, _, newTime ->
+            viewModel.setGoalHour(TimeUnit.HOURS.toMillis(newTime.toLong()))
+        }
         ...
-        binding.npUseTimeGoalMinute.setOnValueChangedListener { _, _, newTime ->
-            viewModel.setGoalMin((newTime * 60 * 1000).toLong())
-        }
+        binding.npUseTimeGoalMinute.setOnValueChangedListener { _, _, newTime ->
+            viewModel.setGoalMin(TimeUnit.MINUTES.toMillis(newTime.toLong()))
+        }

Add this import at the top of the file:

import java.util.concurrent.TimeUnit

53-55: Apply FOCUS_BLOCK_DESCENDANTS to the minute picker as well for consistent UX.

Right now only the hour picker is blocked from text focus.

             npUseTimeGoalHour.descendantFocusability =
                 NumberPicker.FOCUS_BLOCK_DESCENDANTS
+            npUseTimeGoalMinute.descendantFocusability =
+                NumberPicker.FOCUS_BLOCK_DESCENDANTS
feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/appadd/AppAddViewPagerAdapter.kt (1)

13-17: Map position 1 explicitly and fail fast for out-of-range positions.

Using an else makes it easy to mask future itemCount changes.

     override fun createFragment(position: Int): Fragment {
         return when (position) {
             0 -> AppSelectionFragment()
-            else -> SetAppGoalTimeFragment()
+            1 -> SetAppGoalTimeFragment()
+            else -> throw IndexOutOfBoundsException("Unsupported position: $position (itemCount=$itemCount)")
         }
     }
data/challenge/src/main/java/com/hmh/hamyeonham/data/challenge/repository/DefaultChallengeRepository.kt (3)

6-6: Timber import is fine; confirm logging setup remains after network removals.

Given the module-level dependency changes, please verify that Timber is still initialized in this app flavor so these logs don't silently drop. Also consider using a consistent tag for easier filtering.


13-17: Simplify no-op updateDailyChallengeFailed() and deprecate in interface

We didn’t find any call sites besides the declaration and its default impl, so it’s safe to:

  • In DefaultChallengeRepository.kt (data/challenge/src/main/java/.../DefaultChallengeRepository.kt), replace the runCatching { … } block with an explicit success:
       override suspend fun updateDailyChallengeFailed(): Result<Unit> {
  •    return runCatching {
    
  •        Timber.d("Update daily challenge failed")
    
  •    }
    
  •    Timber.d("updateDailyChallengeFailed() no-op")
    
  •    return Result.success(Unit)
    
    }
  • In ChallengeRepository.kt (domain/challenge/src/main/java/.../ChallengeRepository.kt), mark the method deprecated and note planned removal:
     interface ChallengeRepository {
  • suspend fun updateDailyChallengeFailed(): Result
  • @deprecated(
  •    message = "No-op—will be removed in a future release",
    
  •    replaceWith = ReplaceWith(""), // no direct replacement
    
  •    level = DeprecationLevel.WARNING
    
  • )
  • suspend fun updateDailyChallengeFailed(): Result
    }

Optional: Schedule removal of this method in the next major version.

---

`35-37`: **Refactor deleteApps to avoid logging PII and return explicit success**

The current implementation wraps a `Timber.d` call (including the `appCode` identifier) in `runCatching`, which both leaks an identifier in logs and can inadvertently suppress downstream work if logging ever throws. Since `deleteApps()` is effectively a no-op, update it to log a generic message and return success explicitly:

```diff
--- a/data/challenge/src/main/java/com/hmh/hamyeonham/data/challenge/repository/DefaultChallengeRepository.kt
+++ b/data/challenge/src/main/java/com/hmh/hamyeonham/data/challenge/repository/DefaultChallengeRepository.kt
@@ -34,3 +34,5 @@
   override suspend fun deleteApps(appCode: String): Result<Unit> {
-        return runCatching { Timber.d("Delete app with code: $appCode") }
+        Timber.d("deleteApps() no-op")
+        return Result.success(Unit)
   }

Note: this method is invoked by DeleteUsageGoalUseCase (domain/challenge/src/main/java/com/hmh/hamyeonham/challenge/usecase/DeleteUsageGoalUseCase.kt), so it shouldn’t be deprecated—just refactored to remove sensitive logging.

feature/main/src/main/java/com/hmh/hamyeonham/feature/main/home/UsageStaticsViewHolder.kt (2)

39-49: Remove redundant progress assignment and delete dead commented block.

  • You already animate progress in onBind via setProgressWithAnimation; setting pbAppUsage.progress again here is redundant and may cause flicker or no-op animations.
  • The tvAppTimeLeft block is intentionally disabled; prefer deleting commented-out code to reduce noise. Git history preserves it.

Apply this diff:

         binding.run {
-            pbAppUsage.progress = usageStatusAndGoal.usedPercentage
             tvGoalTime.text = convertTimeToString(usageStatusAndGoal.goalTimeInMinute)
-//            tvAppTimeLeft.text =
-//
-//                context.getSecondStrColoredString(
-//                    firstStr = convertTimeToString(usageStatusAndGoal.timeLeftInMinute),
-//                    secondStr = getString(context, R.string.all_left),
-//                    color = com.hmh.hamyeonham.core.designsystem.R.color.gray1,
-//                )
         }

3-11: Clean up unused imports after removing time-left rendering.

getString and getSecondStrColoredString are no longer used once the time-left UI is removed.

 import android.content.Context
-import androidx.core.content.ContextCompat.getString
 import androidx.recyclerview.widget.RecyclerView
 import com.hmh.hamyeonham.common.context.getAppIconFromPackageName
 import com.hmh.hamyeonham.common.context.getAppNameFromPackageName
-import com.hmh.hamyeonham.common.context.getSecondStrColoredString
 import com.hmh.hamyeonham.common.time.convertTimeToString
 import com.hmh.hamyeonham.common.view.setProgressWithAnimation
feature/main/src/main/res/layout/item_usagestatic.xml (1)

56-66: Remove the large XML comment block and clean up related commented-out code

The XML TextView block for tv_app_time_left should be deleted entirely. We also found commented-out references to tvAppTimeLeft in the view holder—remove those to avoid dead code and confusion.

• feature/main/src/main/res/layout/item_usagestatic.xml: delete lines 56–66 (the commented-out <TextView>).
• feature/main/src/main/java/com/hmh/hamyeonham/feature/main/home/UsageStaticsViewHolder.kt: remove the commented-out lines referencing tvAppTimeLeft (around lines 43–45).

feature/main/src/main/res/layout/item_usagestatic_total.xml (1)

53-61: Remove the Commented-Out Total Goal Label

No references to tv_total_goal or its binding remain in the codebase—only the commented lines themselves. You can safely delete the block and then verify that the layout spacing still looks correct.

• File to update:
– feature/main/src/main/res/layout/item_usagestatic_total.xml (lines 53–61)

feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/goals/AppUsageGoalsViewHolder.kt (1)

15-20: Consider aligning model naming with the non-“Challenge” terminology.

The ViewHolder now uses AppUsageGoalsViewHolder, but still depends on ChallengeUsageGoal. For consistency across the codebase (and to prevent cognitive overhead), consider renaming the UI model to an AppUsageGoal*-prefixed name (e.g., AppUsageGoalUiModel) in a follow-up.

core/domain/src/main/java/com/hmh/hamyeonham/core/domain/usagegoal/model/AppUsageGoal.kt (1)

3-10: Clarify time unit for goalTime or rename to goalTimeInMinute to avoid ambiguity.

Downstream UI types and helpers (e.g., convertTimeToString with goalTimeInMinute) imply “minutes.” To prevent unit mismatches and future regressions, either:

  • Rename goalTime → goalTimeInMinute, or
  • Document the unit explicitly in KDoc.

Option A (rename for explicitness):

 data class AppUsageGoal(
     val appGoals: List<App> = emptyList()
 ) {
     data class App(
         val packageName: String = "",
-        val goalTime: Long = 0
+        val goalTimeInMinute: Long = 0
     )
 }

Option B (keep name, add KDoc):

/**
 * App-level usage goals.
 *
 * @property appGoals List of per-app goals.
 *
 * Nested App:
 * @property packageName Android package name (e.g., "com.example.app").
 * @property goalTime Time limit unit = minutes. Non-negative.
 */
domain/challenge/src/main/java/com/hmh/hamyeonham/challenge/model/Challenge.kt (1)

3-6: Avoid duplicating domain models; consider unifying with AppUsageGoal.App

Introducing a separate AppGoal here can drift from the central domain model (core/domain/.../AppUsageGoal.App). Prefer reusing the domain type or adding a typealias to avoid divergence and future mapping boilerplate.

If this module must keep a local symbol for now, consider marking it deprecated to guide migration:

+@Deprecated(
+    message = "Use core.domain.usagegoal.model.AppUsageGoal.App instead",
+    replaceWith = ReplaceWith("AppUsageGoal.App")
+)
 data class AppGoal(
     val appCode: String,
     val appGoalTime: Long,
 )

Additionally, please document the unit for appGoalTime (e.g., milliseconds) to prevent misuse.

feature/main/src/main/java/com/hmh/hamyeonham/feature/main/home/UsageStaticsTotalViewHolder.kt (2)

29-37: Remove commented code or leave a clear TODO for future work

This block is fully commented out. If the “time left” UI is no longer part of the product, remove it and any now-unused imports to reduce noise. If it’s temporarily disabled, leave a TODO with owner and rationale.


39-45: Avoid double-setting progress; rely on the animated setter only

You set pbTotalUsage.progress here and also call setProgressWithAnimation in onBind. The direct set can neutralize or jump over the animation. Set it once via animation.

Apply:

             tvTotalUsage.text =
                 context.getString(
                     R.string.total_used,
                     convertMillisecondToString(totalModel.totalTimeInForeground)
                 )
-            pbTotalUsage.progress = totalModel.totalPercentage
feature/main/src/main/res/navigation/nav_graph.xml (2)

7-9: Update label to reflect App Usage Goals and use a string resource

The destination now points to AppUsageGoalsFragment, but the label still says "Challenge Fragment". Align the label with the new UI and leverage the added string resource for localization.

Apply:

-        android:label="Challenge Fragment" />
+        android:label="@string/app_setting" />

7-7: Optional: Rename challenge_dest to app_usage_goals_dest for clarity

To keep your navigation IDs in sync with the renamed feature, you can update the ID and all its usages:

• feature/main/src/main/res/navigation/nav_graph.xml (line 7)

-        android:id="@+id/challenge_dest"
+        android:id="@+id/app_usage_goals_dest"

• feature/main/src/main/res/menu/bottom_nav_menu.xml (line 3)

-        android:id="@+id/challenge_dest"
+        android:id="@+id/app_usage_goals_dest"

• feature/main/src/main/java/com/hmh/hamyeonham/feature/main/MainActivity.kt (around line 75)

-                R.id.challenge_dest -> binding.vpMain.setCurrentItem(
+                R.id.app_usage_goals_dest -> binding.vpMain.setCurrentItem(
                     MainScreen.CHALLENGE.ordinal,
                     false

Don’t forget to update any remaining references (navigation actions, tests, documentation) if you proceed with this rename.

feature/main/src/main/res/values/strings.xml (1)

21-21: Update “챌린지” wording across codebase

Several remaining instances of “챌린지” were found after the initial change. Please update these to the new “앱 사용 목표” (or appropriate wording) for consistency:

  • feature/main/src/main/res/values/strings.xml
    • dialog_description_unlock_package (already updated)
  • feature/onboarding/src/main/java/com/hmh/hamyeonham/feature/onboarding/viewmodel/SelectOnBoardingDataViewModel.kt
    • Line 34: "챌린지 기간을 선택해주세요"
    • Line 35: "첫 챌린지로 가볍게 도전하기 좋은 7일을 추천해요"
  • core/common/src/main/res/layout/dialog_common_two_button.xml
    • tools:text at Line 26: "챌린지를 등록하시겠습니까?"
    • tools:text at Line 41: "챌린지를 등록하면 포인트를 얻을 수 있어요"
  • core/common/src/main/res/layout/dialog_common_one_button.xml
    • tools:text at Line 26: "챌린지를 등록하시겠습니까?"
    • tools:text at Line 42: "챌린지를 등록하면 포인트를 얻을 수 있어요"
  • feature/main/src/main/java/com/hmh/hamyeonham/feature/main/home/UsageStaticsTotalViewHolder.kt
    • Comments on Lines 51 (“// 챌린지 성공한 경우”) and 56 (“// 챌린지 실패한 경우”)

Updating these will ensure the entire app reflects the new “앱 사용 목표” terminology.

domain/usagestats/src/main/java/com/hmh/hamyeonham/usagestats/usecase/GetTotalUsageStatsUseCase.kt (2)

44-46: Deduplicate package names to avoid double-counting

If appGoals contains duplicates, you may over-sum usage. Safeguard with distinct().

-    private fun getPackageNamesFromUsageGoals(appUsageGoals: AppUsageGoal): List<String> {
-        return appUsageGoals.appGoals.map { it.packageName }
-    }
+    private fun getPackageNamesFromUsageGoals(appUsageGoals: AppUsageGoal): List<String> {
+        return appUsageGoals.appGoals
+            .map { it.packageName }
+            .distinct()
+    }

22-29: Rename local variable for clarity (singular vs. plural)

usageGoals is a single AppUsageGoal. Rename to reduce cognitive load.

-        usageGoalsRepository.getUsageGoals().first().let { usageGoals ->
+        usageGoalsRepository.getUsageGoals().first().let { appUsageGoal ->
             val usageStatsForSelectedPackages = getUsageStatsForSelectedPackages(
                 startTime,
                 endTime,
-                getPackageNamesFromUsageGoals(usageGoals)
+                getPackageNamesFromUsageGoals(appUsageGoal)
             )
             return calculateTotalUsage(usageStatsForSelectedPackages)
         }
domain/usagestats/src/main/java/com/hmh/hamyeonham/usagestats/model/UsageStatusAndGoal.kt (1)

32-41: Consider moving the extension to top-level for discoverability

Defining fun List<AppUsageGoal.App>.toApps() inside the companion of an inner type hides it. A top-level extension improves discoverability and reuse.

Suggested top-level extension:

package com.hmh.hamyeonham.usagestats.model

import com.hmh.hamyeonham.core.domain.usagegoal.model.AppUsageGoal

fun List<AppUsageGoal.App>.toUsageStatusApps(): List(UsageStatusAndGoal.App) =
    map {
        UsageStatusAndGoal.App(
            packageName = it.packageName,
            goalTime = it.goalTime
        )
    }
domain/usagestats/src/main/java/com/hmh/hamyeonham/usagestats/usecase/GetUsageGoalsUseCase.kt (1)

11-13: Avoid suspend for a function that only returns a Flow

Returning a cold Flow typically shouldn’t require a suspend function. This adds friction to call sites without benefit.

Apply if you also remove suspend from the repository’s getUsageGoals():

-    suspend operator fun invoke(): Flow<AppUsageGoal> {
+    operator fun invoke(): Flow<AppUsageGoal> {
         return usageGoalsRepository.getUsageGoals()
     }
domain/challenge/src/main/java/com/hmh/hamyeonham/challenge/usecase/AddUsageGoalsUseCase.kt (2)

13-21: Surface failure or at least log it

onSuccess ignores failure paths; callers get no signal. Consider returning Result or at minimum handling onFailure for observability.

Would you like me to draft a Result-returning version of this use case and update call sites in a follow-up PR?


1-12: Consider relocating this use case out of the challenge domain

This class is about adding usage goals, not challenges. Moving it under the usage-goal/usage-stats domain would reduce confusion post-challenge removal.

core/domain/src/main/java/com/hmh/hamyeonham/core/domain/usagegoal/repository/UsageGoalsRepository.kt (1)

8-8: Consider making getUsageGoals() non-suspending

A repository method that returns a Flow generally shouldn’t be suspend. This simplifies call sites and matches common Kotlin Flow patterns.

Apply if acceptable across call sites:

-    suspend fun getUsageGoals(): Flow<AppUsageGoal>
+    fun getUsageGoals(): Flow<AppUsageGoal>
domain/usagestats/src/main/java/com/hmh/hamyeonham/usagestats/usecase/GetUsageStatsListUseCase.kt (2)

32-41: Avoid O(n^2) lookup by pre-indexing usage by package

Using find inside map makes it O(n^2) over app count. Precompute a map for O(n).

-            val usageStatusAndGoal = UsageStatusAndGoal(
+            val usageByPackage = usageForSelectedApps.associateBy { it.packageName }
+
+            val usageStatusAndGoal = UsageStatusAndGoal(
                 totalTimeInForeground = totalUsage,
                 apps = usageGoal.appGoals.map {
                     UsageStatusAndGoal.App(
                         packageName = it.packageName,
-                        usageTime = usageForSelectedApps.find { usageStatus -> usageStatus.packageName == it.packageName }?.totalTimeInForeground ?: 0,
+                        usageTime = usageByPackage[it.packageName]?.totalTimeInForeground ?: 0,
                         goalTime = it.goalTime,
                     )
                 }.sortedByDescending { it.usageTime },
             )

55-56: Minor naming nit: parameter isn’t a list

Rename appUsageGoalList to appUsageGoal for clarity.

-    private fun getSelectedPackageList(appUsageGoalList: AppUsageGoal): List<String> =
-        appUsageGoalList.appGoals.map { it.packageName }.distinct()
+    private fun getSelectedPackageList(appUsageGoal: AppUsageGoal): List<String> =
+        appUsageGoal.appGoals.map { it.packageName }.distinct()
feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/AppUsageGoalsViewModel.kt (4)

34-39: Make units explicit and avoid implicit numeric widening in MAX_DELETABLE.

usageTime is likely Long (ms). The constant is Int, which relies on implicit conversion in comparisons. Prefer a Long with explicit unit in the name for clarity.

Apply:

   companion object {
-        const val MAX_DELETABLE = 300000
+        const val MAX_DELETABLE_MILLIS: Long = 300_000L
   }
 
-    val isDeletable: Boolean = usageStatusAndGoal.usageTime <= MAX_DELETABLE
+    val isDeletable: Boolean = usageStatusAndGoal.usageTime <= MAX_DELETABLE_MILLIS

70-79: Capture and surface failures when adding usage goals.

runCatching { addUsageGoalsUseCase(apps) } only tracks success. Logging/telemetry on failure helps diagnose issues and improves UX feedback.

You could extend it like:

             runCatching {
                 addUsageGoalsUseCase(apps)
-            }.onSuccess {
+            }.onSuccess {
                 AmplitudeUtils.trackEventWithProperties("complete_add_new")
-            }
+            }.onFailure {
+                AmplitudeUtils.trackEventWithProperties("add_new_failed")
+                // TODO: expose error state or snackbar event to UI if applicable
+            }

81-93: Likewise, handle failures when deleting a usage goal.

Mirror success/failure tracking for deletes to aid observability.

Proposed change:

             runCatching {
                 deleteUsageGoalUseCase(usageStatusAndGoal.packageName)
-            }.onSuccess {
+            }.onSuccess {
                 AmplitudeUtils.trackEventWithProperties("click_delete_complete")
-            }
+            }.onFailure {
+                AmplitudeUtils.trackEventWithProperties("delete_failed")
+                // TODO: propagate error to UI if needed
+            }

19-23: Remove or wire ChallengeState.appUsageGoals – it’s never updated or consumed

appUsageGoals is declared in ChallengeState but isn’t referenced elsewhere in this feature module. To avoid dead state and confusion with usageStatusAndGoals, please choose one of the following:

  • Wire it up in your ViewModel (e.g. add an updateAppUsageGoals(...) method and call updateState { copy(appUsageGoals = …) } when goals load), or
  • Remove the appUsageGoals property entirely until it’s actually needed by the UI.
data/usagestats/src/main/java/com/hmh/hamyeonham/usagestats/mapper/UsageGoalMapper.kt (2)

6-6: Naming nit: toUsageAppGoal can be clearer.

The name reads as if it's converting to "UsageAppGoal". Consider toAppUsageGoalApp() or toAppGoal() to align with AppUsageGoal.App and mirror toUsageGoalEntity.


10-10: Consider tests for the mapping round-trip.

A small unit test asserting entity -> domain -> entity preserves (packageName, goalTime) would guard regressions.

I can draft a quick test if you confirm the test module and runner setup.

core/viewmodel/main/src/main/java/com/hmh/hamyeonham/core/viewmodel/MainState.kt (1)

9-11: Mutable property inside data class affects equality; confirm intent.

permissionGranted is var, impacting equals/hashCode/copy. If you're mutating this at runtime, this is fine. Otherwise, prefer val and updates via copy.

core/service/src/main/java/com/hmh/hamyeonham/core/lock/AppLockManger.kt (2)

81-89: Prefer a dedicated scope over ProcessLifecycleOwner for service-scoped timers.

This class is @ServiceScoped, yet the timer runs on ProcessLifecycleOwner.get().lifecycleScope. Using a dedicated CoroutineScope tied to this manager/service gives you clearer lifecycle control and avoids coupling with process lifecycle.

Example:

 class AppLockManger @Inject constructor(
@@
 ) {
-    private var checkUsageJob: Job? = null
-    private var timerJob: Job? = null
+    private var checkUsageJob: Job? = null
+    private var timerJob: Job? = null
+    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
@@
-        timerJob = ProcessLifecycleOwner.get().lifecycleScope.launch {
+        timerJob = scope.launch {
             delay(appRemainingTime)
             moveToLock(packageName)
         }
     }
@@
     fun onDestroy() {
         releaseCheckUsageJob()
         releaseTimerJob()
+        scope.cancel()
     }

36-41: Nit: naming of handleFocusedChangedEvent and GetIsUnLockUseCase.

Minor readability nit: consider handleFocusChangedEvent and aligning the boolean semantics of GetIsUnLockUseCase with the predicate (isUnlocked vs isLocked) to reduce double negatives.

feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/goals/AppUsageGoalsAdapter.kt (1)

37-39: Avoid safe-cast on the only view type; bind directly or enforce via sealed hierarchy.

Since this adapter creates only AppUsageGoalsViewHolder.UsageGoalsViewHolder, the safe cast can conceal errors by failing silently.

Apply:

-    override fun onBindViewHolder(holder: AppUsageGoalsViewHolder, position: Int) {
-        (holder as? AppUsageGoalsViewHolder.UsageGoalsViewHolder)?.bind(getItem(position))
-    }
+    override fun onBindViewHolder(holder: AppUsageGoalsViewHolder, position: Int) {
+        (holder as AppUsageGoalsViewHolder.UsageGoalsViewHolder).bind(getItem(position))
+    }

Optional: consider stable IDs for smoother diffing:

init {
    setHasStableIds(true)
}
override fun getItemId(position: Int) =
    getItem(position).usageStatusAndGoal.packageName.hashCode().toLong()
core/viewmodel/main/src/main/java/com/hmh/hamyeonham/core/viewmodel/MainViewModel.kt (1)

109-113: Typo: Rename setUsageGaols -> setUsageGoals

Minor naming typo. Renaming improves readability and consistency with the domain model.

Apply within this file:

-    private fun setUsageGaols(appUsageGoals: AppUsageGoal) {
+    private fun setUsageGoals(appUsageGoals: AppUsageGoal) {
         updateState {
             copy(appUsageGoals = appUsageGoals)
         }
     }

Also update the call site in this file:

// In getUsageGoalAndStatList()
usageGoalsRepository.getUsageGoals().collect {
    setUsageGoals(it) // rename here
    getTodayTimeAndSetUsageStatsList()
}
feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/AppUsageGoalsFragment.kt (1)

146-149: Avoid unsafe adapter cast by holding a typed reference

Casting the RecyclerView adapter each bind is brittle. Prefer holding a typed adapter property (e.g., private lateinit var appUsageGoalsAdapter: AppUsageGoalsAdapter) and assign it once when initializing the RecyclerView, then reference it directly here.

data/usagestats/src/main/java/com/hmh/hamyeonham/usagestats/datasource/local/UsageGoalsLocalDataSource.kt (1)

17-27: Simplify flow transformation; remove ExperimentalCoroutinesApi

Use map instead of flatMapConcat+flow. This removes the ExperimentalCoroutinesApi opt-in and is more idiomatic.

Apply:

-    @OptIn(ExperimentalCoroutinesApi::class)
-    fun getUsageGoal(): Flow<AppUsageGoal> {
-        return usageGoalsDao.getUsageGoal()
-            .flatMapConcat { goalsList ->
-                flow {
-                    val result = AppUsageGoal(
-                        appGoals = goalsList.map { it.toUsageAppGoal() }
-                    )
-                    emit(result)
-                }
-            }
-    }
+    fun getUsageGoal(): Flow<AppUsageGoal> {
+        return usageGoalsDao.getUsageGoal()
+            .map { goalsList ->
+                AppUsageGoal(appGoals = goalsList.map { it.toUsageAppGoal() })
+            }
+    }

Also add the missing import at the top of this file:

import kotlinx.coroutines.flow.map
data/usagestats/src/main/java/com/hmh/hamyeonham/usagestats/repository/DefaultUsageGoalsRepository.kt (2)

13-16: Remove unused constructor deps (cleanup)

usageGoalsDao and usageTotalGoalDao aren’t used anymore. Consider removing them to avoid dead dependencies and ease DI maintenance.


43-46: Drop redundant suspend from getUsageGoals
Since Flow is cold and can be returned directly, you can remove the suspend modifier from the repository API and its implementation to reduce confusion and boilerplate. Update the signatures as follows:

  • core/domain/src/main/java/com/hmh/hamyeonham/core/domain/usagegoal/repository/UsageGoalsRepository.kt
    interface UsageGoalsRepository {
  • suspend fun getUsageGoals(): Flow<AppUsageGoal>
    
  • fun getUsageGoals(): Flow<AppUsageGoal>
    // …
    
    }
    
    
  • data/usagestats/src/main/java/com/hmh/hamyeonham/usagestats/repository/DefaultUsageGoalsRepository.kt
    class DefaultUsageGoalsRepository(...) : UsageGoalsRepository {
  • override suspend fun getUsageGoals(): Flow<AppUsageGoal> {
    
  • override fun getUsageGoals(): Flow<AppUsageGoal> {
        return usageGoalsLocalDataSource.getUsageGoal()
    }
    
    }
    
    

Optionally, you can also remove the now-redundant suspend from your domain use case that simply returns the flow:

  • domain/usagestats/src/main/java/com/hmh/hamyeonham/usagestats/usecase/GetUsageGoalsUseCase.kt
    class GetUsageGoalsUseCase(...) {
  • suspend operator fun invoke(): Flow {
  • operator fun invoke(): Flow {
    return usageGoalsRepository.getUsageGoals()
    }
    }

All existing call sites (e.g. in other use-cases, `DefaultDeviceRepository`, `MainViewModel`) already invoke `.first()`, `.collect()`, etc., within coroutine scopes, so they’ll continue to work without any changes.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

**💡 Knowledge Base configuration:**

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

You can enable these sources in your CodeRabbit configuration.

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between be69df6e7a927f741deac8d93c09d2d9c1b291cb and d49a74e56ca25a560cdea5f742a20f04aa76486a.

</details>

<details>
<summary>📒 Files selected for processing (69)</summary>

* `core/domain/src/main/java/com/hmh/hamyeonham/core/domain/usagegoal/model/AppUsageGoal.kt` (1 hunks)
* `core/domain/src/main/java/com/hmh/hamyeonham/core/domain/usagegoal/model/ChallengeStatus.kt` (0 hunks)
* `core/domain/src/main/java/com/hmh/hamyeonham/core/domain/usagegoal/repository/UsageGoalsRepository.kt` (1 hunks)
* `core/network/src/main/java/com/hmh/hamyeonham/core/network/challenge/AppCodeRequest.kt` (0 hunks)
* `core/network/src/main/java/com/hmh/hamyeonham/core/network/challenge/ChallengeService.kt` (0 hunks)
* `core/network/src/main/java/com/hmh/hamyeonham/core/network/challenge/model/AppsRequest.kt` (0 hunks)
* `core/network/src/main/java/com/hmh/hamyeonham/core/network/challenge/model/ChallengeFinishResponse.kt` (0 hunks)
* `core/network/src/main/java/com/hmh/hamyeonham/core/network/challenge/model/ChallengeResponse.kt` (0 hunks)
* `core/network/src/main/java/com/hmh/hamyeonham/core/network/challenge/model/ChallengeWithUsageRequest.kt` (0 hunks)
* `core/network/src/main/java/com/hmh/hamyeonham/core/network/challenge/model/ChallengeWithUsageResponse.kt` (0 hunks)
* `core/network/src/main/java/com/hmh/hamyeonham/core/network/challenge/model/NewChallengeRequest.kt` (0 hunks)
* `core/network/src/main/java/com/hmh/hamyeonham/core/network/di/ChallengeModule.kt` (0 hunks)
* `core/network/src/main/java/com/hmh/hamyeonham/core/network/di/UsageGoalModule.kt` (0 hunks)
* `core/network/src/main/java/com/hmh/hamyeonham/core/network/usagegoal/DailyChallengeService.kt` (0 hunks)
* `core/network/src/main/java/com/hmh/hamyeonham/core/network/usagegoal/model/UsageGoalResponse.kt` (0 hunks)
* `core/service/src/main/java/com/hmh/hamyeonham/core/lock/AppLockManger.kt` (2 hunks)
* `core/viewmodel/main/src/main/java/com/hmh/hamyeonham/core/viewmodel/HomeItem.kt` (0 hunks)
* `core/viewmodel/main/src/main/java/com/hmh/hamyeonham/core/viewmodel/MainState.kt` (1 hunks)
* `core/viewmodel/main/src/main/java/com/hmh/hamyeonham/core/viewmodel/MainViewModel.kt` (2 hunks)
* `data/challenge/src/main/java/com/hmh/hamyeonham/data/challenge/mapper/AppMapper.kt` (0 hunks)
* `data/challenge/src/main/java/com/hmh/hamyeonham/data/challenge/mapper/ChallengeMapper.kt` (0 hunks)
* `data/challenge/src/main/java/com/hmh/hamyeonham/data/challenge/mapper/ChallengeStatusMapper.kt` (0 hunks)
* `data/challenge/src/main/java/com/hmh/hamyeonham/data/challenge/mapper/NewChallengeMapper.kt` (0 hunks)
* `data/challenge/src/main/java/com/hmh/hamyeonham/data/challenge/repository/DefaultChallengeRepository.kt` (1 hunks)
* `data/usagestats/src/main/java/com/hmh/hamyeonham/usagestats/datasource/local/UsageGoalsLocalDataSource.kt` (3 hunks)
* `data/usagestats/src/main/java/com/hmh/hamyeonham/usagestats/datasource/remote/UsageGoalsRemoteDataSource.kt` (0 hunks)
* `data/usagestats/src/main/java/com/hmh/hamyeonham/usagestats/mapper/UsageGoalMapper.kt` (1 hunks)
* `data/usagestats/src/main/java/com/hmh/hamyeonham/usagestats/repository/DefaultUsageGoalsRepository.kt` (1 hunks)
* `domain/challenge/src/main/java/com/hmh/hamyeonham/challenge/model/Challenge.kt` (1 hunks)
* `domain/challenge/src/main/java/com/hmh/hamyeonham/challenge/model/ChallengeWithUsageInput.kt` (0 hunks)
* `domain/challenge/src/main/java/com/hmh/hamyeonham/challenge/model/NewChallenge.kt` (0 hunks)
* `domain/challenge/src/main/java/com/hmh/hamyeonham/challenge/repository/ChallengeRepository.kt` (0 hunks)
* `domain/challenge/src/main/java/com/hmh/hamyeonham/challenge/usecase/AddUsageGoalsUseCase.kt` (2 hunks)
* `domain/challenge/src/main/java/com/hmh/hamyeonham/challenge/usecase/NewChallengeUseCase.kt` (0 hunks)
* `domain/usagestats/src/main/java/com/hmh/hamyeonham/usagestats/model/UsageStatusAndGoal.kt` (2 hunks)
* `domain/usagestats/src/main/java/com/hmh/hamyeonham/usagestats/usecase/GetTotalUsageGoalUseCase.kt` (1 hunks)
* `domain/usagestats/src/main/java/com/hmh/hamyeonham/usagestats/usecase/GetTotalUsageStatsUseCase.kt` (2 hunks)
* `domain/usagestats/src/main/java/com/hmh/hamyeonham/usagestats/usecase/GetUsageGoalsUseCase.kt` (1 hunks)
* `domain/usagestats/src/main/java/com/hmh/hamyeonham/usagestats/usecase/GetUsageStatsListUseCase.kt` (2 hunks)
* `feature/challenge/src/main/AndroidManifest.xml` (0 hunks)
* `feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/AppUsageGoalsFragment.kt` (4 hunks)
* `feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/AppUsageGoalsViewModel.kt` (3 hunks)
* `feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/appadd/AppAddViewPagerAdapter.kt` (2 hunks)
* `feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/appadd/time/SetAppGoalTimeFragment.kt` (1 hunks)
* `feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/calendar/ChallengeCalendarAdapter.kt` (0 hunks)
* `feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/calendar/ChallengeStatusViewHolder.kt` (0 hunks)
* `feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/goals/AppUsageGoalsAdapter.kt` (3 hunks)
* `feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/goals/AppUsageGoalsViewHolder.kt` (1 hunks)
* `feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/newchallenge/NewChallengeActivity.kt` (0 hunks)
* `feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/newchallenge/NewChallengeViewModel.kt` (0 hunks)
* `feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/newchallenge/NewChallengeViewPagerAdapter.kt` (0 hunks)
* `feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/newchallenge/periodselection/PeriodSelectionFragment.kt` (0 hunks)
* `feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/newchallenge/timeselection/TimeSelectionFragment.kt` (0 hunks)
* `feature/challenge/src/main/res/layout/activity_new_challenge.xml` (0 hunks)
* `feature/challenge/src/main/res/layout/fragment_challenge.xml` (4 hunks)
* `feature/challenge/src/main/res/layout/fragment_period_selection.xml` (0 hunks)
* `feature/challenge/src/main/res/layout/fragment_time_selection.xml` (0 hunks)
* `feature/challenge/src/main/res/layout/item_challenge_status.xml` (0 hunks)
* `feature/challenge/src/main/res/layout/item_point.xml` (0 hunks)
* `feature/challenge/src/main/res/values/strings.xml` (0 hunks)
* `feature/main/src/main/java/com/hmh/hamyeonham/feature/main/MainActivity.kt` (2 hunks)
* `feature/main/src/main/java/com/hmh/hamyeonham/feature/main/MainAdapter.kt` (2 hunks)
* `feature/main/src/main/java/com/hmh/hamyeonham/feature/main/home/UsageStaticsTotalViewHolder.kt` (2 hunks)
* `feature/main/src/main/java/com/hmh/hamyeonham/feature/main/home/UsageStaticsViewHolder.kt` (1 hunks)
* `feature/main/src/main/res/layout/item_usagestatic.xml` (1 hunks)
* `feature/main/src/main/res/layout/item_usagestatic_total.xml` (1 hunks)
* `feature/main/src/main/res/menu/bottom_nav_menu.xml` (1 hunks)
* `feature/main/src/main/res/navigation/nav_graph.xml` (1 hunks)
* `feature/main/src/main/res/values/strings.xml` (1 hunks)

</details>

<details>
<summary>💤 Files with no reviewable changes (37)</summary>

* data/challenge/src/main/java/com/hmh/hamyeonham/data/challenge/mapper/NewChallengeMapper.kt
* core/viewmodel/main/src/main/java/com/hmh/hamyeonham/core/viewmodel/HomeItem.kt
* core/network/src/main/java/com/hmh/hamyeonham/core/network/challenge/model/NewChallengeRequest.kt
* feature/challenge/src/main/res/layout/item_point.xml
* data/usagestats/src/main/java/com/hmh/hamyeonham/usagestats/datasource/remote/UsageGoalsRemoteDataSource.kt
* feature/challenge/src/main/res/layout/fragment_period_selection.xml
* feature/challenge/src/main/res/layout/fragment_time_selection.xml
* feature/challenge/src/main/AndroidManifest.xml
* feature/challenge/src/main/res/layout/item_challenge_status.xml
* domain/challenge/src/main/java/com/hmh/hamyeonham/challenge/usecase/NewChallengeUseCase.kt
* data/challenge/src/main/java/com/hmh/hamyeonham/data/challenge/mapper/ChallengeMapper.kt
* data/challenge/src/main/java/com/hmh/hamyeonham/data/challenge/mapper/ChallengeStatusMapper.kt
* core/network/src/main/java/com/hmh/hamyeonham/core/network/usagegoal/DailyChallengeService.kt
* core/domain/src/main/java/com/hmh/hamyeonham/core/domain/usagegoal/model/ChallengeStatus.kt
* domain/challenge/src/main/java/com/hmh/hamyeonham/challenge/model/NewChallenge.kt
* domain/challenge/src/main/java/com/hmh/hamyeonham/challenge/model/ChallengeWithUsageInput.kt
* feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/calendar/ChallengeCalendarAdapter.kt
* core/network/src/main/java/com/hmh/hamyeonham/core/network/challenge/model/ChallengeFinishResponse.kt
* feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/newchallenge/timeselection/TimeSelectionFragment.kt
* core/network/src/main/java/com/hmh/hamyeonham/core/network/challenge/model/ChallengeWithUsageRequest.kt
* data/challenge/src/main/java/com/hmh/hamyeonham/data/challenge/mapper/AppMapper.kt
* core/network/src/main/java/com/hmh/hamyeonham/core/network/challenge/model/AppsRequest.kt
* feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/calendar/ChallengeStatusViewHolder.kt
* core/network/src/main/java/com/hmh/hamyeonham/core/network/challenge/ChallengeService.kt
* core/network/src/main/java/com/hmh/hamyeonham/core/network/challenge/model/ChallengeWithUsageResponse.kt
* feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/newchallenge/periodselection/PeriodSelectionFragment.kt
* feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/newchallenge/NewChallengeViewPagerAdapter.kt
* core/network/src/main/java/com/hmh/hamyeonham/core/network/di/ChallengeModule.kt
* feature/challenge/src/main/res/layout/activity_new_challenge.xml
* core/network/src/main/java/com/hmh/hamyeonham/core/network/di/UsageGoalModule.kt
* core/network/src/main/java/com/hmh/hamyeonham/core/network/challenge/model/ChallengeResponse.kt
* core/network/src/main/java/com/hmh/hamyeonham/core/network/challenge/AppCodeRequest.kt
* feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/newchallenge/NewChallengeViewModel.kt
* feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/newchallenge/NewChallengeActivity.kt
* core/network/src/main/java/com/hmh/hamyeonham/core/network/usagegoal/model/UsageGoalResponse.kt
* domain/challenge/src/main/java/com/hmh/hamyeonham/challenge/repository/ChallengeRepository.kt
* feature/challenge/src/main/res/values/strings.xml

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (2)</summary>

<details>
<summary>feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/goals/AppUsageGoalsViewHolder.kt (1)</summary><blockquote>

<details>
<summary>feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/goals/ChallengeViewHolder.kt (1)</summary>

* `binding` (15-74)

</details>

</blockquote></details>
<details>
<summary>core/viewmodel/main/src/main/java/com/hmh/hamyeonham/core/viewmodel/MainViewModel.kt (2)</summary><blockquote>

<details>
<summary>feature/onboarding/src/main/java/com/hmh/hamyeonham/feature/onboarding/viewmodel/OnBoardingViewModel.kt (1)</summary>

* `updateState` (84-88)

</details>
<details>
<summary>feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/appadd/AppAddViewModel.kt (1)</summary>

* `updateState` (132-136)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ 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)</summary>

* GitHub Check: PR Checker

</details>

<details>
<summary>🔇 Additional comments (30)</summary><blockquote>

<details>
<summary>feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/appadd/time/SetAppGoalTimeFragment.kt (2)</summary>

`17-17`: **Rename looks good and is consistent across usage.**

Class rename to SetAppGoalTimeFragment aligns with the adapter update; no functional regressions apparent.

---

`51-52`: **Confirm the intended hour range (0..1).**

Limiting hours to 1 seems strict for per-app goals; if this is the new product decision, all good. Otherwise consider expanding (e.g., 0..23).

</details>
<details>
<summary>feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/appadd/AppAddViewPagerAdapter.kt (2)</summary>

`7-7`: **Adapter updated to the new fragment name.**

Import and usage switched to SetAppGoalTimeFragment; wiring looks correct.



Also applies to: 16-16

---

`7-7`: **No leftover `SetGoalTimeFragment` references detected**  
Ran `rg -nP -C2 '\bSetGoalTimeFragment\b'` across the repository and found no occurrences. The rename appears complete.

</details>
<details>
<summary>feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/goals/AppUsageGoalsViewHolder.kt (1)</summary>

`15-20`: **Rename looks good; adapter/ViewHolder hierarchy alignment is consistent.**

The sealed class rename to AppUsageGoalsViewHolder and its usage in the nested ViewHolder are consistent. No functional changes introduced.

</details>
<details>
<summary>feature/challenge/src/main/res/layout/fragment_challenge.xml (2)</summary>

`25-25`: **String/title change aligns with product rename**

The title now uses @string/app_setting. This matches the navigation/menu rename away from “Challenge”.

---

`79-85`: **ID rename verified – no remaining bt_goal_add references found**  
I ran searches for raw “bt_goal_add”, camel-case “btGoalAdd”, `R.id.bt_goal_add`, and `findViewById` usages, and found no matches. All references appear updated to `bt_app_add`.

</details>
<details>
<summary>feature/main/src/main/res/menu/bottom_nav_menu.xml (1)</summary>

`5-5`: **Menu label ‘app_setting’ verified across all locales**  
✅ Confirmed `<string name="app_setting">` is present in every `res/values*/strings.xml`. Approving these changes.

</details>
<details>
<summary>feature/main/src/main/res/values/strings.xml (2)</summary>

`2-2`: **LGTM: New string added**

`app_setting` looks good and aligns with the updated bottom nav label.

---

`18-18`: **Confirm usage and apply correct text**  
I couldn’t find any references to `@string/total_time` in your layouts or code, so please verify where this string is actually used and ensure the replacement makes sense in that context.  

– If you intend to use it in item_usagestatic_total.xml (e.g. with `tv_total_time_left`), re-add the `android:text="@string/total_time"` attribute after applying the diff below.  
– If it isn’t used anywhere, consider removing or renaming the resource to match its intended purpose.  

Proposed diff:  
```diff
-    <string name="total_time">헉! 블랙홀에 빠져\n별조각을 획득하지 못했어요.</string>
+    <string name="total_time">총 사용 시간</string>
domain/usagestats/src/main/java/com/hmh/hamyeonham/usagestats/model/UsageStatusAndGoal.kt (1)

33-40: LGTM: Type migration to AppUsageGoal.App and mapping

The mapping remains straightforward and correct with the new type.

domain/usagestats/src/main/java/com/hmh/hamyeonham/usagestats/usecase/GetTotalUsageGoalUseCase.kt (1)

11-13: Verified migration to AppUsageGoal; no remaining UsageGoal references
I’ve searched the entire codebase (including imports and wildcard patterns) and found no lingering UsageGoal usages. All callers, mappers, and tests should now be using AppUsageGoal.

domain/usagestats/src/main/java/com/hmh/hamyeonham/usagestats/usecase/GetUsageGoalsUseCase.kt (1)

3-3: Import alignment LGTM

Importing AppUsageGoal is consistent with the domain refactor.

feature/main/src/main/java/com/hmh/hamyeonham/feature/main/MainAdapter.kt (1)

15-18: Fragment swap to AppUsageGoalsFragment LGTM

Mapping CHALLENGE to AppUsageGoalsFragment aligns with the feature removal.

domain/challenge/src/main/java/com/hmh/hamyeonham/challenge/usecase/AddUsageGoalsUseCase.kt (2)

17-18: AppUsageGoal mapping LGTM

Switching to AppUsageGoal.App(appCode, goalTime) is correct and consistent with the new model.


14-20: Verified no remote calls in ChallengeRepository.postApps

DefaultChallengeRepository.postApps now only logs via Timber and does not perform any network operations. The call in AddUsageGoalsUseCase is therefore safe—if you’d like to keep the logging side-effect, you can leave it as is; otherwise you may simplify by removing the redundant call:

• domain/challenge/src/main/java/com/hmh/hamyeonham/challenge/usecase/AddUsageGoalsUseCase.kt:14-20
• data/challenge/src/main/java/com/hmh/hamyeonham/data/challenge/repository/DefaultChallengeRepository.kt (postApps logs only)

Optional simplification:

-        challengeRepository.postApps(apps).onSuccess {
-            usageGoalsRepository.addUsageGoalList(
-                apps.apps.map { AppUsageGoal.App(it.appCode, it.goalTime) }
-            )
-        }
+        usageGoalsRepository.addUsageGoalList(
+            apps.apps.map { AppUsageGoal.App(it.appCode, it.goalTime) }
+        )
core/domain/src/main/java/com/hmh/hamyeonham/core/domain/usagegoal/repository/UsageGoalsRepository.kt (1)

8-10: API switch to AppUsageGoal LGTM

Signatures updated to AppUsageGoal/AppUsageGoal.App look consistent across the domain.

domain/usagestats/src/main/java/com/hmh/hamyeonham/usagestats/usecase/GetUsageStatsListUseCase.kt (1)

30-31: Confirm business rule: add “deleted usage of today” regardless of the requested time window

You add deleteGoalRepository.getDeletedUsageOfToday() even when start/end may not be “today”. Verify this matches the intended behavior for arbitrary ranges.

data/usagestats/src/main/java/com/hmh/hamyeonham/usagestats/mapper/UsageGoalMapper.kt (1)

6-10: Symmetric, minimal mappings look good.

The extensions are concise, internal, and match the new domain model. This keeps persistence<->domain conversions straightforward.

core/viewmodel/main/src/main/java/com/hmh/hamyeonham/core/viewmodel/MainState.kt (2)

6-11: Potential duplication between appGoals and appUsageGoals.

You now carry both a list of AppGoal and an aggregate AppUsageGoal. If both model the same concept at different shapes, ensure a single source of truth to prevent divergence.

  • Is appGoals strictly UI projection? If so, consider deriving it from appUsageGoals to keep state minimal.

3-4: No layering issue: AppGoal is a domain model, not a feature
The AppGoal class lives in domain/challenge/src/main/java/com/hmh/hamyeonham/challenge/model/Challenge.kt, so core.viewmodel is merely depending on a domain module. Relying on domain types in your view-model layer is expected—there’s no cross-layer violation here.

Likely an incorrect or invalid review comment.

core/service/src/main/java/com/hmh/hamyeonham/core/lock/AppLockManger.kt (2)

43-61: Flow firstOrNull() early-return can skip scheduling; confirm emission behavior.

If GetUsageGoalsUseCase() emits later (cold flow), using firstOrNull() may return null and skip monitoring. If goals are guaranteed to be present/emitted, fine; otherwise, consider waiting with a timeout.

Option:

-            val usageGoals = getUsageGoalsUseCase().firstOrNull() ?: return@launch
+            val usageGoals = withContext(Dispatchers.IO) {
+                kotlinx.coroutines.withTimeoutOrNull(5_000) {
+                    getUsageGoalsUseCase().first()
+                }
+            } ?: return@launch

Validate if this is needed; otherwise, keep as-is.


64-79: Lock check logic reads well after per-app simplification.

Clean separation: compare app usage vs app goal, then either lock or schedule timer based on remaining time. This is much simpler than the prior global total logic.

feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/goals/AppUsageGoalsAdapter.kt (1)

10-13: Adapter renaming and type updates look consistent.

Class and generic type updates are coherent with the new holder. No issues spotted.

core/viewmodel/main/src/main/java/com/hmh/hamyeonham/core/viewmodel/MainViewModel.kt (1)

6-6: AppUsageGoal import aligns with domain refactor — LGTM

Import correctly reflects the shift to AppUsageGoal-based flow.

feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/AppUsageGoalsFragment.kt (3)

20-20: Adapter migration to AppUsageGoalsAdapter — LGTM

Import matches the new adapter and aligns with the PR’s goal removal.


161-176: RecyclerView adapter setup and click handling — LGTM

Adapter wiring and delete handling align with the new goals flow. Resource usage continues to live under feature.challenge, which is fine as long as those resources remain.


43-63: ✅ Navigation Graph Updated and No Lingering References

  • The nav_graph.xml at feature/main/src/main/res/navigation/nav_graph.xml now references
    com.hmh.hamyeonham.challenge.AppUsageGoalsFragment for @+id/challenge_dest and has no remaining ChallengeFragment entries.
  • The layout file fragment_challenge.xml still exists under feature/challenge/src/main/res/layout, preserving the FragmentChallengeBinding.

All navigation and binding references are correctly updated.

data/usagestats/src/main/java/com/hmh/hamyeonham/usagestats/datasource/local/UsageGoalsLocalDataSource.kt (2)

38-45: Bulk insert mapping — LGTM

Mapping to UsageGoalEntity list is correct and consistent with the single insert path.


29-36: Upsert semantics confirmed for addUsageAppGoal

The UsageGoalsDao.insertUsageGoal method is annotated with @Insert(onConflict = OnConflictStrategy.REPLACE), so calling addUsageAppGoal will replace an existing goal for the same package rather than create duplicates. No changes needed here.

Comment on lines 31 to 33
override suspend fun postApps(request: Apps): Result<Unit> {
return runCatching { challengeService.postApps(request.toAppsRequest()) }
return runCatching { Timber.d("Post apps: $request") }
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

postApps() became a no-op and currently logs potentially sensitive details.

  • Returning success unconditionally is a behavior change; ensure callers don’t depend on error propagation.
  • Logging the full Apps payload can expose user app lists. Prefer minimal/no logging for PII safety.
  • You can simplify to an explicit success return.

Apply this diff:

-        return runCatching { Timber.d("Post apps: $request") }
+        Timber.d("postApps() no-op")
+        return Result.success(Unit)

Follow-ups:

  • If this method is truly unused, deprecate here and schedule removal from ChallengeRepository to reduce API surface.
  • Verify usages:

🏁 Script executed:

#!/bin/bash
# Find usages of postApps with context (exclude build outputs if present)
rg -n -C3 --type=kt '\bpostApps\s*\('

Length of output: 66


🏁 Script executed:

#!/bin/bash
# Retry usage search across Kotlin files without relying on file type definitions
rg -n -C3 'postApps\(' --glob '*.kt'

Length of output: 2931


Remove sensitive logging and simplify postApps()

  • File: data/challenge/src/main/java/com/hmh/hamyeonham/data/challenge/repository/DefaultChallengeRepository.kt (lines 31–33)
    Replace the runCatching { Timber.d("Post apps: $request") } wrapper—which always returns success and logs the full payload—with an explicit success return and a minimal log message.
  • Verify callers: the only usage is in
    domain/challenge/src/main/java/com/hmh/hamyeonham/challenge/usecase/AddUsageGoalsUseCase.kt
    Ensure that always returning success here doesn’t break any intended error handling.

Proposed diff:

--- a/data/challenge/src/main/java/com/hmh/hamyeonham/data/challenge/repository/DefaultChallengeRepository.kt
+++ b/data/challenge/src/main/java/com/hmh/hamyeonham/data/challenge/repository/DefaultChallengeRepository.kt
@@ -31,3 +31,5 @@
-    override suspend fun postApps(request: Apps): Result<Unit> {
-        return runCatching { Timber.d("Post apps: $request") }
-    }
+    override suspend fun postApps(request: Apps): Result<Unit> {
+        Timber.d("postApps() no-op")
+        return Result.success(Unit)
+    }
📝 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
override suspend fun postApps(request: Apps): Result<Unit> {
return runCatching { challengeService.postApps(request.toAppsRequest()) }
return runCatching { Timber.d("Post apps: $request") }
}
override suspend fun postApps(request: Apps): Result<Unit> {
Timber.d("postApps() no-op")
return Result.success(Unit)
}
🤖 Prompt for AI Agents
In
data/challenge/src/main/java/com/hmh/hamyeonham/data/challenge/repository/DefaultChallengeRepository.kt
around lines 31-33, remove the runCatching wrapper that logs the full Apps
payload and always returns success; instead return Result.success(Unit) directly
and replace the Timber.d call with a minimal non-sensitive message (e.g.,
"postApps invoked"). Also confirm callers (notably
domain/challenge/.../AddUsageGoalsUseCase.kt) do not rely on errors from this
method—if they do, propagate or construct appropriate failures instead of
silently returning success.

Comment on lines 18 to 41
override suspend fun updateUsageGoal(): Result<Boolean> {
return runCatching {
usageGoalsRemoteDataSource.getUsageGoals().fold(
onSuccess = { usageGoals ->
val totalGoalTime = usageGoals.totalGoalTime
usageTotalGoalDao.insertUsageTotalGoal(
UsageTotalGoalEntity(
totalGoalTime = totalGoalTime,
status = usageGoals.status.name
)
)

// 각 앱 목표 시간 저장
usageGoalsDao.insertUsageGoalList(usageGoals.toUsageGoalEntityList())
usageGoals.status.name != ChallengeStatus.FAILURE.name
},
onFailure = {
false
}
)
// usageGoalsRemoteDataSource.getUsageGoals().fold(
// onSuccess = { usageGoals ->
// val totalGoalTime = usageGoals.totalGoalTime
// usageTotalGoalDao.insertUsageTotalGoal(
// UsageTotalGoalEntity(
// totalGoalTime = totalGoalTime,
// status = usageGoals.status.name
// )
// )
//
// // 각 앱 목표 시간 저장
// usageGoalsDao.insertUsageGoalList(usageGoals.toUsageGoalEntityList())
// usageGoals.status.name != ChallengeStatus.FAILURE.name
// },
// onFailure = {
// false
// }
// )
Timber.d("Updating usage goals in local data source")
return Result.success(true)
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Bug: Misuse of runCatching with a non-local return inside the lambda

Returning Result.success(true) inside runCatching’s lambda causes a non-local return, bypassing runCatching and its exception wrapping. Either return a raw Boolean inside the block or drop runCatching.

Fix with either of the following:

Option A (keep runCatching):

-    override suspend fun updateUsageGoal(): Result<Boolean> {
-        return runCatching {
+    override suspend fun updateUsageGoal(): Result<Boolean> {
+        return runCatching {
             Timber.d("Updating usage goals in local data source")
-            return Result.success(true)
-        }
+            true
+        }
     }

Option B (simplify, remove runCatching):

-    override suspend fun updateUsageGoal(): Result<Boolean> {
-        return runCatching {
-            Timber.d("Updating usage goals in local data source")
-            return Result.success(true)
-        }
-    }
+    override suspend fun updateUsageGoal(): Result<Boolean> {
+        Timber.d("Updating usage goals in local data source")
+        return Result.success(true)
+    }
🤖 Prompt for AI Agents
In
data/usagestats/src/main/java/com/hmh/hamyeonham/usagestats/repository/DefaultUsageGoalsRepository.kt
around lines 18 to 41, the lambda passed to runCatching contains a non-local
return (return Result.success(true)) which escapes the lambda and bypasses
runCatching’s exception handling; fix by either A) keeping runCatching and
returning a raw Boolean from the lambda (replace the inner return
Result.success(true) with just true) so runCatching returns Result<Boolean>, or
B) remove runCatching entirely and directly return a Result (e.g.,
Result.success(true)) from the function body; choose one option and adjust the
code accordingly so no non-local return occurs inside the lambda.

Comment on lines +3 to +6
data class AppGoal(
val appCode: String,
val appGoalTime: Long,
)
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify there are no remaining references to the removed Challenge model

Given the removal of the Challenge type, ensure no lingering references (types, imports, mappers) remain.

Run:


🏁 Script executed:

#!/bin/bash
# Find usages of removed/legacy Challenge constructs and the new AppGoal
rg -n -C2 --type=kt '\bChallenge\b|ChallengeStatus|ChallengeDao|ChallengeService|updateDailyChallengeFailed|AppGoal\('

# Check if project is using the canonical domain model instead
rg -n -C2 --type=kt 'AppUsageGoal\.App\b'

Length of output: 218


🏁 Script executed:

#!/bin/bash
# Retry searching Kotlin files by globbing *.kt instead of using an unrecognized type
rg -n -C2 --glob '*.kt' '\bChallenge\b|ChallengeStatus|ChallengeDao|ChallengeService|updateDailyChallengeFailed'
rg -n -C2 --glob '*.kt' 'AppGoal\('
rg -n -C2 --glob '*.kt' 'AppUsageGoal\.App\b'

Length of output: 16046


Remove remaining references to the removed Challenge model

The search shows that several data‐ and infrastructure‐layer classes still reference “Challenge” constructs that must be removed or renamed now that the Challenge domain type is gone:

  • domain/challenge/src/main/java/com/hmh/hamyeonham/challenge/repository/ChallengeRepository.kt
    • Methods like updateDailyChallengeFailed(), deleteChallengeWithUsage(...) and deleteAllChallengeWithUsage().
  • data/challenge/src/main/java/com/hmh/hamyeonham/data/challenge/datasource/ChallengeLocalDataSourceImpl.kt
    • Imports of ChallengeDao and entity types (ChallengeWithUsageEntity, DailyChallengeEntity) and the injected challengeDao.
  • data/challenge/src/main/java/com/hmh/hamyeonham/data/challenge/repository/DefaultChallengeRepository.kt
    • Override of updateDailyChallengeFailed().
  • core/database/src/main/java/com/hmh/hamyeonham/core/database/di/DaoModule.kt
    providesChallengeDao(...) provider.
  • core/database/src/main/java/com/hmh/hamyeonham/core/database/HMHRoomDatabase.kt
    abstract fun challengeDao(): ChallengeDao declaration.
  • core/database/src/main/java/com/hmh/hamyeonham/core/database/dao/ChallengeDao.kt
    • The ChallengeDao interface itself.
  • data/usagestats/src/main/java/com/hmh/hamyeonham/usagestats/repository/DefaultUsageGoalsRepository.kt
    • Commented code referencing ChallengeStatus (can be removed if no longer relevant).

All these references should be removed or updated to use the new AppGoal/Apps model (or your canonical domain types) to complete the cleanup.

🤖 Prompt for AI Agents
In
domain/challenge/src/main/java/com/hmh/hamyeonham/challenge/model/Challenge.kt
around lines 3 to 6, leftover references to the removed Challenge domain type
remain across the codebase; remove or replace them with the new AppGoal/Apps
model: delete or rename repository methods in
domain/challenge/.../ChallengeRepository.kt (updateDailyChallengeFailed,
deleteChallengeWithUsage, deleteAllChallengeWithUsage) to use AppGoal/Apps
semantics; remove imports and the injected challengeDao plus entity types from
data/challenge/.../ChallengeLocalDataSourceImpl.kt and adapt the data source to
the new models; update data/challenge/.../DefaultChallengeRepository.kt to
remove the override of updateDailyChallengeFailed and implement equivalent logic
using AppGoal/Apps; remove providesChallengeDao from
core/database/.../di/DaoModule.kt and the abstract fun challengeDao()
declaration from HMHRoomDatabase, and delete the ChallengeDao interface and any
related entity classes under core/database/dao; finally, remove or update
commented references to ChallengeStatus in
data/usagestats/.../DefaultUsageGoalsRepository.kt; ensure all callers are
refactored to use AppGoal/Apps types and rebuild/compile to catch remaining
references.

Comment on lines +133 to 137
binding.btAppAdd.setOnSingleClickListener {
AmplitudeUtils.trackEventWithProperties("click_add_button")
val intent = Intent(requireContext(), AppAddActivity::class.java)
appSelectionResultLauncher.launch(intent)
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Confirm layout ID btAppAdd exists (binding uses FragmentChallengeBinding)

Since the binding class is FragmentChallengeBinding, verify that the layout defines a view with id btAppAdd; otherwise you’ll get a runtime crash.

Run to verify:


🏁 Script executed:

#!/bin/bash
# Find the view id in XML layouts
rg -nP '@\+id/btAppAdd' -g '**/*.xml' -C2

# Check for old id still referenced somewhere
rg -nP '@\+id/btGoalAdd' -g '**/*.xml' -C2

Length of output: 88


🏁 Script executed:

#!/bin/bash
set -e

# Search for the view ID btAppAdd in layout XML files under res/layout
rg -n 'android:id="@\+id/btAppAdd"' -g 'res/**/layout/**/*.xml' -C2

# Fallback: search for any reference to btAppAdd in layout XML
rg -n 'btAppAdd' -g 'res/**/layout/**/*.xml' -C2

# Also check for the old ID btGoalAdd to catch any stale references
rg -n 'android:id="@\+id/btGoalAdd"' -g 'res/**/layout/**/*.xml' -C2
rg -n 'btGoalAdd' -g 'res/**/layout/**/*.xml' -C2

Length of output: 227


🏁 Script executed:

#!/bin/bash
set -e

echo "Listing layout files in feature/challenge module:"
find feature/challenge/src/main/res/layout -type f -name "*.xml"

echo -e "\nSearching for btAppAdd id in these layouts:"
rg -n 'android:id="@\\+id/btAppAdd"' -g 'feature/challenge/src/main/res/layout/**/*.xml' -C2 || echo "No matches for btAppAdd id."

echo -e "\nSearching for any reference to btAppAdd in fragment_challenge.xml:"
rg -n 'btAppAdd' feature/challenge/src/main/res/layout/fragment_challenge.xml -C2 || echo "btAppAdd not found in fragment_challenge.xml."

echo -e "\nChecking for old id btGoalAdd to catch stale usages:"
rg -n 'android:id="@\\+id/btGoalAdd"' -g 'feature/challenge/src/main/res/layout/**/*.xml' -C2 || echo "No btGoalAdd id references found."
rg -n 'btGoalAdd' -g 'feature/challenge/src/main/res/layout/**/*.xml' -C2 || echo "No btGoalAdd references found."

Length of output: 1527


Add missing view ID btAppAdd to fragment_challenge.xml

I searched all layout files under feature/challenge/src/main/res/layout and didn’t find any view with

android:id="@+id/btAppAdd"

Without this ID, binding.btAppAdd won’t be generated and will throw at runtime. Please update fragment_challenge.xml to include a view with that ID, for example:

<Button
    android:id="@+id/btAppAdd"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Add" />

• feature/challenge/src/main/res/layout/fragment_challenge.xml: add a view with @+id/btAppAdd

🤖 Prompt for AI Agents
In
feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/AppUsageGoalsFragment.kt
around lines 133 to 137, binding.btAppAdd is referenced but the corresponding
view ID is missing from the layout; open
feature/challenge/src/main/res/layout/fragment_challenge.xml and add a view with
android:id="@+id/btAppAdd" (e.g., a Button or MaterialButton with appropriate
layout_width/layout_height and text), ensure its placement and styling match the
fragment UI, then rebuild to regenerate the binding so binding.btAppAdd is
available at runtime.

app:layout_constraintEnd_toEndOf="@id/tv_modifier_button"
app:layout_constraintStart_toStartOf="@id/tv_app_lock"
app:layout_constraintTop_toBottomOf="@id/tv_app_lock" />
app:layout_constraintTop_toBottomOf="@id/tv_modifier_button" />
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Constraint cycle between tv_app_lock, tv_modifier_button, and rv_app_usage_goals

rv_app_usage_goals top is constrained to tv_modifier_button bottom; tv_modifier_button bottom is constrained to tv_app_lock bottom; tv_app_lock bottom is constrained to rv_app_usage_goals top. This creates a cycle and can break the solver.

Break the cycle by anchoring rv_app_usage_goals directly under tv_app_lock:

-                app:layout_constraintTop_toBottomOf="@id/tv_modifier_button" />
+                app:layout_constraintTop_toBottomOf="@id/tv_app_lock" />

Optionally, if you want to keep the visual tie between tv_modifier_button and tv_app_lock, you can leave tv_modifier_button constrained to tv_app_lock as-is. No other changes are needed for a valid chain.

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

🤖 Prompt for AI Agents
In feature/challenge/src/main/res/layout/fragment_challenge.xml around line 81,
there is a constraint cycle between tv_app_lock, tv_modifier_button, and
rv_app_usage_goals; fix it by changing rv_app_usage_goals’s top constraint so it
anchors directly to tv_app_lock bottom (remove or replace the existing
app:layout_constraintTop_toBottomOf="@id/tv_modifier_button" with a constraint
to @id/tv_app_lock), and keep tv_modifier_button constrained to tv_app_lock if
you want to preserve their visual relationship.

Comment on lines +52 to +55
// totalModel.challengeSuccess -> {
// BlackHoleInfo.createByPercentage(totalModel.totalPercentage)
// ?: BlackHoleInfo.LEVEL0
// }
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

BlackHole level always shows LEVEL5; restore percentage-based level

With challengeSuccess removed, the when now always resolves to LEVEL5. Use percentage to determine the level for correct UX feedback.

Apply:

     private fun bindBlackHoleInfo(totalModel: HomeItem.TotalModel) {
-        val blackHoleInfo = when {
-            // 챌린지 성공한 경우
-//            totalModel.challengeSuccess -> {
-//                BlackHoleInfo.createByPercentage(totalModel.totalPercentage)
-//                    ?: BlackHoleInfo.LEVEL0
-//            }
-            // 챌린지 실패한 경우
-            else -> {
-                BlackHoleInfo.LEVEL5
-            }
-        }
+        val blackHoleInfo =
+            BlackHoleInfo.createByPercentage(totalModel.totalPercentage) ?: BlackHoleInfo.LEVEL0
📝 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
// totalModel.challengeSuccess -> {
// BlackHoleInfo.createByPercentage(totalModel.totalPercentage)
// ?: BlackHoleInfo.LEVEL0
// }
private fun bindBlackHoleInfo(totalModel: HomeItem.TotalModel) {
val blackHoleInfo =
BlackHoleInfo.createByPercentage(totalModel.totalPercentage) ?: BlackHoleInfo.LEVEL0
// …rest of the method unchanged
}
🤖 Prompt for AI Agents
In
feature/main/src/main/java/com/hmh/hamyeonham/feature/main/home/UsageStaticsTotalViewHolder.kt
around lines 52 to 55, the when-expression currently always returns
BlackHoleInfo.LEVEL5 because the challengeSuccess branch was removed; restore
percentage-based determination by replacing the always-LEVEL5 branch with a call
to BlackHoleInfo.createByPercentage(totalModel.totalPercentage) and fallback to
BlackHoleInfo.LEVEL0 if that returns null so the black hole level is derived
from totalModel.totalPercentage.

Comment on lines +107 to 108
Timber.d("Unlock package: $packageName")
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Confirm action does not clear the intent extra; dialog may reappear on next resume

On confirm you only log. Because the extra is cleared only on dismiss, the dialog can show again on future onResume calls. Clear the extra on confirm as well.

Apply:

             setConfirmButtonClickListener {
-                Timber.d("Unlock package: $packageName")
+                Timber.d("Unlock package: $packageName")
+                intent.removeExtra(NavigationProvider.UN_LOCK_PACKAGE_NAME)
             }

If additional business logic should occur here (e.g., actually unlocking the package), wire it via the view model or navigation provider.

📝 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
Timber.d("Unlock package: $packageName")
}
setConfirmButtonClickListener {
Timber.d("Unlock package: $packageName")
intent.removeExtra(NavigationProvider.UN_LOCK_PACKAGE_NAME)
}
🤖 Prompt for AI Agents
In feature/main/src/main/java/com/hmh/hamyeonham/feature/main/MainActivity.kt
around lines 107-108, the dialog Confirm action only logs the package and does
not clear the intent extra so the dialog may reappear on the next onResume;
update the Confirm branch to remove the intent extra that triggered the dialog
(e.g., call intent.removeExtra(EXTRA_UNLOCK_PACKAGE) or
intent.removeExtra("packageName") using the actual constant/string used), then
proceed with any business logic (wire to ViewModel or navigation provider) —
keep the dismiss behavior for the cancel/dismiss path unchanged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✂️delete 불필요한 코드 삭제

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants