Skip to content

Conversation

@comst19
Copy link
Collaborator

@comst19 comst19 commented Dec 28, 2025

1. ⭐️ 변경된 내용

  • 매칭 풀 부족 모달 구현 및 show/hide 로직 구현
  • 뉴 매치 시 checkMatchingPiece 호출
  • 퍼즐 부족 에러 시 현재 유저의 퍼즐 동기화

2. 🖼️ 스크린샷(선택)

3. 💡 알게된 부분

4. 📌 이 부분은 꼭 봐주세요!

Summary by CodeRabbit

  • New Features
    • New "matching pool empty" dialog (Basic / New) with strings and retry action; daily gating to show it at most once per day.
  • Improvements
    • Standardized pull-to-refresh across matching screens.
    • Expanded analytics: event tracking for dialogs, store, purchases and revenue events.
    • Added reset-date utility for daily gating and logout/withdraw now clears the daily dialog flag.
  • Bug Fixes
    • Fixed an escaped trailing backslash in string resources.
  • Tests
    • Added tests for reset-date logic and matching dialog UI/state.

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

@coderabbitai
Copy link

coderabbitai bot commented Dec 28, 2025

📝 Walkthrough

Walkthrough

Adds a MatchingPoolEmpty dialog and strings; renames pull-to-refresh intents/callbacks; wires MatchingPoolEmpty handling with daily gating persisted in datastore; introduces AnalyticsEvent.Track/Revenue and revenue tracking; injects analytics into billing/store/matching; clears dialog state on logout/withdraw.

Changes

Cohort / File(s) Summary
Strings
core/designsystem/src/main/res/values/strings.xml
Removed an escaped trailing backslash and added five strings for matching-pool-empty dialog title, subtexts (basic/new) and button labels.
Dialog type & UI
feature/matching/src/main/java/.../constant/MatchingDialogType.kt, feature/matching/src/main/java/.../dialog/MatchingPoolEmptyDialog.kt
Added MatchingPoolEmpty(MatchingPoolEmptyType) and internal MatchingPoolEmptyDialog composable with Basic/New variants and previews.
Matching UI wiring
feature/matching/src/main/java/.../MatchingScreen.kt, .../page/MatchingUserScreen.kt, .../MatchingScreenTest.kt
Renamed onRefreshonPullToRefresh across signatures/usages; added dialog branch for MatchingPoolEmpty; updated previews/tests.
ViewModel / Intent / State
feature/matching/src/main/java/.../MatchingViewModel.kt, .../contract/MatchingIntent.kt, .../contract/MatchingState.kt
Replaced OnRefresh with OnPullToRefresh; added MatchingPoolEmpty handling, daily gating (read/set/clear), MatchingPoolEmptyType, and updated instant-match/error flows to show pool-empty dialogs.
Dialog signatures & analytics
feature/matching/src/main/java/.../dialog/*.kt
Made TimerCompleteDialog internal; added analytics tracking to several dialog button handlers; InsufficientPuzzleDialog gained entryRouteForLog parameter.
Analytics model & dispatch
core/analytics/src/main/java/.../AnalyticsEvent.kt, .../AmplitudeAnalyticsHelper.kt, .../AnalyticsHelper.kt
Replaced AnalyticsEvent data class with sealed interface plus Track and Revenue; amplitude dispatch split for Track vs Revenue; added trackRevenueEvent(...).
Billing integration
core/billing/build.gradle.kts, core/billing/src/main/java/.../BillingHelperImpl.kt
Added core.analytics dependency; injected AnalyticsHelper into BillingHelperImpl and emit revenue events when purchases yield reward items (constructor gained analyticsHelper).
Store analytics
feature/store/src/main/java/.../StoreScreen.kt, .../NormalProductList.kt, .../PromotionProductList.kt
Track screen view for store and add click tracking for product/promotional taps.
Matching card & detail analytics
feature/matching/src/main/java/.../component/card/MatchingCard.kt, .../detail/MatchingDetailScreen.kt, .../contact/ContactScreen.kt
Dynamic buttonName computation for expanded-card analytics; centralized screen-level tracking in detail screen; image click tracking in ContactScreen; removed some inline dialog analytics.
Datastore / repo / tests for gating
core/datastore/src/main/java/.../LocalUserDataSource*.kt, core/data/src/main/java/.../UserRepositoryImpl.kt, core/domain/src/main/java/.../UserRepository.kt, core/testing/.../FakeUserRepository.kt, core/data/.../AuthRepositoryImpl.kt, tests
Persisted LAST_MODAL_SHOWN_DATE with read/set/clear APIs; repository and fake implementations; cleared value on logout/withdraw; updated tests to assert behavior.
Utilities & debug
core/common/src/main/java/.../TimeUtil.kt, core/common/src/test/.../TimeUtilTest.kt, debug/src/main/java/.../AnalyticsScreen.kt
Added DAILY_RESET_HOUR and getResetDateString(clock) with tests; debug UI now explicitly renders Track and Revenue variants.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant User
  participant MatchingScreen
  participant MatchingViewModel
  participant LocalUserStore
  participant Nav
  rect `#F3FAFF`
    Note over MatchingViewModel,LocalUserStore: Instant-match flow with daily gating for MatchingPoolEmpty
  end
  User->>MatchingScreen: Tap "Get New Match"
  MatchingScreen->>MatchingViewModel: getInstantNewMatch(isFree)
  MatchingViewModel->>LocalUserStore: request instant match -> response (matchId / error)
  alt success (matchId)
    MatchingViewModel->>MatchingViewModel: checkMatchingPiece(matchId)
    alt piece exists
      MatchingViewModel->>Nav: navigate to MatchingDetailRoute(matchId)
    else piece missing
      MatchingViewModel->>MatchingScreen: show InSufficient dialog
    end
  else failure (NOT_ENOUGH_USER_FOR_INSTANT_MATCH)
    MatchingViewModel->>LocalUserStore: read lastMatchingPoolEmptyDialogShownDate()
    alt not shown today and pool empty
      MatchingViewModel->>MatchingScreen: set dialogType = MatchingPoolEmpty(Basic|New)
      MatchingScreen->>User: render MatchingPoolEmptyDialog
      User->>MatchingPoolEmptyDialog: tap primary button
      MatchingPoolEmptyDialog->>MatchingScreen: onNextTryClick()
      MatchingScreen->>MatchingViewModel: retry / log analytics
    else fallback
      MatchingViewModel->>MatchingScreen: show other dialog
    end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

기능 ⚒️, 테스트 📜

Suggested reviewers

  • kkh725
  • tgyuuAn

Poem

🐰 I hopped through strings and dialogs anew,
An empty pool with labels shining through,
I tracked each click, then cleared yesterday's trace,
A tiny hop, a smoother matching place—
Thump-thump, hooray, code sprung with rabbit grace!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.04% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: adding error exception handling for MatchingScreen, which aligns with the PR's core objectives of implementing matching pool empty dialog and error handling logic.
Description check ✅ Passed The description covers the main change sections (changed content, implementation details) but the template requires four specific sections; sections 2 and 3 are empty (optional screenshot and learnings), and section 4 lacks specific review notes.
✨ Finishing touches
  • 📝 Generate docstrings

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

❤️ Share

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

@comst19 comst19 marked this pull request as ready for review December 28, 2025 14:12
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingViewModel.kt (1)

320-345: Pass matchId to handleMatchingError in operations that may receive ALREADY_PURCHASED_CONTACT errors.

The error handling structure is sound, but two functions have a critical issue: they receive matchId but don't pass it to handleMatchingError, breaking error recovery:

  • postOpponentContact(matchId: Int) (line 268): Calls postOpponentContacts(matchId) which can return ALREADY_PURCHASED_CONTACT. Should pass matchId so navigation to ContactRoute succeeds.
  • checkMatchingPiece(matchId: Int) (line 275): Should pass matchId when calling handleMatchingError(it, matchId).

Without matchId, the ALREADY_PURCHASED_CONTACT handler navigates nowhere and falls back to generic error handling.

🧹 Nitpick comments (4)
feature/matching/src/main/java/com/puzzle/matching/graph/detail/dialog/TimerCompleteDialog.kt (1)

13-13: Good refactor to reduce API surface.

Changing the visibility to internal appropriately limits the scope of this dialog to the feature module, following the principle of least privilege for API design.

If you want to be thorough, verify that no other modules depend on this function:

#!/bin/bash
# Description: Check if TimerCompleteDialog is referenced outside the feature/matching module

# Search for imports or direct references to TimerCompleteDialog in other modules
rg -n 'TimerCompleteDialog' -g '!feature/matching/**' -g '*.kt'
feature/matching/src/main/java/com/puzzle/matching/graph/main/dialog/MatchingPoolEmptyDialog.kt (2)

13-13: Reminder: Address the TODO comment.

The TODO indicates that BASIC matching pool logic with a different button label is not yet implemented. This may lead to inconsistent UX if BASIC matching scenarios are triggered.

Do you want me to help design the logic to differentiate button labels for BASIC vs. other matching pool scenarios, or open an issue to track this?


38-44: Consider making the preview function private.

Preview composables are typically marked private to avoid exposing them in the public API surface.

🔎 Suggested fix
 @Preview
 @Composable
-fun PreviewMatchingPoolEmptyDialog() {
+private fun PreviewMatchingPoolEmptyDialog() {
     PieceTheme {
         MatchingPoolEmptyDialog({})
     }
 }
feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingScreen.kt (1)

43-43: Unused import: PROFILE_TIMER_COMPLETE

This constant doesn't appear to be used anywhere in this file. Consider removing it if it was added unintentionally.

🔎 Suggested fix
-import com.puzzle.matching.graph.detail.common.constant.MatchingDetailDialogType.PROFILE_TIMER_COMPLETE
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 803c8a6 and 3a530bb.

📒 Files selected for processing (6)
  • core/designsystem/src/main/res/values/strings.xml
  • feature/matching/src/main/java/com/puzzle/matching/graph/detail/dialog/TimerCompleteDialog.kt
  • feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingScreen.kt
  • feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingViewModel.kt
  • feature/matching/src/main/java/com/puzzle/matching/graph/main/constant/MatchingDialogType.kt
  • feature/matching/src/main/java/com/puzzle/matching/graph/main/dialog/MatchingPoolEmptyDialog.kt
🧰 Additional context used
🧬 Code graph analysis (3)
feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingViewModel.kt (9)
core/data/src/main/java/com/puzzle/data/repository/MatchingRepositoryImpl.kt (1)
  • checkMatchingPiece (79-80)
core/data/src/test/java/com/puzzle/data/source/matching/FakeMatchingDataSource.kt (1)
  • checkMatchingPiece (86-86)
core/domain/src/main/java/com/puzzle/domain/repository/MatchingRepository.kt (1)
  • checkMatchingPiece (19-19)
core/network/src/main/java/com/puzzle/network/api/PieceApi.kt (1)
  • checkMatchingPiece (168-169)
core/network/src/main/java/com/puzzle/network/source/matching/MatchingDataSourceImpl.kt (1)
  • checkMatchingPiece (61-61)
core/network/src/main/java/com/puzzle/network/source/matching/MatchingDataSource.kt (1)
  • checkMatchingPiece (26-26)
core/testing/src/main/java/com/puzzle/testing/domain/repository/SpyMatchingRepository.kt (1)
  • checkMatchingPiece (77-77)
core/common-ui/src/main/java/com/puzzle/common/base/BaseViewModel.kt (1)
  • setState (39-40)
core/common/src/main/java/com/puzzle/common/ResultUtil.kt (1)
  • suspendRunCatching (5-13)
feature/matching/src/main/java/com/puzzle/matching/graph/main/dialog/MatchingPoolEmptyDialog.kt (2)
core/designsystem/src/main/java/com/puzzle/designsystem/component/Dialog.kt (3)
  • PieceDialog (33-58)
  • PieceDialogIconTop (164-214)
  • PieceDialogOneButtonBottom (243-260)
core/designsystem/src/main/java/com/puzzle/designsystem/foundation/Theme.kt (1)
  • PieceTheme (15-18)
feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingScreen.kt (1)
feature/matching/src/main/java/com/puzzle/matching/graph/main/dialog/MatchingPoolEmptyDialog.kt (1)
  • MatchingPoolEmptyDialog (14-36)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (5)
feature/matching/src/main/java/com/puzzle/matching/graph/main/constant/MatchingDialogType.kt (1)

9-9: LGTM!

The new MatchingPoolEmpty dialog type is correctly added as a data object, consistent with the existing pattern for simple dialog types like InSufficient and NewMatching.

core/designsystem/src/main/res/values/strings.xml (1)

92-94: LGTM!

The new string resources for the matching pool empty dialog are well-named, consistent with existing patterns, and the messaging appropriately informs users that their puzzles have been returned.

feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingViewModel.kt (2)

238-247: LGTM!

The updated flow correctly:

  1. Shows a puzzle-used snackbar when free matches are exhausted
  2. Delegates navigation to checkMatchingPiece which handles the piece verification step before proceeding to the detail screen

254-254: LGTM!

Closing the dialog on confirmation is the appropriate behavior since the server has already refunded the puzzles.

feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingScreen.kt (1)

177-185: LGTM!

The new MatchingPoolEmpty dialog handling follows the established pattern consistently, including proper analytics tracking for the "next_try" button click.

@Piece-Puzzly Piece-Puzzly deleted a comment from coderabbitai bot Dec 28, 2025
@comst19 comst19 marked this pull request as draft December 28, 2025 14:30
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingViewModel.kt (1)

275-275: Pass matchId to handleMatchingError to enable ALREADY_PURCHASED_CONTACT handling.

This issue was previously flagged but remains unresolved. The handleMatchingError function now accepts an optional matchId parameter to navigate to ContactRoute when the ALREADY_PURCHASED_CONTACT error occurs. However, matchId is not passed here, preventing that navigation path from triggering when checkMatchingPiece fails.

🔎 Proposed fix
 private fun checkMatchingPiece(matchId: Int) = viewModelScope.launch(dispatcher) {
     suspendRunCatching {
         matchingRepository.checkMatchingPiece(matchId)
     }.onSuccess {
         navigationHelper.navigate(To(MatchingDetailRoute(matchId)))
-    }.onFailure { handleMatchingError(it) }
+    }.onFailure { handleMatchingError(it, matchId) }
 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3a530bb and 50b2bbb.

📒 Files selected for processing (1)
  • feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingViewModel.kt
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (6)
feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingViewModel.kt (6)

254-254: LGTM!

The handling for MatchingPoolEmpty dialog correctly closes the dialog, which is appropriate for an informational message.


268-268: Good improvement.

Passing matchId to handleMatchingError enables the ALREADY_PURCHASED_CONTACT error case to navigate correctly to the contact screen.


320-320: Good API design.

Adding the optional matchId parameter to handleMatchingError provides the context needed for error-specific navigation while maintaining backward compatibility.


324-327: Good practice: synchronizing puzzle count before showing the dialog.

Calling fetchPuzzleCount() before displaying the insufficient puzzle dialog ensures that the UI reflects the current puzzle count, providing accurate information to the user.


329-331: Appropriate error handling for empty matching pool.

Showing the MatchingPoolEmpty dialog for the NOT_ENOUGH_USER_FOR_INSTANT_MATCH error provides clear feedback to users when instant matching is unavailable.


333-337: LGTM!

The ALREADY_PURCHASED_CONTACT error handling correctly navigates to the contact screen when a matchId is available, with proper null-safety.

@comst19 comst19 self-assigned this Dec 28, 2025
@comst19 comst19 added 리뷰 원해요🔥 피어의 리뷰를 기다리는 ing.. 🔥 ㅁㅅ민수 labels Dec 28, 2025
@comst19 comst19 requested review from kkh725 and tgyuuAn December 28, 2025 15:07
@comst19 comst19 marked this pull request as ready for review December 28, 2025 15:07
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
feature/matching/src/androidTest/java/com/puzzle/matching/graph/main/MatchingScreenTest.kt (1)

38-38: LGTM! Test helper correctly updated for new API.

The addition of the onRefresh parameter with a no-op lambda is appropriate for the test helper, ensuring all existing tests continue to work with the updated MatchingScreen signature.

Optionally, consider adding a test case to verify that the refresh callback is invoked in the expected scenarios (e.g., after handling puzzle shortage errors or when the MatchingPoolEmpty dialog is dismissed), especially since the PR introduces error handling and synchronization logic tied to this callback.

feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingViewModel.kt (1)

325-328: fetchPuzzleCount() is async; dialog may show stale count.

fetchPuzzleCount() launches a coroutine and returns immediately. The InSufficient dialog will display before the puzzle count state updates, potentially showing a stale value.

If accurate display is important, consider awaiting the fetch or showing a loading state.

🔎 Option: await the fetch before showing the dialog
 PieceErrorCode.NOT_ENOUGH_PUZZLE -> {
-    fetchPuzzleCount()
+    fetchPuzzleCount().join()
     showPurchaseDialog(MatchingDialogType.InSufficient)
 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 50b2bbb and 160654d.

📒 Files selected for processing (2)
  • feature/matching/src/androidTest/java/com/puzzle/matching/graph/main/MatchingScreenTest.kt
  • feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingViewModel.kt
🔇 Additional comments (6)
feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingViewModel.kt (6)

230-233: LGTM!

The explicit isPuzzleUsed: false parameter clearly indicates that no puzzle is consumed for free matches, making the intent readable and correct.


235-247: Good fix for the previously flagged issue.

The isPuzzleUsed parameter approach is cleaner than relying on state checks. The caller now explicitly declares intent, ensuring the snackbar displays correctly when a puzzle is consumed.


249-255: LGTM!

isPuzzleUsed: true correctly indicates that confirming the NewMatching dialog consumes a puzzle. The MatchingPoolEmpty case appropriately just dismisses the dialog.


268-268: LGTM!

Passing matchId to handleMatchingError enables context-aware handling for the ALREADY_PURCHASED_CONTACT case.


270-277: Consider setting openedMatchId before navigation.

openedMatchId is set after navigationHelper.navigate(). If navigation triggers a lifecycle event that refreshes the match list (via fetchMatchInfogetMatchInfoList), the openedMatchId check at line 148 would use the stale value, potentially expanding the wrong card on return.

🔎 Suggested fix
     }.onSuccess {
+        openedMatchId = matchId
         navigationHelper.navigate(To(MatchingDetailRoute(matchId)))
-        openedMatchId = matchId
     }.onFailure { handleMatchingError(it) }

330-338: Good error recovery patterns.

NOT_ENOUGH_USER_FOR_INSTANT_MATCH now shows the new MatchingPoolEmpty dialog instead of a snackbar, aligning with the PR objective. The ALREADY_PURCHASED_CONTACT handling provides graceful recovery by navigating to the contact screen when the user already has access.

Copy link
Collaborator

@kkh725 kkh725 left a comment

Choose a reason for hiding this comment

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

고생하셨슴니다


@Preview
@Composable
fun PreviewMatchingPoolEmptyDialog() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

private 붙이면 좋을 것 같네요

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

가시성 추가했습니다.

=> e9608e9

}

private suspend fun getInstantNewMatch() = suspendRunCatching {
private suspend fun getInstantNewMatch(isPuzzleUsed: Boolean) = suspendRunCatching {
Copy link
Collaborator

Choose a reason for hiding this comment

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

퍼즐을 처리한 뒤에 함수가 실행되는것이 아닌데 isPuzzleUsed 가 네이밍이 헷갈릴 것 같아요.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

isPuzzleUsed -> isFree로 변경했습니다.

=> b965f6e

@kkh725
Copy link
Collaborator

kkh725 commented Dec 29, 2025

피알 제목에 지라티켓명 달아주세요 ~~

@comst19 comst19 changed the title MatchingScreen 에서 발생하는 에러 예외 처리 추가 [PC-1514] MatchingScreen 에서 발생하는 에러 예외 처리 추가 Dec 29, 2025
onCheckMyProfileClick = {},
onEditProfileClick = {},
onNotificationClick = {},
onRefresh = {},
Copy link
Member

Choose a reason for hiding this comment

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

onRefresh는 사용자의 동작일까요? 뭔가 swipeRefresh 그런 것 같은데 사용자의 intent를 나타내는 더 좋은 네이밍이 있을지도 ?!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

네, 사용자가 메인 매칭 화면에서 아래로 당겨서 새로고침하는 동작입니다.

아래로 당겨서 하는 행위라 onPullToRefresh로 변경했습니다.
=> 9ca87f9

@tgyuuAn tgyuuAn added 머지 해도될듯염🌟 현재 코드를 기존 코드에 합쳐도 될 것 같다라고 판단..! 🌟 and removed 리뷰 원해요🔥 피어의 리뷰를 기다리는 ing.. 🔥 labels Jan 1, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingViewModel.kt (1)

321-346: Consider passing matchId from checkMatchingPiece to handleMatchingError.

The ALREADY_PURCHASED_CONTACT error handling (lines 334-338) silently does nothing when matchId is null. Currently, checkMatchingPiece on line 276 doesn't pass its matchId to handleMatchingError, so if that call returns ALREADY_PURCHASED_CONTACT, no navigation occurs.

If this error can occur from checkMatchingPiece, consider updating line 276:

-        }.onFailure { handleMatchingError(it) }
+        }.onFailure { handleMatchingError(it, matchId) }
feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingScreen.kt (1)

50-50: Optional: Consider explicit imports over wildcard import.

The wildcard import MatchingDialogType.* works but explicit imports (Contact, InSufficient, NewMatching, MatchingPoolEmpty) improve readability and make it clearer which dialog types are used in this file.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 160654d and 9ca87f9.

📒 Files selected for processing (5)
  • feature/matching/src/androidTest/java/com/puzzle/matching/graph/main/MatchingScreenTest.kt
  • feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingScreen.kt
  • feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingViewModel.kt
  • feature/matching/src/main/java/com/puzzle/matching/graph/main/contract/MatchingIntent.kt
  • feature/matching/src/main/java/com/puzzle/matching/graph/main/page/MatchingUserScreen.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • feature/matching/src/androidTest/java/com/puzzle/matching/graph/main/MatchingScreenTest.kt
🧰 Additional context used
🧬 Code graph analysis (3)
feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingScreen.kt (1)
feature/matching/src/main/java/com/puzzle/matching/graph/main/dialog/MatchingPoolEmptyDialog.kt (1)
  • MatchingPoolEmptyDialog (14-36)
feature/matching/src/main/java/com/puzzle/matching/graph/main/page/MatchingUserScreen.kt (1)
core/designsystem/src/main/java/com/puzzle/designsystem/component/Box.kt (1)
  • PiecePullRefreshBox (11-36)
feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingViewModel.kt (9)
core/common/src/main/java/com/puzzle/common/ResultUtil.kt (1)
  • suspendRunCatching (5-13)
core/data/src/main/java/com/puzzle/data/repository/MatchingRepositoryImpl.kt (1)
  • checkMatchingPiece (79-80)
core/data/src/test/java/com/puzzle/data/source/matching/FakeMatchingDataSource.kt (1)
  • checkMatchingPiece (86-86)
core/domain/src/main/java/com/puzzle/domain/repository/MatchingRepository.kt (1)
  • checkMatchingPiece (19-19)
core/network/src/main/java/com/puzzle/network/source/matching/MatchingDataSource.kt (1)
  • checkMatchingPiece (26-26)
core/network/src/main/java/com/puzzle/network/source/matching/MatchingDataSourceImpl.kt (1)
  • checkMatchingPiece (61-61)
core/network/src/main/java/com/puzzle/network/api/PieceApi.kt (1)
  • checkMatchingPiece (168-169)
core/testing/src/main/java/com/puzzle/testing/domain/repository/SpyMatchingRepository.kt (1)
  • checkMatchingPiece (77-77)
core/common-ui/src/main/java/com/puzzle/common/base/BaseViewModel.kt (1)
  • setState (39-40)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (9)
feature/matching/src/main/java/com/puzzle/matching/graph/main/contract/MatchingIntent.kt (1)

14-14: LGTM!

The rename from OnRefresh to OnPullToRefresh is more descriptive and aligns with the updated callback naming throughout the matching flow.

feature/matching/src/main/java/com/puzzle/matching/graph/main/page/MatchingUserScreen.kt (1)

39-43: LGTM!

The parameter rename to onPullToRefresh is consistent with the broader API changes. The internal wiring to PiecePullRefreshBox.onRefresh is correctly maintained.

feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingViewModel.kt (4)

87-87: LGTM!

Intent handling correctly updated to match the renamed OnPullToRefresh intent.


231-247: LGTM!

The isPuzzleUsed parameter correctly addresses the previous review feedback about the unreachable snackbar condition. The flow now properly shows the puzzle-used snackbar only when a paid match is initiated, and checkMatchingPiece is consistently called for all new matches.


249-255: LGTM!

Dialog confirmation handling is correctly implemented:

  • NewMatching triggers a paid match with isPuzzleUsed=true
  • MatchingPoolEmpty simply dismisses the dialog as expected for an informational alert

268-277: LGTM!

Error handling now includes matchId context for proper navigation on ALREADY_PURCHASED_CONTACT. The openedMatchId update after navigation correctly tracks the opened match for subsequent list refreshes.

feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingScreen.kt (3)

176-184: LGTM!

The MatchingPoolEmptyDialog integration follows the established pattern for dialog handling, with proper analytics tracking. The dialog correctly routes through onAcceptClick to close via the ViewModel.


120-135: LGTM!

The onPullToRefresh callback is properly threaded from MatchingRoute through MatchingScreen, with consistent naming throughout the component hierarchy.


253-261: LGTM!

MatchingUserScreen correctly receives the renamed onPullToRefresh callback.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
feature/matching/src/main/java/com/puzzle/matching/graph/main/dialog/MatchingPoolEmptyDialog.kt (1)

13-13: Address the TODO for BASIC matching pool logic.

The comment indicates that different button labels are needed when the matching pool is empty for BASIC matches. This functionality should be implemented or tracked separately.

Would you like me to open a separate issue to track this TODO, or do you have plans to address it in a follow-up PR?

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9ca87f9 and 6a204b2.

📒 Files selected for processing (3)
  • feature/matching/src/main/java/com/puzzle/matching/graph/detail/dialog/TimerCompleteDialog.kt
  • feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingViewModel.kt
  • feature/matching/src/main/java/com/puzzle/matching/graph/main/dialog/MatchingPoolEmptyDialog.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • feature/matching/src/main/java/com/puzzle/matching/graph/detail/dialog/TimerCompleteDialog.kt
🧰 Additional context used
🧬 Code graph analysis (2)
feature/matching/src/main/java/com/puzzle/matching/graph/main/dialog/MatchingPoolEmptyDialog.kt (2)
core/designsystem/src/main/java/com/puzzle/designsystem/component/Dialog.kt (3)
  • PieceDialog (33-58)
  • PieceDialogIconTop (164-214)
  • PieceDialogOneButtonBottom (243-260)
core/designsystem/src/main/java/com/puzzle/designsystem/foundation/Theme.kt (1)
  • PieceTheme (15-18)
feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingViewModel.kt (6)
core/common/src/main/java/com/puzzle/common/ResultUtil.kt (1)
  • suspendRunCatching (5-13)
core/data/src/main/java/com/puzzle/data/repository/MatchingRepositoryImpl.kt (1)
  • checkMatchingPiece (79-80)
core/domain/src/main/java/com/puzzle/domain/repository/MatchingRepository.kt (1)
  • checkMatchingPiece (19-19)
core/network/src/main/java/com/puzzle/network/source/matching/MatchingDataSource.kt (1)
  • checkMatchingPiece (26-26)
core/network/src/main/java/com/puzzle/network/source/matching/MatchingDataSourceImpl.kt (1)
  • checkMatchingPiece (61-61)
core/common-ui/src/main/java/com/puzzle/common/base/BaseViewModel.kt (1)
  • setState (39-40)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (5)
feature/matching/src/main/java/com/puzzle/matching/graph/main/dialog/MatchingPoolEmptyDialog.kt (1)

34-34: Verify the no-op dismiss behavior is intentional.

The dialog cannot be dismissed by tapping outside since onDismissRequest is a no-op. This forces users to click the button, which may be the intended UX to ensure they acknowledge the empty pool state.

Confirm this is the desired behavior. If users should be able to dismiss the dialog by tapping outside, update this to hide the dialog:

🔎 Proposed fix if dismissal should be allowed
-        onDismissRequest = {}
+        onDismissRequest = { onNextTryClick() }
feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingViewModel.kt (4)

230-247: LGTM! The isFree parameter fix resolves the previous unreachable code issue.

The refactored logic correctly handles both free and paid instant matches:

  • Free matches (line 231): Skip puzzle usage snackbar
  • Paid matches (line 252): Show puzzle usage snackbar before checking piece

The addition of checkMatchingPiece(matchId) (line 246) ensures proper piece validation before navigation, aligning with the PR objectives.


254-254: LGTM! MatchingPoolEmpty dialog handling is appropriate.

The dialog dismissal allows users to acknowledge the empty pool state and return to the matching screen.


270-277: LGTM! The checkMatchingPiece integration is well-structured.

The function properly:

  • Validates the matching piece via repository
  • Navigates to detail screen on success
  • Preserves card expansion state with openedMatchId
  • Delegates error handling appropriately

321-346: LGTM! Enhanced error handling with proper context propagation.

The improvements include:

  • Line 326: Puzzle count synchronization before showing InSufficient dialog (as per PR objectives)
  • Lines 330-332: Correct integration of MatchingPoolEmpty dialog for empty pool errors
  • Lines 334-338: Smart recovery for ALREADY_PURCHASED_CONTACT using matchId context

Note: fetchPuzzleCount() at line 326 is fire-and-forget, so the InSufficient dialog shows immediately while the count updates asynchronously. This is acceptable since the dialog doesn't directly display the puzzle count in its content.

* [PC-1537] analyticsHelper 미구현 screen 로깅 추가 및 최상단이 아닌 컴포넌트단으로 위치 이동

* [PC-1537] MatchingDetailScreen에 entryRoute 추가

* [PC-1537] entryRoute -> entryRouteForLog 로 파라미터명 수정

* [PC-1537] entryRoute -> entryRouteForLog 로 파라미터명 수정

* [PC-1537] AmplitudeEvent를 track, revenue 타입으로 변경

* [PC-1537] billing 모듈에 analytics 의존성 추가

* [PC-1537] trackRevenue 추가
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (1)
feature/matching/src/main/java/com/puzzle/matching/graph/detail/MatchingDetailScreen.kt (1)

147-199: Inconsistent screen name for BasicInfoPage analytics.

At Line 137, BasicInfoPage maps to "match_detail_basic_profile", but at Line 378-379 in MatchingDetailBottomBar, the same page uses "match_detail_basic_info" for the photo_view click event. This inconsistency will cause analytics fragmentation.

🔎 Proposed fix
                     onShowPicturesClick()
                     analyticsHelper.trackClickEvent(
-                        screenName = "match_detail_basic_info",
+                        screenName = "match_detail_basic_profile",
                         buttonName = "photo_view"
                     )
🧹 Nitpick comments (5)
feature/matching/src/main/java/com/puzzle/matching/graph/detail/dialog/AcceptMatchingDialog.kt (1)

45-48: Verify screen name consistency for analytics.

The click event uses screenName = "match_dialog" (line 46), while the screen view event uses screenName = "match_main_accept_popup" (line 55). This inconsistency might make it difficult to correlate click events with screen views in analytics.

Consider aligning the screen names or verify this is intentional.

feature/matching/src/main/java/com/puzzle/matching/graph/detail/dialog/ProfileImageDialog.kt (1)

28-31: Verify screen name consistency for analytics.

Similar to AcceptMatchingDialog, the click event uses screenName = "match_dialog" while the screen view event uses screenName = "match_detail_photo". Consider aligning these names for better analytics correlation.

debug/src/main/java/com/puzzle/debug/AnalyticsScreen.kt (1)

105-110: Consider displaying quantity for Revenue events.

The Revenue event has a quantity field that isn't displayed here. For a complete debug view, consider including it.

🔎 Proposed fix
                         is AnalyticsEvent.Revenue ->
                             Text(
-                                text = "${event.productId} : ${event.price}",
+                                text = "${event.productId} : ${event.price} x ${event.quantity}",
                                 style = PieceTheme.typography.bodySSB,
                                 color = PieceTheme.colors.dark2,
                             )
feature/matching/src/main/java/com/puzzle/matching/graph/detail/dialog/PurchaseMatchingDialog.kt (1)

80-82: Minor: Remove extra blank line.

There's an unnecessary blank line at Line 81 after the closing brace of PurchaseDialog.

🔎 Proposed fix
         onDismissRequest = onDismissRequest,
     )
-
 }
core/analytics/src/main/java/com/puzzle/analytics/AnalyticsEvent.kt (1)

5-8: Consider using immutable Map instead of MutableMap for properties.

Using MutableMap<String, Any?>? in a data class can lead to unintended mutations after creation. Prefer Map<String, Any?>? for the public API, and convert to mutable only where needed internally.

🔎 Proposed fix
     data class Track(
         val type: String,
-        val properties: MutableMap<String, Any?>? = null
+        val properties: Map<String, Any?>? = null
     ) : AnalyticsEvent {

This may require adjusting call sites that currently construct a mutableMapOf and pass it directly—they can still use mutableMapOf(...).toMap() or just pass the map as-is since MutableMap is a subtype of Map.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6a204b2 and cc7691c.

⛔ Files ignored due to path filters (2)
  • app/src/release/generated/baselineProfiles/baseline-prof.txt is excluded by !**/generated/**
  • app/src/release/generated/baselineProfiles/startup-prof.txt is excluded by !**/generated/**
📒 Files selected for processing (17)
  • core/analytics/src/main/java/com/puzzle/analytics/AmplitudeAnalyticsHelper.kt
  • core/analytics/src/main/java/com/puzzle/analytics/AnalyticsEvent.kt
  • core/analytics/src/main/java/com/puzzle/analytics/AnalyticsHelper.kt
  • core/billing/build.gradle.kts
  • core/billing/src/main/java/com/puzzle/billing/data/BillingHelperImpl.kt
  • debug/src/main/java/com/puzzle/debug/AnalyticsScreen.kt
  • feature/matching/src/main/java/com/puzzle/matching/graph/contact/ContactScreen.kt
  • feature/matching/src/main/java/com/puzzle/matching/graph/detail/MatchingDetailScreen.kt
  • feature/matching/src/main/java/com/puzzle/matching/graph/detail/dialog/AcceptMatchingDialog.kt
  • feature/matching/src/main/java/com/puzzle/matching/graph/detail/dialog/ProfileImageDialog.kt
  • feature/matching/src/main/java/com/puzzle/matching/graph/detail/dialog/PurchaseMatchingDialog.kt
  • feature/matching/src/main/java/com/puzzle/matching/graph/detail/dialog/RefuseMatchingDialog.kt
  • feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingScreen.kt
  • feature/matching/src/main/java/com/puzzle/matching/graph/main/component/card/MatchingCard.kt
  • feature/store/src/main/java/com/puzzle/store/StoreScreen.kt
  • feature/store/src/main/java/com/puzzle/store/ui/list/NormalProductList.kt
  • feature/store/src/main/java/com/puzzle/store/ui/list/PromotionProductList.kt
🧰 Additional context used
🧬 Code graph analysis (4)
feature/store/src/main/java/com/puzzle/store/StoreScreen.kt (1)
core/analytics/src/main/java/com/puzzle/analytics/AnalyticsHelper.kt (1)
  • TrackScreenViewEvent (86-109)
core/analytics/src/main/java/com/puzzle/analytics/AnalyticsHelper.kt (2)
core/analytics/src/main/java/com/puzzle/analytics/AmplitudeAnalyticsHelper.kt (1)
  • logEvent (11-16)
core/analytics/src/main/java/com/puzzle/analytics/DebugAnalyticsHelper.kt (1)
  • logEvent (17-21)
feature/matching/src/main/java/com/puzzle/matching/graph/detail/dialog/RefuseMatchingDialog.kt (1)
core/designsystem/src/main/java/com/puzzle/designsystem/component/Dialog.kt (5)
  • PieceDialog (33-58)
  • PieceDialogDefaultTop (60-84)
  • PieceDialogDefaultTop (86-110)
  • PieceDialogDefaultTop (112-136)
  • PieceDialogDefaultTop (138-162)
feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingScreen.kt (3)
feature/matching/src/main/java/com/puzzle/matching/graph/detail/dialog/PurchaseMatchingDialog.kt (2)
  • PurchaseDialog (19-82)
  • InsufficientPuzzleDialog (84-119)
feature/matching/src/main/java/com/puzzle/matching/graph/main/dialog/MatchingPoolEmptyDialog.kt (1)
  • MatchingPoolEmptyDialog (14-36)
core/analytics/src/main/java/com/puzzle/analytics/AnalyticsHelper.kt (1)
  • TrackScreenViewEvent (86-109)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (15)
core/billing/build.gradle.kts (1)

14-14: LGTM! The analytics dependency addition is necessary to support the revenue tracking integration in BillingHelperImpl. The implementation scope is appropriate for internal usage, and no circular dependency exists between core/billing and core/analytics.

feature/store/src/main/java/com/puzzle/store/StoreScreen.kt (1)

103-106: LGTM!

The screen view tracking is correctly placed and uses key = Unit to ensure it fires once per screen instance. The implementation aligns with the analytics pattern established in the codebase.

feature/matching/src/main/java/com/puzzle/matching/graph/contact/ContactScreen.kt (1)

137-142: LGTM!

The analytics tracking for profile image clicks is correctly implemented. The event is tracked immediately when the image is clicked, and the naming convention is clear and consistent.

feature/store/src/main/java/com/puzzle/store/ui/list/PromotionProductList.kt (1)

37-44: LGTM - Analytics tracking correctly integrated.

The click handler properly invokes the purchase callback first, then tracks the analytics event. The pattern is consistent with other analytics integrations in this PR.

feature/matching/src/main/java/com/puzzle/matching/graph/detail/dialog/RefuseMatchingDialog.kt (1)

23-56: LGTM - Analytics instrumentation properly integrated.

The analytics helper is correctly obtained from composition local, click events are tracked after the action callback, and screen view is tracked appropriately. The dialog title composition update with the suffix string resource is properly structured.

core/analytics/src/main/java/com/puzzle/analytics/AnalyticsHelper.kt (2)

60-72: LGTM - New revenue tracking method well-structured.

The trackRevenueEvent method is clean with sensible defaults (quantity = 1). It properly creates a Revenue event and delegates to logEvent for dispatch.


102-108: LGTM - Event construction migrated to type-safe Track variant.

The migration from generic AnalyticsEvent to AnalyticsEvent.Track is consistent throughout the file and aligns with the new sealed interface design.

feature/matching/src/main/java/com/puzzle/matching/graph/main/component/card/MatchingCard.kt (2)

140-154: LGTM - Dynamic button naming for analytics.

The labeled block pattern with early return for unmatched states is a clean approach to conditionally track analytics only for known match status/type combinations.


159-170: Verify: Analytics tracking removed for collapsed card clicks.

The CollapsedContent onClick previously tracked a "collapsed_match_button" event but now only invokes onCardClick. Confirm this intentional removal aligns with analytics requirements.

feature/matching/src/main/java/com/puzzle/matching/graph/detail/MatchingDetailScreen.kt (1)

136-145: LGTM! Centralized screen tracking is a good pattern.

The screen-level TrackScreenViewEvent with a keyed currentPage ensures consistent analytics tracking across page transitions.

feature/matching/src/main/java/com/puzzle/matching/graph/detail/dialog/PurchaseMatchingDialog.kt (2)

36-41: LGTM! Button name mapping for analytics is clear and consistent.

The when expression cleanly maps each PurchaseType to its corresponding analytics button name.


84-119: Hardcoded screen name in TrackScreenViewEvent may conflict with entryRouteForLog.

Line 118 uses a hardcoded "match_main_accept_popup" for TrackScreenViewEvent, but this dialog can be opened from different screens (e.g., match_detail_basic_profile, match_detail_value_pick). The entryRouteForLog parameter is only used for button click tracking, not for the screen view event. Consider whether this is intentional or if the screen view should also reflect the entry route.

core/analytics/src/main/java/com/puzzle/analytics/AnalyticsEvent.kt (1)

24-28: LGTM! Revenue event structure is well-defined.

The Revenue data class with nullable productId, price, and quantity provides a clean model for revenue tracking events.

feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingScreen.kt (2)

178-180: LGTM! MatchingPoolEmpty dialog wiring is correct.

The new MatchingPoolEmpty branch correctly routes to MatchingPoolEmptyDialog and wires onNextTryClick to trigger the accept action, enabling retry flow from the pool-empty state.


139-144: LGTM! Centralized screenName computation.

The screenName mapping based on matchingStatus provides a single source of truth for analytics screen names used throughout the composable.

Comment on lines +27 to +31
private fun AnalyticsEvent.Revenue.toRevenue(): Revenue = Revenue().apply {
productId = this.productId
price = this.price
quantity = this.quantity
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: this shadowing causes self-assignment instead of mapping from event.

Inside the apply block, this refers to the new com.amplitude.android.events.Revenue instance being built, not the AnalyticsEvent.Revenue receiver. The assignments productId = this.productId result in self-assignment (no-op), leaving the Amplitude Revenue object with default/null values.

🔎 Proposed fix using qualified `this` reference
-    private fun AnalyticsEvent.Revenue.toRevenue(): Revenue = Revenue().apply {
-        productId = this.productId
-        price = this.price
-        quantity = this.quantity
-    }
+    private fun AnalyticsEvent.Revenue.toRevenue(): Revenue = Revenue().apply {
+        productId = this@toRevenue.productId
+        price = this@toRevenue.price
+        quantity = this@toRevenue.quantity
+    }
🤖 Prompt for AI Agents
In core/analytics/src/main/java/com/puzzle/analytics/AmplitudeAnalyticsHelper.kt
around lines 27-31, the apply block assigns fields from the newly created
Revenue instance (shadowing `this`) instead of the AnalyticsEvent.Revenue
receiver, resulting in no data being copied; fix by referencing the extension
receiver when assigning (e.g., use this@toRevenue.productId /
this@toRevenue.price / this@toRevenue.quantity) or capture the source in a local
val (val src = this) before apply and assign from src.productId, src.price,
src.quantity so the Amplitude Revenue object is populated correctly.

Comment on lines +116 to +121
if (verifyResult.rewardPuzzleCount > 0){
analyticsHelper.trackRevenueEvent(
productId = productId,
price = verifyResult.rewardPuzzleCount.toDouble(),
)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's understand the file structure and find the BillingHelperImpl.kt file
find . -name "BillingHelperImpl.kt" -type f 2>/dev/null | head -5

Repository: Piece-Puzzly/Piece-Android

Length of output: 143


🏁 Script executed:

# Look at the BillingHelperImpl.kt file around the flagged lines
git ls-files | grep -i billinghelperimpl

Repository: Piece-Puzzly/Piece-Android

Length of output: 141


🏁 Script executed:

# Search for trackRevenueEvent definition and usage
rg -n "trackRevenueEvent" --type=kotlin -B 2 -A 5

Repository: Piece-Puzzly/Piece-Android

Length of output: 1654


🏁 Script executed:

# Search for price-related information in the billing context
rg -n "priceAmountMicros|formattedPrice|price.*Double|monetaryPrice|purchasePrice" --type=kotlin -B 1 -A 1

Repository: Piece-Puzzly/Piece-Android

Length of output: 2383


🏁 Script executed:

# Read the BillingHelperImpl.kt file to understand the context around lines 116-121
cat -n core/billing/src/main/java/com/puzzle/billing/data/BillingHelperImpl.kt | sed -n '100,140p'

Repository: Piece-Puzzly/Piece-Android

Length of output: 1661


🏁 Script executed:

# Search for verifyResult definition and where it comes from
rg -n "verifyResult|VerifyResult" --type=kotlin -B 2 -A 2 | head -60

Repository: Piece-Puzzly/Piece-Android

Length of output: 1889


🏁 Script executed:

# Look for the function signature that contains this code
ast-grep --pattern 'fun $FUNC($$$verifyResult$$$) {
  $$$
  trackRevenueEvent($$$)
  $$$
}'

Repository: Piece-Puzzly/Piece-Android

Length of output: 52


🏁 Script executed:

# Check if product price information is accessible in the function
rg -n "fun.*purchase\|fun.*verify" --type=kotlin core/billing/src/main/java/com/puzzle/billing/data/BillingHelperImpl.kt -A 20 | head -80

Repository: Piece-Puzzly/Piece-Android

Length of output: 52


🏁 Script executed:

# Search for Purchase class definition and its properties
rg -n "class Purchase|data class Purchase" --type=kotlin -A 20

Repository: Piece-Puzzly/Piece-Android

Length of output: 2743


🏁 Script executed:

# Check what properties are available on the purchase object
rg -n "purchase\." --type=kotlin core/billing/src/main/java/com/puzzle/billing/data/BillingHelperImpl.kt | head -20

Repository: Piece-Puzzly/Piece-Android

Length of output: 281


🏁 Script executed:

# Look for PurchaseProduct or verifyResult structure
rg -n "data class.*VerifyResult|data class.*PurchaseProduct" --type=kotlin -A 10

Repository: Piece-Puzzly/Piece-Android

Length of output: 3965


🏁 Script executed:

# Search for how price information should be obtained from purchase details
rg -n "priceAmountMicros|originalPrice|originalJson" --type=kotlin -B 2 -A 2 | head -50

Repository: Piece-Puzzly/Piece-Android

Length of output: 1261


🏁 Script executed:

# Check what properties are available on Google Play's Purchase object
# Look for how Purchase properties are used in the file
rg -n "purchase\.[a-zA-Z]+" --type=kotlin core/billing/src/main/java/com/puzzle/billing/data/BillingHelperImpl.kt -o | sort | uniq

Repository: Piece-Puzzly/Piece-Android

Length of output: 144


🏁 Script executed:

# Search for Google Play Billing library imports and Purchase usage
rg -n "import.*Purchase|class Purchase|com\.android\.billingclient" --type=kotlin core/billing/src/main/java/com/puzzle/billing/data/BillingHelperImpl.kt

Repository: Piece-Puzzly/Piece-Android

Length of output: 824


🏁 Script executed:

# Check if there's documentation or usage of priceAmountMicros from Purchase object
rg -n "priceAmountMicros" --type=kotlin -B 3 -A 3 core/billing/src/main/java/com/puzzle/billing/data/BillingHelperImpl.kt

Repository: Piece-Puzzly/Piece-Android

Length of output: 540


🏁 Script executed:

# Verify what the trackRevenueEvent is actually meant to track in Amplitude
rg -n "trackRevenueEvent|Revenue" --type=kotlin core/analytics/src/main/java/com/puzzle/analytics/AnalyticsHelper.kt -B 2 -A 15 | head -40

Repository: Piece-Puzzly/Piece-Android

Length of output: 652


🏁 Script executed:

# Check all usages of trackRevenueEvent to understand the pattern
rg -n "trackRevenueEvent" --type=kotlin -B 5 -A 2

Repository: Piece-Puzzly/Piece-Android

Length of output: 1654


🏁 Script executed:

# Check if Purchase object has price information available
# Look at the full fetchPurchaseReward function to confirm what properties are accessible
cat -n core/billing/src/main/java/com/puzzle/billing/data/BillingHelperImpl.kt | sed -n '104,125p'

Repository: Piece-Puzzly/Piece-Android

Length of output: 945


🏁 Script executed:

# Search for how other parts of the codebase handle revenue tracking
rg -n "Revenue|revenue" --type=kotlin core/analytics/src/main/java/com/puzzle/analytics/ -B 1 -A 1

Repository: Piece-Puzzly/Piece-Android

Length of output: 2337


🏁 Script executed:

# Check if ProductDetails or pricing information is available in the context
# Look at how products are queried and if price is accessible
rg -n "queryProductDetails|ProductDetails" --type=kotlin core/billing/src/main/java/com/puzzle/billing/data/BillingHelperImpl.kt -B 2 -A 5

Repository: Piece-Puzzly/Piece-Android

Length of output: 2420


🏁 Script executed:

# Check the full Purchase flow to understand what information is available
cat -n core/billing/src/main/java/com/puzzle/billing/data/BillingHelperImpl.kt | sed -n '60,110p'

Repository: Piece-Puzzly/Piece-Android

Length of output: 2689


🏁 Script executed:

# Look for where purchases are processed and what data flows through
rg -n "purchasesUpdatedListener|handlePurchase" --type=kotlin core/billing/src/main/java/com/puzzle/billing/data/BillingHelperImpl.kt -B 2 -A 10

Repository: Piece-Puzzly/Piece-Android

Length of output: 1262


🏁 Script executed:

# Let's check the Google Play Purchase object to see if price information is available
rg -n "originalJson|priceAmountMicros|originalPrice" --type=kotlin -C 2

Repository: Piece-Puzzly/Piece-Android

Length of output: 1261


🏁 Script executed:

# Check the AnalyticsEvent.Revenue and Amplitude Revenue to understand what price field means
cat -n core/analytics/src/main/java/com/puzzle/analytics/AnalyticsEvent.kt | sed -n '20,35p'

Repository: Piece-Puzzly/Piece-Android

Length of output: 340


🏁 Script executed:

# Check AmplitudeAnalyticsHelper to see how revenue is processed
cat -n core/analytics/src/main/java/com/puzzle/analytics/AmplitudeAnalyticsHelper.kt | sed -n '1,35p'

Repository: Piece-Puzzly/Piece-Android

Length of output: 1304


Revenue tracking uses puzzle count instead of actual monetary price.

The trackRevenueEvent is designed to track monetary revenue, but price is set to rewardPuzzleCount.toDouble() (puzzle count), not the actual purchase price. This will result in incorrect revenue analytics in Amplitude.

Extract the actual monetary price from the purchase object (available via Google Play Billing Library) or pass product price information to this function instead.

🤖 Prompt for AI Agents
In core/billing/src/main/java/com/puzzle/billing/data/BillingHelperImpl.kt
around lines 116-121, the code currently passes
verifyResult.rewardPuzzleCount.toDouble() as the monetary price to
analyticsHelper.trackRevenueEvent; replace this with the actual monetary price
from the purchase/product metadata: extract the price (and currency if required)
from the Google Play Billing purchase or the ProductDetails provided by the
billing response (or add a price parameter to this method and propagate it from
the caller), convert that localized price/amount to a numeric monetary value,
and pass that numeric price into trackRevenueEvent instead of
rewardPuzzleCount.toDouble().

Comment on lines +146 to +155
val onNewMatchingCardClick = {
onNewMatchingCardClick()

val buttonName = if (state.canFreeMatch) "new_matching_free" else "new_matching_purchase"

analyticsHelper.trackClickEvent(
screenName = screenName,
buttonName = buttonName,
)
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Avoid shadowing the parameter with a local variable of the same name.

The local val onNewMatchingCardClick shadows the function parameter onNewMatchingCardClick: () -> Unit, which is confusing and error-prone. Rename the local lambda to something distinct like handleNewMatchingCardClick or onNewMatchingCardClickWithAnalytics.

🔎 Proposed fix
-    val onNewMatchingCardClick = {
+    val handleNewMatchingCardClick = {
         onNewMatchingCardClick()

         val buttonName = if (state.canFreeMatch) "new_matching_free" else "new_matching_purchase"

         analyticsHelper.trackClickEvent(
             screenName = screenName,
             buttonName = buttonName,
         )
     }

Then update usages at lines 245 and 255:

-                onNewMatchingCardClick = onNewMatchingCardClick,
+                onNewMatchingCardClick = handleNewMatchingCardClick,

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

🤖 Prompt for AI Agents
In
feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingScreen.kt
around lines 146 to 155, the local val onNewMatchingCardClick shadows the
function parameter of the same name; rename the local lambda to a distinct
identifier (e.g., handleNewMatchingCardClick or
onNewMatchingCardClickWithAnalytics), keep its body calling the original
parameter and performing analytics, and update all references/usages (notably at
lines 245 and 255) to use the new local name.

Comment on lines +41 to +44
analyticsHelper.trackClickEvent(
screenName = "store_main",
buttonName = "store_main_banner_${product.name}_${product.price}",
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check analytics button naming in PromotionProductList
rg -n -A2 -B2 "trackClickEvent" feature/store/src/main/java/com/puzzle/store/ui/list/PromotionProductList.kt

Repository: Piece-Puzzly/Piece-Android

Length of output: 308


🏁 Script executed:

#!/bin/bash
# Also check the full context in NormalProductList around the analytics call
rg -n -A5 -B5 "trackClickEvent" feature/store/src/main/java/com/puzzle/store/ui/list/NormalProductList.kt

Repository: Piece-Puzzly/Piece-Android

Length of output: 547


🏁 Script executed:

#!/bin/bash
# Search for all trackClickEvent calls to understand naming patterns
rg -n "trackClickEvent" feature/store/src/main/java/com/puzzle/store/ui/list/

Repository: Piece-Puzzly/Piece-Android

Length of output: 336


🏁 Script executed:

#!/bin/bash
# Examine NormalProductList and PromotionProductList to understand the distinction
head -50 feature/store/src/main/java/com/puzzle/store/ui/list/NormalProductList.kt

Repository: Piece-Puzzly/Piece-Android

Length of output: 1779


🏁 Script executed:

#!/bin/bash
# Check PromotionProductList context
head -50 feature/store/src/main/java/com/puzzle/store/ui/list/PromotionProductList.kt

Repository: Piece-Puzzly/Piece-Android

Length of output: 1721


🏁 Script executed:

#!/bin/bash
# Search for analytics naming conventions or documentation
rg -i "analytics|button.*name|naming.*convention" feature/store/ -l | head -20

Repository: Piece-Puzzly/Piece-Android

Length of output: 280


Reconsider button name semantics for NormalProductList analytics.

The button name uses "store_main_banner_..." but NormalProductList renders card components, not banners. Compare with PromotionProductList, which also uses "store_main_banner" for actual image banners—this creates semantic confusion. Consider renaming to "store_main_product_..." to accurately reflect the component type and align with analytics naming clarity.

Additionally, embedding product.name and product.price directly into the button name may cause inconsistencies if these values contain special characters or whitespace, whereas PromotionProductList uses static naming. Align the naming strategy across both list types.

🤖 Prompt for AI Agents
In feature/store/src/main/java/com/puzzle/store/ui/list/NormalProductList.kt
around lines 41 to 44, the analytics buttonName currently uses
"store_main_banner_${product.name}_${product.price}" which is semantically
incorrect for a product card and embeds raw product.name/product.price; change
the buttonName to use a product-specific but stable identifier and correct
component type (for example "store_main_product_${product.id}" or simply
"store_main_product" if no id available) to match PromotionProductList naming
conventions, and stop interpolating raw name/price (or sanitize them) to avoid
special-character/whitespace issues.

* [PC-1534] 매칭 풀 다이얼로그(기본) show/hide 로직 구현

* [PC-1534] 매칭 풀 다이얼로그 show/hide 로직 수정

* [PC-1534] showPurchaseDialog -> showDialog

* [PC-1534] MatchingPoolType -> MatchingPoolEmptyType

* [PC-1534] isModalShownToday -> isBasicMatchingPoolEmptyDialogShownToday

* [PC-1534] getStandardDate -> getResetDateString

* [PC-1534] getResetDateString 함수 테스트 코드 작성

* [PC-1534] LastModal -> LastMatchingPoolEmptyDialog

* [PC-1534] 로그아웃, 탈퇴 시 매칭 풀 소진 다이얼로그 clear

* [PC-1534] LastModal -> LastMatchingPoolEmptyDialog

* [PC-1534] LastModal -> LastMatchingPoolEmptyDialog

* [PC-1534] 로그인, 로그아웃 테스트 코드에 lastMatchingPoolEmptyDialogShownDate 빈 값 확인 코드 추가

* [PC-1534] 매칭 풀 소진 다이얼로그(기본) show/hide 테스트 코드 추가

* [PC-1534] 매칭 풀 소진 다이얼로그(기본) subText 수정

* [PC-1534] 매칭 풀 소진 다이얼로그 표출 확인 테스트 코드 작성

* [PC-1534] 매칭 풀 소진 다이얼로그 본 날짜 토큰 만료시 제거하도록 변경

* [PC-1534] 맛보기 매칭 풀 소진일 때는 Basic 매칭 풀 소진 표출되도록 변경

* [PC-1534] lastMatchingPoolEmptyDialogShownDate 타입 non-null로 변경
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

♻️ Duplicate comments (1)
feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingScreen.kt (1)

146-155: Parameter shadowing still present.

The local val onNewMatchingCardClick shadows the function parameter of the same name. This was flagged in a previous review and should be renamed to something distinct like handleNewMatchingCardClick.

🔎 Proposed fix
-    val onNewMatchingCardClick = {
-        onNewMatchingCardClick()
+    val handleNewMatchingCardClick = {
+        onNewMatchingCardClick()

         val buttonName = if (state.canFreeMatch) "new_matching_free" else "new_matching_purchase"

         analyticsHelper.trackClickEvent(
             screenName = screenName,
             buttonName = buttonName,
         )
     }

Then update usages at lines 252 and 262:

-                onNewMatchingCardClick = onNewMatchingCardClick,
+                onNewMatchingCardClick = handleNewMatchingCardClick,
🧹 Nitpick comments (8)
feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingViewModel.kt (1)

356-381: Consider awaiting fetchPuzzleCount() before showing the dialog.

In NOT_ENOUGH_PUZZLE handling, fetchPuzzleCount() launches a coroutine but the dialog is shown immediately after. If the dialog displays the puzzle count, users might briefly see stale data.

🔎 Proposed fix
 PieceErrorCode.NOT_ENOUGH_PUZZLE -> {
-    fetchPuzzleCount()
+    fetchPuzzleCount().join()
     showDialog(MatchingDialogType.InSufficient)
 }

Alternatively, if the dialog doesn't depend on the updated count, document this as intentional fire-and-forget behavior.

core/data/src/test/java/com/puzzle/data/repository/AuthRepositoryImplTest.kt (1)

70-70: LGTM! Consider adding positive test cases for the setter.

The assertions correctly verify that the dialog date is cleared during logout and withdrawal flows.

Consider adding test cases that verify setLastMatchingPoolEmptyDialogShownDate actually persists the date value before clearing. This would provide more comprehensive coverage of the dialog date lifecycle.

Also applies to: 85-85

core/data/src/main/java/com/puzzle/data/repository/AuthRepositoryImpl.kt (1)

47-57: Consider aligning job join order with launch order for clarity.

The implementation correctly clears the dialog date during logout and withdrawal. The parallel launch with sequential join pattern is appropriate.

For consistency and maintainability, consider joining jobs in the same order they're launched:

🔎 Proposed refactor for consistent ordering
     override suspend fun logout() {
         coroutineScope {
             val clearUserRoleJob = launch { localUserDataSource.clearUserRole() }
             val clearLastMatchingPoolEmptyDialogShownDateJob = launch { localUserDataSource.clearLastMatchingPoolEmptyDialogShownDate() }
             val clearTokenJob = launch { localTokenDataSource.clearToken() }
 
-            clearTokenJob.join()
+            clearUserRoleJob.join()
             clearLastMatchingPoolEmptyDialogShownDateJob.join()
-            clearUserRoleJob.join()
+            clearTokenJob.join()
         }
     }

Apply the same pattern to withdraw() for consistency.

Also applies to: 59-71

core/datastore/src/main/java/com/puzzle/datastore/datasource/user/LocalUserDataSource.kt (1)

7-7: Consider documenting the expected date string format.

The API surface is clean and follows the existing pattern for userRole.

Since dates are stored as strings, consider adding KDoc to specify the expected format (e.g., "yyyy-MM-dd" or ISO 8601). This would prevent inconsistent formatting across the codebase.

Example:

/**
 * Flow of the last date the matching pool empty dialog was shown.
 * Format: yyyy-MM-dd (e.g., "2025-12-28")
 */
val lastMatchingPoolEmptyDialogShownDate: Flow<String>

/**
 * Sets the last date the matching pool empty dialog was shown.
 * @param dateString Date in yyyy-MM-dd format
 */
suspend fun setLastMatchingPoolEmptyDialogShownDate(dateString: String)

Also applies to: 10-11

core/datastore/src/main/java/com/puzzle/datastore/datasource/user/LocalUserDataSourceImpl.kt (1)

37-37: Consider renaming the DataStore key to match the semantic purpose.

The key name LAST_MODAL_SHOWN_DATE is generic and doesn't reflect that it's specifically for the "matching pool empty dialog." Consider renaming to LAST_MATCHING_POOL_EMPTY_DIALOG_SHOWN_DATE for clarity.

🔎 Proposed naming improvement
     companion object {
         private val USER_ROLE = stringPreferencesKey("USER_ROLE")
-        private val LAST_MODAL_SHOWN_DATE = stringPreferencesKey("LAST_MODAL_SHOWN_DATE")
+        private val LAST_MATCHING_POOL_EMPTY_DIALOG_SHOWN_DATE = stringPreferencesKey("LAST_MATCHING_POOL_EMPTY_DIALOG_SHOWN_DATE")
     }

And update references on lines 17, 28, and 32.

Note: Changing the key name will reset any existing stored values, so coordinate this with your migration strategy if users already have this data persisted.

core/testing/src/main/java/com/puzzle/testing/domain/repository/FakeUserRepository.kt (1)

71-79: Consider using MutableStateFlow for reactive state in test fake.

The current implementation uses flowOf(), which emits the current value once and completes. If a test collects the flow and then calls setLastMatchingPoolEmptyDialogShownDate(), the collector won't receive the updated value.

For a more robust test fake that supports reactive updates:

🔎 Proposed refactor using MutableStateFlow
-    private var lastMatchingPoolEmptyDialogShownDate: String = ""
+    private val lastMatchingPoolEmptyDialogShownDateFlow = MutableStateFlow("")
-    override fun getLastMatchingPoolEmptyDialogShownDate(): Flow<String> = flowOf(lastMatchingPoolEmptyDialogShownDate)
+    override fun getLastMatchingPoolEmptyDialogShownDate(): Flow<String> = lastMatchingPoolEmptyDialogShownDateFlow

     override suspend fun setLastMatchingPoolEmptyDialogShownDate(dateString: String) {
-        this.lastMatchingPoolEmptyDialogShownDate = dateString
+        lastMatchingPoolEmptyDialogShownDateFlow.value = dateString
     }

     override suspend fun clearLastMatchingPoolEmptyDialogShownDate() {
-        this.lastMatchingPoolEmptyDialogShownDate = ""
+        lastMatchingPoolEmptyDialogShownDateFlow.value = ""
     }
core/data/src/test/java/com/puzzle/data/source/user/FakeLocalUserDataSource.kt (2)

12-12: Naming inconsistency: currentLastModalShownDate vs MatchingPoolEmptyDialog.

The private field uses the generic term "Modal" while the public property is specifically for "MatchingPoolEmptyDialog". Consider renaming to currentLastMatchingPoolEmptyDialogShownDate for consistency and clarity.

🔎 Proposed naming fix
-    private var currentLastModalShownDate = ""
+    private var currentLastMatchingPoolEmptyDialogShownDate = ""
     override val lastMatchingPoolEmptyDialogShownDate: Flow<String> get() = flowOf(currentLastMatchingPoolEmptyDialogShownDate)

Update lines 24 and 28 accordingly:

     override suspend fun setLastMatchingPoolEmptyDialogShownDate(dateString: String) {
-        currentLastModalShownDate = dateString
+        currentLastMatchingPoolEmptyDialogShownDate = dateString
     }

     override suspend fun clearLastMatchingPoolEmptyDialogShownDate() {
-        currentLastModalShownDate = ""
+        currentLastMatchingPoolEmptyDialogShownDate = ""
     }

13-13: Consider using MutableStateFlow for reactive state.

Similar to other test fakes in this PR, using flowOf() creates a cold flow that emits once and completes. This won't notify collectors of subsequent state changes, which could affect tests that rely on observing updates.

🔎 Proposed refactor using MutableStateFlow
-    private var currentLastModalShownDate = ""
-    override val lastMatchingPoolEmptyDialogShownDate: Flow<String> get() = flowOf(currentLastModalShownDate)
+    private val _lastMatchingPoolEmptyDialogShownDate = MutableStateFlow("")
+    override val lastMatchingPoolEmptyDialogShownDate: Flow<String> get() = _lastMatchingPoolEmptyDialogShownDate
     override suspend fun setLastMatchingPoolEmptyDialogShownDate(dateString: String) {
-        currentLastModalShownDate = dateString
+        _lastMatchingPoolEmptyDialogShownDate.value = dateString
     }

     override suspend fun clearLastMatchingPoolEmptyDialogShownDate() {
-        currentLastModalShownDate = ""
+        _lastMatchingPoolEmptyDialogShownDate.value = ""
     }

Also applies to: 23-29

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cc7691c and 3ad2ad4.

📒 Files selected for processing (19)
  • core/common/src/main/java/com/puzzle/common/TimeUtil.kt
  • core/common/src/test/kotlin/com/puzzle/common/TimeUtilTest.kt
  • core/data/src/main/java/com/puzzle/data/repository/AuthRepositoryImpl.kt
  • core/data/src/main/java/com/puzzle/data/repository/MatchingRepositoryImpl.kt
  • core/data/src/main/java/com/puzzle/data/repository/UserRepositoryImpl.kt
  • core/data/src/test/java/com/puzzle/data/repository/AuthRepositoryImplTest.kt
  • core/data/src/test/java/com/puzzle/data/source/user/FakeLocalUserDataSource.kt
  • core/datastore/src/main/java/com/puzzle/datastore/datasource/user/LocalUserDataSource.kt
  • core/datastore/src/main/java/com/puzzle/datastore/datasource/user/LocalUserDataSourceImpl.kt
  • core/designsystem/src/main/res/values/strings.xml
  • core/domain/src/main/java/com/puzzle/domain/repository/UserRepository.kt
  • core/testing/src/main/java/com/puzzle/testing/domain/repository/FakeUserRepository.kt
  • feature/matching/src/androidTest/java/com/puzzle/matching/graph/main/MatchingScreenTest.kt
  • feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingScreen.kt
  • feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingViewModel.kt
  • feature/matching/src/main/java/com/puzzle/matching/graph/main/constant/MatchingDialogType.kt
  • feature/matching/src/main/java/com/puzzle/matching/graph/main/contract/MatchingState.kt
  • feature/matching/src/main/java/com/puzzle/matching/graph/main/dialog/MatchingPoolEmptyDialog.kt
  • presentation/src/main/java/com/puzzle/presentation/MainViewModel.kt
💤 Files with no reviewable changes (1)
  • core/data/src/main/java/com/puzzle/data/repository/MatchingRepositoryImpl.kt
🚧 Files skipped from review as they are similar to previous changes (3)
  • feature/matching/src/androidTest/java/com/puzzle/matching/graph/main/MatchingScreenTest.kt
  • core/designsystem/src/main/res/values/strings.xml
  • feature/matching/src/main/java/com/puzzle/matching/graph/main/constant/MatchingDialogType.kt
🧰 Additional context used
🧬 Code graph analysis (3)
core/common/src/test/kotlin/com/puzzle/common/TimeUtilTest.kt (1)
core/common/src/main/java/com/puzzle/common/TimeUtil.kt (1)
  • getResetDateString (112-121)
feature/matching/src/main/java/com/puzzle/matching/graph/main/dialog/MatchingPoolEmptyDialog.kt (2)
core/designsystem/src/main/java/com/puzzle/designsystem/component/Dialog.kt (3)
  • PieceDialog (33-58)
  • PieceDialogIconTop (164-214)
  • PieceDialogOneButtonBottom (243-260)
core/designsystem/src/main/java/com/puzzle/designsystem/foundation/Theme.kt (1)
  • PieceTheme (15-18)
feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingViewModel.kt (7)
core/common-ui/src/main/java/com/puzzle/common/base/BaseViewModel.kt (1)
  • setState (39-40)
core/data/src/main/java/com/puzzle/data/repository/MatchingRepositoryImpl.kt (1)
  • checkMatchingPiece (78-79)
core/domain/src/main/java/com/puzzle/domain/repository/MatchingRepository.kt (1)
  • checkMatchingPiece (19-19)
core/network/src/main/java/com/puzzle/network/source/matching/MatchingDataSourceImpl.kt (1)
  • checkMatchingPiece (61-61)
core/network/src/main/java/com/puzzle/network/api/PieceApi.kt (1)
  • checkMatchingPiece (168-169)
core/testing/src/main/java/com/puzzle/testing/domain/repository/SpyMatchingRepository.kt (1)
  • checkMatchingPiece (77-77)
core/common/src/main/java/com/puzzle/common/TimeUtil.kt (1)
  • getResetDateString (112-121)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (15)
core/common/src/test/kotlin/com/puzzle/common/TimeUtilTest.kt (2)

161-175: LGTM! Test correctly validates pre-reset behavior.

The test properly verifies that when the current hour is before the daily reset hour (22:00), the function returns the previous day's date. Using a fixed clock ensures deterministic test results.


177-191: LGTM! Test correctly validates at-reset-hour behavior.

The test properly verifies the boundary condition where the hour equals the reset hour (22:00). The implementation uses strict less-than comparison, so at exactly 22:00, the current day is returned as expected.

core/common/src/main/java/com/puzzle/common/TimeUtil.kt (3)

3-3: LGTM! Import necessary for testable time-based logic.

The Clock import enables dependency injection of time for the new getResetDateString function, which is a best practice for testing time-dependent behavior.


111-121: LGTM! Clean implementation with good testability.

The function correctly implements reset date logic using a 22:00 cutoff. Key strengths:

  • Clock injection enables deterministic testing
  • Hour-based comparison provides clear boundary semantics
  • Thread-safe and stateless
  • Returns standard ISO date format

The implementation only checks the hour field (not minutes/seconds), so 21:59:59 and 21:00:00 both return the previous day, while 22:00:00 and 23:59:59 both return the current day. This simplification is appropriate for daily reset logic.


123-123: LGTM! Constant appropriately named and scoped.

The DAILY_RESET_HOUR constant is well-defined at 22:00 (10 PM KST), making the reset logic configurable and clear. Top-level placement enables usage in both production code and tests.

feature/matching/src/main/java/com/puzzle/matching/graph/main/dialog/MatchingPoolEmptyDialog.kt (1)

15-44: LGTM!

The dialog implementation correctly uses destructuring for subText and label based on matchingPoolEmptyType, and properly wires the PieceDialog components. The empty onDismissRequest enforces user interaction through the button.

feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingViewModel.kt (4)

77-79: LGTM!

Good initialization pattern to check the daily dialog status on ViewModel creation, ensuring the state is ready before any user interactions.


258-281: LGTM!

The getInstantNewMatch function now correctly handles the isFree parameter and shows appropriate dialogs based on the error type. The snackbar only shows when a puzzle is consumed (!isFree), which addresses the previous review concern about the unreachable condition.


392-401: Defensive handling on failure is good.

Setting isBasicMatchingPoolEmptyDialogShownToday = true on failure is a safe default that prevents dialog spam if the data source fails. The .first() usage is acceptable here since the flow from DataStore should always emit at least one value.


403-416: LGTM!

The handleMatchingPoolEmptyDialog function correctly gates showing the dialog based on both the daily check and the presence of BASIC matches in the source list. The flag management ensures this check runs only once per fetch cycle.

feature/matching/src/main/java/com/puzzle/matching/graph/main/contract/MatchingState.kt (1)

28-28: Default value prevents premature dialog display.

Setting isBasicMatchingPoolEmptyDialogShownToday = true as default is a good defensive choice—it prevents the dialog from flashing before the actual check from DataStore completes.

feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingScreen.kt (2)

178-187: LGTM!

The new MatchingPoolEmpty dialog branch correctly wires the MatchingPoolEmptyDialog component with the appropriate type and analytics tracking. The onNextTryClick callback properly delegates to onAcceptClick with the current dialog type.


139-144: Good analytics mapping.

The screenName computation provides clear, descriptive screen names for each matching status, improving analytics clarity.

core/datastore/src/main/java/com/puzzle/datastore/datasource/user/LocalUserDataSourceImpl.kt (1)

17-17: LGTM! Implementation follows established patterns.

The implementation correctly uses DataStore utilities (getValue, setValue, clear) and follows the same pattern as the existing userRole implementation.

Also applies to: 27-33

core/domain/src/main/java/com/puzzle/domain/repository/UserRepository.kt (1)

21-23: LGTM! Clean domain API for dialog date persistence.

The new methods provide a clear interface for managing the matching pool empty dialog state and follow the repository pattern consistently.

Comment on lines +34 to +43
override fun getLastMatchingPoolEmptyDialogShownDate(): Flow<String> =
localUserDataSource.lastMatchingPoolEmptyDialogShownDate

override suspend fun setLastMatchingPoolEmptyDialogShownDate(dateString: String) {
localUserDataSource.setLastMatchingPoolEmptyDialogShownDate(dateString)
}

override suspend fun clearLastMatchingPoolEmptyDialogShownDate() {
localUserDataSource.clearLastMatchingPoolEmptyDialogShownDate()
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "UserRepositoryImpl.kt" | head -20

Repository: Piece-Puzzly/Piece-Android

Length of output: 144


🏁 Script executed:

git ls-files | grep -E "(UserRepository|DataSource)" | grep -i kotlin

Repository: Piece-Puzzly/Piece-Android

Length of output: 52


🏁 Script executed:

rg -nP --type=kotlin 'getResetDateString|lastMatchingPoolEmptyDialogShownDate' -B2 -A5

Repository: Piece-Puzzly/Piece-Android

Length of output: 14580


🏁 Script executed:

rg -nP --type=kotlin 'DateTimeFormatter|SimpleDateFormat|LocalDate|Instant' -C2 | head -100

Repository: Piece-Puzzly/Piece-Android

Length of output: 10370


🏁 Script executed:

cat -n core/common/src/main/java/com/puzzle/common/TimeUtil.kt | sed -n '112,130p'

Repository: Piece-Puzzly/Piece-Android

Length of output: 670


🏁 Script executed:

rg -nP --type=kotlin 'fun setLastMatchingPoolEmptyDialogShownDate|fun clearLastMatchingPoolEmptyDialogShownDate' -A10 core/datastore

Repository: Piece-Puzzly/Piece-Android

Length of output: 2202


🏁 Script executed:

cat -n core/data/src/main/java/com/puzzle/data/repository/UserRepositoryImpl.kt | sed -n '1,50p'

Repository: Piece-Puzzly/Piece-Android

Length of output: 2389


🏁 Script executed:

rg -nP --type=kotlin 'setLastMatchingPoolEmptyDialogShownDate' --no-heading

Repository: Piece-Puzzly/Piece-Android

Length of output: 1403


Consider documenting the expected date format.

The date format is standardized to ISO-8601 (yyyy-MM-dd) via getResetDateString(), which uses DateTimeFormatter.ISO_LOCAL_DATE. While this ensures consistency for current usages (all code paths call getResetDateString() before storing), adding a KDoc comment documenting the expected format would clarify the contract for future maintainers and prevent accidental misuse of the public setter.

Optional: Add format validation in the setter if the API needs to be more defensive.

🤖 Prompt for AI Agents
In core/data/src/main/java/com/puzzle/data/repository/UserRepositoryImpl.kt
around lines 34-43, the public setter/getter for
lastMatchingPoolEmptyDialogShownDate lacks documentation of the expected date
string format; add a concise KDoc above the property/each public method stating
the expected ISO-8601 date format ("yyyy-MM-dd",
DateTimeFormatter.ISO_LOCAL_DATE) so callers know the contract, and optionally
add defensive validation in setLastMatchingPoolEmptyDialogShownDate to parse the
input with LocalDate.parse(..., DateTimeFormatter.ISO_LOCAL_DATE) and throw an
IllegalArgumentException (or return/fail gracefully) on parse failure.

Comment on lines +50 to +53
val shouldShowSpecialModal: Boolean =
userRole == UserRole.USER &&
(matchInfoList.isEmpty() || matchInfoList.none { it.matchType == MatchType.BASIC }) &&
!isBasicMatchingPoolEmptyDialogShownToday
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

rg -n "shouldShowSpecialModal" --type kotlin -C 2

Repository: Piece-Puzzly/Piece-Android

Length of output: 713


Remove unused shouldShowSpecialModal computed property.

The property defined at lines 50-53 is never used anywhere in the codebase and should be removed to eliminate dead code.

🤖 Prompt for AI Agents
In
feature/matching/src/main/java/com/puzzle/matching/graph/main/contract/MatchingState.kt
around lines 50 to 53, remove the unused computed property
shouldShowSpecialModal; delete its declaration and any associated imports or
references (none exist), and run a build/IDE clean to ensure no remaining
references or warnings remain.

Comment on lines +56 to +64
@Preview
@Composable
private fun PreviewNewMatchingPoolEmptyDialog() {
PieceTheme {
Column {
MatchingPoolEmptyDialog(MatchingPoolEmptyType.Basic,{})
}
}
} No newline at end of file
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Copy-paste bug: Preview for "New" type uses MatchingPoolEmptyType.Basic.

PreviewNewMatchingPoolEmptyDialog should use MatchingPoolEmptyType.New to properly preview the "New" variant.

🔎 Proposed fix
 @Preview
 @Composable
 private fun PreviewNewMatchingPoolEmptyDialog() {
     PieceTheme {
         Column {
-            MatchingPoolEmptyDialog(MatchingPoolEmptyType.Basic,{})
+            MatchingPoolEmptyDialog(MatchingPoolEmptyType.New) {}
         }
     }
 }
📝 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
@Preview
@Composable
private fun PreviewNewMatchingPoolEmptyDialog() {
PieceTheme {
Column {
MatchingPoolEmptyDialog(MatchingPoolEmptyType.Basic,{})
}
}
}
@Preview
@Composable
private fun PreviewNewMatchingPoolEmptyDialog() {
PieceTheme {
Column {
MatchingPoolEmptyDialog(MatchingPoolEmptyType.New) {}
}
}
}
🤖 Prompt for AI Agents
In
feature/matching/src/main/java/com/puzzle/matching/graph/main/dialog/MatchingPoolEmptyDialog.kt
around lines 56-64, the PreviewNewMatchingPoolEmptyDialog currently passes
MatchingPoolEmptyType.Basic by mistake; update the call to pass
MatchingPoolEmptyType.New so the preview renders the "New" variant (keep the
existing lambda parameter as-is).

Comment on lines +82 to +90
PieceErrorCode.EXPIRED_REFRESH_TOKEN -> {
viewModelScope.launch {
userRepository.clearLastMatchingPoolEmptyDialogShownDate()

navigationHelper.navigate(
TopLevelTo(AuthGraphBaseRoute)
)
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add error handling for clearLastMatchingPoolEmptyDialogShownDate().

If clearLastMatchingPoolEmptyDialogShownDate() throws an exception, navigation to AuthGraphBaseRoute will never occur, potentially leaving the user in an invalid state. Wrap the clear operation in a runCatching block to ensure navigation proceeds even if the clear operation fails.

🔎 Proposed fix with error handling
 PieceErrorCode.EXPIRED_REFRESH_TOKEN -> {
     viewModelScope.launch {
-        userRepository.clearLastMatchingPoolEmptyDialogShownDate()
+        runCatching {
+            userRepository.clearLastMatchingPoolEmptyDialogShownDate()
+        }
 
         navigationHelper.navigate(
             TopLevelTo(AuthGraphBaseRoute)
         )
     }
 }
📝 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
PieceErrorCode.EXPIRED_REFRESH_TOKEN -> {
viewModelScope.launch {
userRepository.clearLastMatchingPoolEmptyDialogShownDate()
navigationHelper.navigate(
TopLevelTo(AuthGraphBaseRoute)
)
}
}
PieceErrorCode.EXPIRED_REFRESH_TOKEN -> {
viewModelScope.launch {
runCatching {
userRepository.clearLastMatchingPoolEmptyDialogShownDate()
}
navigationHelper.navigate(
TopLevelTo(AuthGraphBaseRoute)
)
}
}
🤖 Prompt for AI Agents
In presentation/src/main/java/com/puzzle/presentation/MainViewModel.kt around
lines 82 to 90, the call to
userRepository.clearLastMatchingPoolEmptyDialogShownDate() can throw and prevent
navigation to AuthGraphBaseRoute; wrap that call in a runCatching { ...
}.onFailure { /* optional logging */ } so that regardless of errors the
navigationHelper.navigate(TopLevelTo(AuthGraphBaseRoute)) runs inside the
viewModelScope.launch block; keep the clear call asynchronous within the
coroutine and avoid rethrowing so navigation always proceeds.

(exception is IllegalArgumentException ||
(exception is HttpResponseException && exception.status == HttpResponseStatus.Unauthorized)
) -> {
userRepository.clearLastMatchingPoolEmptyDialogShownDate()
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add error handling for clearLastMatchingPoolEmptyDialogShownDate().

If clearLastMatchingPoolEmptyDialogShownDate() throws an exception, it will be caught by the onFailure block at line 152, but navigation to OnboardingRoute will not occur, potentially leaving the user in an invalid state. Wrap the clear operation to ensure navigation proceeds even if it fails.

🔎 Proposed fix with error handling
                 (exception is IllegalArgumentException ||
                         (exception is HttpResponseException && exception.status == HttpResponseStatus.Unauthorized)
                         ) -> {
-                    userRepository.clearLastMatchingPoolEmptyDialogShownDate()
+                    runCatching {
+                        userRepository.clearLastMatchingPoolEmptyDialogShownDate()
+                    }
                     navigationHelper.navigate(TopLevelTo(route = OnboardingRoute))
                     return
                 }
📝 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
userRepository.clearLastMatchingPoolEmptyDialogShownDate()
runCatching {
userRepository.clearLastMatchingPoolEmptyDialogShownDate()
}
🤖 Prompt for AI Agents
In presentation/src/main/java/com/puzzle/presentation/MainViewModel.kt around
line 147, the call to userRepository.clearLastMatchingPoolEmptyDialogShownDate()
can throw and prevent subsequent navigation in the onFailure path; wrap this
call in a try/catch (or use runCatching { ... }.onFailure { /* log */ }) so that
any exception is caught and logged/ignored, ensuring the navigation to
OnboardingRoute still executes regardless of
clearLastMatchingPoolEmptyDialogShownDate() failure.

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

🧹 Nitpick comments (1)
feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingViewModel.kt (1)

356-363: Consider awaiting puzzle count fetch before showing dialog.

Line 361 launches fetchPuzzleCount() asynchronously but doesn't await the result before showing the InSufficient dialog on line 362. This creates a race condition where the dialog may display stale puzzle count data.

Consider awaiting the fetch or documenting this intentional behavior if immediate feedback is preferred.

🔎 Proposed fix to await puzzle count
                PieceErrorCode.NOT_ENOUGH_PUZZLE -> {
-                    fetchPuzzleCount()
+                    fetchPuzzleCount().join()
                    showDialog(MatchingDialogType.InSufficient)
                }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3ad2ad4 and 86bc9b5.

📒 Files selected for processing (1)
  • feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingViewModel.kt
🧰 Additional context used
🧬 Code graph analysis (1)
feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingViewModel.kt (2)
core/common-ui/src/main/java/com/puzzle/common/base/BaseViewModel.kt (1)
  • setState (39-40)
core/common/src/main/java/com/puzzle/common/TimeUtil.kt (1)
  • getResetDateString (112-121)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (5)
feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingViewModel.kt (5)

11-11: LGTM!

The new imports and instance variables correctly support the daily dialog gating feature.

Also applies to: 32-32, 52-52, 71-72


254-281: LGTM!

The isFree parameter correctly tracks puzzle usage, the snackbar is shown only when a puzzle is consumed, and error handling appropriately branches based on the match type.


287-289: LGTM!

The dialog confirmation logic correctly calls getInstantNewMatch(false) when the user confirms they want to use a puzzle, and appropriately dismisses the MatchingPoolEmpty dialog.


383-390: LGTM!

The date comparison logic correctly resets the dialog check flag when crossing the daily reset boundary.


403-416: Daily gating logic is well-implemented.

The handleMatchingPoolEmptyDialog function correctly gates the dialog display to once per day using the reset date and persists the shown date to datastore.

Note: The issue with using the unfiltered list was already flagged in an earlier comment.

Comment on lines +157 to +163
}.onSuccess { originalList ->

handleMatchingPoolEmptyDialog(originalList)

val filteredList = originalList.filterNot {
it.matchStatus == MatchStatus.REFUSED || it.isBlocked
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use filtered list for pool-empty dialog check.

The function passes originalList to handleMatchingPoolEmptyDialog at line 159, but this list includes REFUSED and blocked matches. The dialog logic at line 406 checks whether any BASIC matches exist, but REFUSED/blocked BASIC matches won't be visible to the user after filtering (lines 161-163).

This could prevent the dialog from showing when the user actually has no visible BASIC matches.

🔎 Proposed fix

Call handleMatchingPoolEmptyDialog with the filtered list instead:

-            }.onSuccess { originalList ->
-
-                handleMatchingPoolEmptyDialog(originalList)
-
-                val filteredList = originalList.filterNot {
+            }.onSuccess { originalList ->
+                val filteredList = originalList.filterNot {
                    it.matchStatus == MatchStatus.REFUSED || it.isBlocked
                }
+
+                handleMatchingPoolEmptyDialog(filteredList)
📝 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
}.onSuccess { originalList ->
handleMatchingPoolEmptyDialog(originalList)
val filteredList = originalList.filterNot {
it.matchStatus == MatchStatus.REFUSED || it.isBlocked
}
}.onSuccess { originalList ->
val filteredList = originalList.filterNot {
it.matchStatus == MatchStatus.REFUSED || it.isBlocked
}
handleMatchingPoolEmptyDialog(filteredList)
🤖 Prompt for AI Agents
In
feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingViewModel.kt
around lines 157-163, the code calls handleMatchingPoolEmptyDialog(originalList)
before filtering out REFUSED and blocked entries, so the dialog check uses items
the user won't see; change the call to pass the filtered list instead (i.e.,
compute filteredList first by excluding REFUSED and blocked items, then call
handleMatchingPoolEmptyDialog(filteredList)) so the pool-empty dialog reflects
the actual visible BASIC matches.

Comment on lines +392 to +401
private fun checkTodayMatchingPoolEmptyDialogStatus() = viewModelScope.launch(dispatcher) {
suspendRunCatching {
userRepository.getLastMatchingPoolEmptyDialogShownDate().first()
}.onSuccess { lastDate ->
val currentDate = getResetDateString()
setState { copy(isBasicMatchingPoolEmptyDialogShownToday = lastDate == currentDate) }
}.onFailure {
setState { copy(isBasicMatchingPoolEmptyDialogShownToday = 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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -name "MatchingViewModel.kt" -type f

Repository: Piece-Puzzly/Piece-Android

Length of output: 154


🏁 Script executed:

cat -n ./feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingViewModel.kt | head -100

Repository: Piece-Puzzly/Piece-Android

Length of output: 5509


🏁 Script executed:

cat -n ./feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingViewModel.kt | sed -n '385,410p'

Repository: Piece-Puzzly/Piece-Android

Length of output: 1375


🏁 Script executed:

cat -n ./feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingViewModel.kt | sed -n '403,420p'

Repository: Piece-Puzzly/Piece-Android

Length of output: 1029


🏁 Script executed:

rg "isBasicMatchingPoolEmptyDialogShownToday" ./feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingViewModel.kt -B 2 -A 2

Repository: Piece-Puzzly/Piece-Android

Length of output: 861


The defensive default silently suppresses the dialog when datastore access fails.

On line 399, when reading the last shown date fails, isBasicMatchingPoolEmptyDialogShownToday defaults to true, which prevents the dialog from appearing. The dialog only shows when this flag is false (line 408), so an error causes silent suppression rather than fallback display or retry.

While this prevents potential dialog spam on repeated errors, it also means users won't see the dialog if the datastore has a persistent issue, hiding functionality without notification.

🤖 Prompt for AI Agents
In
feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingViewModel.kt
around lines 392-401, the onFailure block currently sets
isBasicMatchingPoolEmptyDialogShownToday = true which suppresses the dialog on
datastore errors; change the failure handling to set
isBasicMatchingPoolEmptyDialogShownToday = false so the dialog will display as a
safe default, and add logging/reporting of the exception (e.g., processLogger or
Timber) so the error is visible for debugging; optionally consider adding a
lightweight retry or a metric but at minimum flip the boolean on failure and log
the error.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
feature/matching/src/test/java/com/puzzle/matching/graph/main/MatchingViewModelTest.kt (1)

132-150: Consider removing or reviving the commented-out test.

This commented-out test appears to cover error handling for user not-found scenarios. Given that the PR adds error exception handling for the MatchingScreen, this test scenario may still be relevant.

Options:

  1. Remove: If the scenario is no longer applicable, delete the dead code.
  2. Revive: If the scenario is still valid, update it to use current APIs (setMatchInfoListResult, fetchMatchInfo, etc.) and uncomment.
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 86bc9b5 and 8f391a4.

📒 Files selected for processing (1)
  • feature/matching/src/test/java/com/puzzle/matching/graph/main/MatchingViewModelTest.kt
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (2)
feature/matching/src/test/java/com/puzzle/matching/graph/main/MatchingViewModelTest.kt (2)

71-73: LGTM!

The test setup correctly reflects the updated API (setMatchInfoListResult), and the comment clearly explains the test intent.


84-130: Well-structured parameterized test with comprehensive coverage.

The test improvements are solid:

  • Explicit user info setup improves readability
  • advanceUntilIdle() ensures coroutine completion before assertions
  • Descriptive assertion message aids debugging
  • Coverage of all MatchType variants (BASIC, AUTO, TRIAL, PREMIUM) is thorough

One consideration: the logic at line 123 assumes only REFUSED prevents timer starts. Verify this matches the actual business logic—if other MatchStatus values (e.g., EXPIRED, MATCHED) should also skip timers, the test expectation would need adjustment.

@comst19 comst19 merged commit 41c22dc into develop Jan 3, 2026
2 checks passed
@comst19 comst19 deleted the feature/PC-1514 branch January 3, 2026 10:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ㅁㅅ민수 머지 해도될듯염🌟 현재 코드를 기존 코드에 합쳐도 될 것 같다라고 판단..! 🌟

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants