Skip to content

[Refactor/#121] 단일 Activity + Fragment 구조로 앱 아키텍처 변경 #122

Merged
nrj022 merged 14 commits intodevelopfrom
refactor/#121-activity-fragment-architecture
Apr 10, 2026
Merged

[Refactor/#121] 단일 Activity + Fragment 구조로 앱 아키텍처 변경 #122
nrj022 merged 14 commits intodevelopfrom
refactor/#121-activity-fragment-architecture

Conversation

@nrj022
Copy link
Copy Markdown
Owner

@nrj022 nrj022 commented Apr 3, 2026

Related Issue

해당 PR 작업 중 파생된 이슈는 별도로 분리하여 처리 예정입니다.

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 카드 공유 기능을 개선된 프레그먼트 기반 인터페이스로 제공합니다.
    • 카드 편집 화면이 업데이트된 네비게이션 시스템으로 재구성되었습니다.
  • 리팩토링

    • 앱 모듈 구조를 통합하여 성능을 최적화했습니다.
    • 액티비티 기반 네비게이션에서 프레그먼트 기반 네비게이션으로 전환되었습니다.
    • 앱의 기본 버전이 1.3.0에서 1.4.0으로 업데이트되었습니다.

nrj022 added 10 commits March 31, 2026 11:09
- :feature:main 모듈 제거
- 메인 화면을 ComposeView 기반 HomeFragment로 전환
- 리소스 및 유틸 클래스 이동 (:feature:main → :feature:card)
- 의존성 추가 (coil-gif, fragment-ktx)
- 사용하지 않는 코드 정리
- CardShareActivity 삭제 및 CardShareFragment 구현
- Fragment 전환에 따른 네비게이션 및 뒤로가기 로직 수정
    - Home 이동 시 HomeFragment로 화면 교체 처리
    - Back 클릭 시 popBackStack 호출
- ComposeView를 활용하여 기존 Compose UI 연동 및 ViewCompositionStrategy 설정
- 단일 프로세스로 앱 구조가 변경됨에 따라 화면 전환시마다 씬 초기화(에셋 제거 및 카메라, 배경 초기화)가 필요, 이에 관련 기능 추가
- CardEditorActivity → CardEditorFragment로 전환
- MainActivity 추가 및 Fragment 호스팅 / UnityPlayer 생명주기 관리 담당

- MainViewModel, MainSideEffect 도입
  → Fragment ↔ Unity 인스턴스 간 통신 구조 분리

- 카드 데이터 로딩 이후 Unity 초기화되도록
  CardEditorViewModel 로직 개선

- FragmentManager 기반으로
  Home / CardEditor / CardShare 화면 전환 로직 마이그레이션

- Unity 통신 메시지 및 메모리 이벤트(onLowMemory, onTrimMemory)
  처리 로직을 MainActivity로 일원화
- 화면 전환을 위한 슬라이드 애니메이션 리소스 4종 추가 (slide_in_left/right, slide_out_left/right)
- HomeFragment -> CardEditorFragment, CardEditorFragment -> CardShareFragment 이동 시 애니메이션 적용
- CardEditorState의 isLoading 기본값을 true에서 false로 변경
- CardEditorViewModel 내 불필요한 isLoading 종료(false) 처리 로직 제거
- :feature:card 모듈을 :feature 모듈로 이동 및 관련 파일 재배치
- 패키지 경로 수정 및 프로젝트 구성 설정(Gradle, Manifest) 업데이트
- MainActivity, MainViewModel, MainSideEffect를
  com.zcard.feature.cardeditor → com.zcard.feature로 이동

- 패키지 변경에 따라 AndroidManifest.xml 및
  레이아웃 파일(activity_card_editor.xml, fragment_card_editor.xml) 내 클래스 참조 경로 수정

- CardEditorFragment에서 이동된 클래스 import 추가
- savedInstanceState가 null일 때만 HomeFragment를 추가하도록 변경하여 기기 회전 등 작업 시 Fragment가 중복 생성되는 현상 수정
- 초기 화면 설정 시 불필요한 addToBackStack(null) 호출 제거
@nrj022 nrj022 self-assigned this Apr 3, 2026
@nrj022 nrj022 added the refactor 패키지 구조, 파일 이름 등 수정 label Apr 3, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 3, 2026

Walkthrough

앱 아키텍처를 화면별 Activity 구조에서 단일 Activity + Fragment 구조로 리팩토링하였습니다. feature/mainfeature/card 모듈을 통합하여 feature 단일 모듈로 변경하고, 패키지 네임스페이스를 com.zcard.main/com.zcard.card에서 com.zcard.feature로 통일하였습니다.

Changes

Cohort / File(s) Summary
Build Configuration & Module Structure
app/build.gradle.kts, settings.gradle.kts, gradle/libs.versions.toml
baseVersion을 1.3.0에서 1.4.0으로 업데이트하고, 의존성을 :feature:card, :feature:main에서 :feature 단일 모듈로 변경. 모듈 include도 통합.
App Manifest & Entry Point
app/src/main/AndroidManifest.xml
launcher activity를 com.zcard.feature.MainActivity로 변경, 제거된 CardEditorActivity, CardShareActivity 선언 제거, 기존 deep link 제거.
Feature Module Configuration
feature/build.gradle.kts
Android namespace를 com.zcard.card에서 com.zcard.feature로 변경, Coil Compose/GIF 및 Fragment KTX 의존성 추가.
Main Activity & Side Effects
feature/src/main/java/com/zcard/feature/MainActivity.kt, feature/src/main/java/com/zcard/feature/MainSideEffect.kt, feature/src/main/java/com/zcard/feature/MainViewModel.kt
새로운 Hilt 기반 MainActivity 추가: Unity player 관리, Fragment 컨테이너 호스팅, 생명주기 이벤트 처리. MainSideEffect, MainViewModel으로 Fragment 간 통신 지원.
CardEditor Migration
feature/src/main/java/com/zcard/feature/cardeditor/CardEditorFragment.kt, feature/src/main/java/com/zcard/feature/cardeditor/CardEditor*.kt, feature/src/main/java/com/zcard/feature/cardeditor/model/*, feature/src/main/java/com/zcard/feature/cardeditor/ui/*
Activity에서 Fragment로 전환, 패키지를 com.zcard.card.cardeditor에서 com.zcard.feature.cardeditor로 이동. 뒤로가기 동작을 Fragment 스택 팝으로 변경, CardShareFragment 네비게이션 추가. isLoading 기본값 true → false 변경, handleInitUnity()initUnity() 메서드 개선.
CardShare Migration
feature/src/main/java/com/zcard/feature/cardshare/CardShareFragment.kt, feature/src/main/java/com/zcard/feature/cardshare/CardShareScreen.kt
새로운 CardShareFragment 및 Compose 기반 CardShareScreen 추가, 기존 CardShareActivity 기능을 Fragment로 이전.
Home Screen
feature/src/main/java/com/zcard/feature/home/HomeFragment.kt, feature/src/main/java/com/zcard/feature/home/HomeScreen.kt, feature/src/main/java/com/zcard/feature/home/HomeViewModel.kt, feature/src/main/java/com/zcard/feature/home/model/*, feature/src/main/java/com/zcard/feature/home/util/*
새로운 HomeFragment 추가, 패키지를 com.zcard.main에서 com.zcard.feature.home으로 이동. LifecycleResumeEffect로 카드 로딩 타이밍 개선.
Resources
feature/src/main/res/layout/activity_main.xml, feature/src/main/res/anim/slide_*.xml, feature/src/main/res/values/strings.xml
새로운 activity_main.xml 레이아웃 추가 (Unity 컨테이너 + Fragment 컨테이너), Fragment 전환 애니메이션 4개 추가, 문자열 리소스 통합.
Removed: feature/main Module
feature/main/build.gradle.kts, feature/main/src/main/AndroidManifest.xml, feature/main/src/main/java/com/zcard/main/MainActivity.kt, feature/main/src/main/res/values/strings.xml, feature/main/proguard-rules.pro, .gitignore
feature/main 모듈 전체 제거, MainActivity 및 관련 리소스 제거.
Removed: feature/card Tests
feature/card/src/androidTest/java/com/zcard/card/ExampleInstrumentedTest.kt, feature/card/src/test/java/com/zcard/card/ExampleUnitTest.kt, feature/card/src/test/java/com/zcard/card/cardeditor/CardEditorUtilsTest.kt
모듈 통합으로 인한 이전 테스트 파일 제거.
Test Files Update
feature/src/androidTest/java/com/zcard/feature/ExampleInstrumentedTest.kt, feature/src/test/java/com/zcard/feature/ExampleUnitTest.kt
패키지를 com.zcard.maincom.zcard.feature로 변경.
Binary Update
libs/unityLibrary-release.aar
Unity library AAR 파일 업데이트 (LFS 포인터 변경).

Assessment against linked issues

Objective Addressed Explanation
단일 Activity 구조 도입 [#121]
Fragment 기반 화면 전환 [#121]
모듈 통합 (feature/main + feature/cardfeature) [#121]
패키지 네임스페이스 통일 (com.zcard.feature.*) [#121]
프로세스 분리 제거 [#121]

Out-of-scope changes

Code Change Explanation
CardEditorState의 isLoading 기본값 true → false 변경 (feature/src/main/java/com/zcard/feature/cardeditor/CardEditorState.kt) 아키텍처 리팩토링 범위 외의 상태 관리 로직 변경입니다.
CardEditorViewModel의 initUnity() 메서드 추가 및 에러 핸들링 개선 (feature/src/main/java/com/zcard/feature/cardeditor/CardEditorViewModel.kt) 에러 처리 로직 추가는 단일 Activity + Fragment 구조 이행과는 직접 관련이 없는 기능 개선입니다.
HomeScreen에 LifecycleResumeEffect 추가 (feature/src/main/java/com/zcard/feature/home/HomeScreen.kt) 생명주기 관리 개선은 구조 리팩토링 범위 외의 추가 최적화입니다.
MainColumn에 background(White) modifier 추가 (feature/src/main/java/com/zcard/feature/home/HomeScreen.kt) UI 스타일링 변경으로 아키텍처 리팩토링 범위 외입니다.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/#121-activity-fragment-architecture

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.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 11

Caution

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

⚠️ Outside diff range comments (2)
feature/src/main/java/com/zcard/feature/cardeditor/util/CardEditorUtils.kt (1)

15-28: ⚠️ Potential issue | 🟡 Minor

음수 입력 처리 누락

toBase62 함수에서 음수 입력 시 빈 문자열이 반환됩니다. while (n > 0) 조건으로 인해 음수는 루프를 건너뛰게 됩니다.

🛡️ 음수 처리 추가 제안
 fun toBase62(num: Long): String {
     if (num == 0L) return "0"
+    if (num < 0L) throw IllegalArgumentException("Negative numbers are not supported")

     var n = num
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@feature/src/main/java/com/zcard/feature/cardeditor/util/CardEditorUtils.kt`
around lines 15 - 28, toBase62 currently returns an empty string for negative
inputs because the while (n > 0) loop is skipped; add explicit negative handling
in the toBase62 function: check if num < 0 and either throw an
IllegalArgumentException (e.g., "num must be non-negative") or support negatives
by using absolute value and prepending '-' to the result; update the function
(variables: num, n, result) so negatives are handled before entering the while
loop and the final returned string is correct.
feature/src/main/java/com/zcard/feature/home/util/HomeUtils.kt (1)

13-16: ⚠️ Potential issue | 🟡 Minor

단수/복수 처리 및 엣지 케이스 고려 필요

현재 구현에서 몇 가지 엣지 케이스가 있습니다:

  • 0 minutes ago, 1 minutes ago 등 단수형일 때 문법적으로 부정확합니다.
  • millis가 미래 시간인 경우 음수 duration이 발생하여 예상치 못한 결과가 나올 수 있습니다.
💡 개선 제안
 return when {
+    duration.isNegative -> "just now"
+    duration.toMinutes() == 0L -> "just now"
+    duration.toMinutes() == 1L -> "1 minute ago"
     duration.toMinutes() < 60 -> "${duration.toMinutes()} minutes ago"
+    duration.toHours() == 1L -> "1 hour ago"
     duration.toHours() < 24 -> "${duration.toHours()} hours ago"
+    duration.toDays() == 1L -> "1 day ago"
     duration.toDays() < 30 -> "${duration.toDays()} days ago"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@feature/src/main/java/com/zcard/feature/home/util/HomeUtils.kt` around lines
13 - 16, The when block using duration.toMinutes()/toHours()/toDays() should
handle singular vs plural and future (negative) durations: compute a signed
value (e.g., minutes = duration.toMinutes()), branch on sign first (if value ==
0 -> "just now"; if value < 0 -> return "in X minute(s)/hour(s)/day(s)" using
absolute value), and for positive values choose singular wording when the
magnitude == 1 (e.g., "1 minute ago" vs "2 minutes ago"); update the comparisons
and string construction in the when block (the expressions using
duration.toMinutes(), duration.toHours(), duration.toDays()) to use these
computed values and correct pluralization and future phrasing.
🧹 Nitpick comments (6)
feature/src/main/java/com/zcard/feature/cardeditor/ui/common/BaseDialog.kt (2)

51-57: 불필요한 else null 제거 가능

Composable 람다 내에서 else null은 아무런 효과가 없으므로 제거해도 됩니다.

♻️ 간소화 제안
         dismissButton = {
             if(dismissLabel.isNotBlank()) {
                 TextButton(onClick = onDismiss) {
                     Text(text = dismissLabel, style = MaterialTheme.typography.labelSmall)
                 }
-            } else null
+            }
         },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@feature/src/main/java/com/zcard/feature/cardeditor/ui/common/BaseDialog.kt`
around lines 51 - 57, The dismissButton lambda currently returns an unnecessary
"else null"; update the lambda for dismissButton to only conditionally emit the
TextButton when dismissLabel.isNotBlank() (using the existing TextButton(...)
with onDismiss and MaterialTheme.typography.labelSmall) and remove the trailing
"else null" branch so the composable lambda is simplified (refer to
dismissButton, dismissLabel, TextButton and onDismiss).

37-45: 중복된 let 표현 간소화 가능

customContent?.let { customContent }customContent와 동일하므로 간소화할 수 있습니다.

♻️ 간소화 제안
-        } ?: customContent?.let { customContent },
+        } ?: customContent,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@feature/src/main/java/com/zcard/feature/cardeditor/ui/common/BaseDialog.kt`
around lines 37 - 45, The expression in BaseDialog.kt's text assignment uses an
unnecessary let: replace the redundant "customContent?.let { customContent }"
with just "customContent" (i.e., simplify the nullable passthrough), keeping the
existing conditional logic around content.takeIf { it.isNotEmpty() } and the
surrounding structure in the same expression.
feature/src/main/java/com/zcard/feature/MainViewModel.kt (1)

31-34: 높이 비율 입력값은 범위를 보정해두는 편이 안전합니다.

Line 31-34에서 fraction이 0f~1f 범위를 벗어나면 레이아웃 계산 이상으로 이어질 수 있어 coerceIn(0f, 1f) 보정을 권장합니다.

♻️ 제안 diff
     fun emitUnityContainerHeightFraction(fraction: Float) {
         viewModelScope.launch {
-            _mainSideEffect.emit(MainSideEffect.UnityContainerHeightFraction(fraction))
+            _mainSideEffect.emit(
+                MainSideEffect.UnityContainerHeightFraction(fraction.coerceIn(0f, 1f))
+            )
         }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@feature/src/main/java/com/zcard/feature/MainViewModel.kt` around lines 31 -
34, The emitUnityContainerHeightFraction function should clamp the incoming
fraction to the 0f–1f range to avoid invalid layout calculations; update
emitUnityContainerHeightFraction to call fraction.coerceIn(0f, 1f) (or
equivalent) before emitting MainSideEffect.UnityContainerHeightFraction via
_mainSideEffect.emit so the emitted value is always bounded.
feature/build.gradle.kts (1)

10-11: 네임스페이스가 실제 패키지 구조와 일치하지 않습니다.

build.gradle.kts의 네임스페이스는 "com.zcard.card"로 설정되어 있지만, 모듈의 대부분의 코드는 com.zcard.feature.* 패키지를 사용합니다. 현재는 컴파일되지만, 모듈의 주 구조를 반영하도록 네임스페이스를 업데이트하는 것을 권장합니다.

♻️ 제안된 변경사항
 android {
-    namespace = "com.zcard.card"
+    namespace = "com.zcard.feature"
     compileSdk = 36
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@feature/build.gradle.kts` around lines 10 - 11, Update the android namespace
in the module's build.gradle.kts so it matches the module package structure
(currently uses "com.zcard.card"); change the namespace value in the android {
namespace = ... } declaration to the correct package root used by the code
(e.g., "com.zcard.feature") so the namespace aligns with the com.zcard.feature.*
packages.
feature/src/main/java/com/zcard/feature/MainActivity.kt (1)

32-45: Unity eager-init 비용은 한 번 측정해보는 편이 좋겠습니다.

Home가 첫 화면인데 Line 45에서 바로 initUnity()를 호출하면 editor에 들어가지 않는 세션도 Unity cold-start와 baseline 메모리를 그대로 부담합니다. 이번 리팩터의 목표가 메모리/컨텍스트 비용 절감이라면 첫 editor 진입 시 lazy-init하거나 warm-up 조건을 분리하는 쪽이 더 잘 맞습니다.

Also applies to: 68-70

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@feature/src/main/java/com/zcard/feature/MainActivity.kt` around lines 32 -
45, Calling initUnity() eagerly in MainActivity.onCreate causes Unity to
cold-start and allocate baseline memory even when users land on HomeFragment and
never enter the editor; change to lazy-init or conditional warm-up: remove the
direct initUnity() call from onCreate (refer to onCreate and initUnity), instead
initialize Unity only when navigating to the editor (e.g., from HomeFragment
entry point or editor launch handler) or provide a separate warmUpUnity() that
performs lightweight preparation and call full initUnity() on actual editor
entry; also apply the same lazy/conditional approach to the other eager calls
referenced around lines 68-70.
feature/src/main/java/com/zcard/feature/cardeditor/CardEditorFragment.kt (1)

237-248: 코드는 안전하나 가독성 개선 가능.

activetrue일 때만 temp.elementId에 접근하므로 NPE는 발생하지 않지만, null 체크 로직이 분산되어 있어 코드 의도가 명확하지 않습니다.

♻️ 가독성 개선 제안
     private fun updateTransformPanel(state: CardEditorState) {
         val temp = state.tempTransform
         val active = state.isTransformPanelActive && temp != null
 
         binding.containerTransformOption.isVisible = active
         binding.imgBtnCameraFocusController.isVisible = active
         binding.imgBtnCameraFocusController.setImageResource(
             if(state.isTransformCameraFocus) R.drawable.ic_fit_screen else R.drawable.ic_target)
         binding.imgBtnTransformReset.isVisible = active && state.hasPendingTransform
-        binding.imgObjectThumb.setImageResource(getObjectThumbByKey(requireContext(), temp?.thumbnailKey))
-        binding.textElementKey.text = if(active) toBase62(temp.elementId) else ""
+        if (active && temp != null) {
+            binding.imgObjectThumb.setImageResource(getObjectThumbByKey(requireContext(), temp.thumbnailKey))
+            binding.textElementKey.text = toBase62(temp.elementId)
+        } else {
+            binding.imgObjectThumb.setImageResource(getObjectThumbByKey(requireContext(), null))
+            binding.textElementKey.text = ""
+        }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@feature/src/main/java/com/zcard/feature/cardeditor/CardEditorFragment.kt`
around lines 237 - 248, updateTransformPanel에서 temp(null 가능)와 active 플래그가 분산되어
있어 가독성이 떨어집니다; 함수 시작부에서 val temp = state.tempTransform을 유지하되 active가 true일 때만
temp를 안전하게 사용하도록 early return 또는 temp?.let { ... } 패턴으로 묶으세요 (메서드명:
updateTransformPanel, 변수: temp, active, state.isTransformPanelActive,
state.hasPendingTransform, 뷰 바인딩: binding.containerTransformOption,
imgBtnCameraFocusController, imgBtnTransformReset, imgObjectThumb,
textElementKey, 유틸: toBase62). 이렇게 하면 null 체크가 한 곳에 모여 의도가 명확해지고 temp.elementId
접근 시 NPE 우려가 제거됩니다.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@feature/src/androidTest/java/com/zcard/feature/ExampleInstrumentedTest.kt`:
- Line 22: 테스트의 하드코딩된 패키지명(assertEquals call in ExampleInstrumentedTest.kt)과 실제
Gradle namespace(feature/build.gradle.kts의 namespace) 불일치로 계측 테스트가 실패합니다;
ExampleInstrumentedTest.kt에서 현재 assertEquals("com.zcard.feature.test",
appContext.packageName)을 실제 namespace인 "com.zcard.card"로 변경하거나(또는 의도적으로 테스트 값을
"com.zcard.feature"로 통일하려면 feature/build.gradle.kts의 namespace를 함께 변경) 둘 중 하나를
선택해 두 위치(assertEquals in ExampleInstrumentedTest.kt and namespace in
feature/build.gradle.kts)를 일치시키세요.

In `@feature/src/main/java/com/zcard/feature/cardeditor/CardEditorFragment.kt`:
- Around line 78-82: In CardEditorFragment remove the duplicate
OnBackPressedCallback registration so only one callback is registered: locate
the requireActivity().onBackPressedDispatcher.addCallback(this, object :
OnBackPressedCallback(true) { override fun handleOnBackPressed() {
Toast.makeText(requireContext(),
getString(R.string.editor_msg_block_system_back), Toast.LENGTH_SHORT).show() }
}) occurrences and delete the redundant one (keep a single registration in the
fragment lifecycle, e.g., the existing one in onViewCreated/onCreateView),
ensuring the Toast logic remains in the retained callback.
- Around line 72-73: The fragment currently uses val cardId =
arguments?.getLong(ARG_CARD_ID) ?: -1 and blindly sends
CardEditorIntent.Init(cardId) to viewModel; update CardEditorFragment to
explicitly handle the missing/invalid card id instead of using -1: check
arguments and ARG_CARD_ID presence, and if absent or cardId is invalid, either
show an error UI/toast and return (prevent calling viewModel.onIntent) or
surface a clear error (e.g., throw IllegalArgumentException) so
CardEditorIntent.Init is only invoked with a valid id; refer to
CardEditorFragment, ARG_CARD_ID, viewModel.onIntent(...) and
CardEditorIntent.Init(...) when making the change.

In `@feature/src/main/java/com/zcard/feature/cardeditor/CardEditorViewModel.kt`:
- Around line 184-190: The observeSpawnedObjects handler currently only applies
Result.success and silently ignores failures; update observeSpawnedObjects (the
flow.onEach block inside the observeSpawnedObjects function) to handle
result.onFailure as well by logging the Throwable (use your logger) and updating
_cardEditorState to reflect the error (e.g., set an error field or clear/mark
spawnedObjects stale) so the UI can show feedback; keep using
viewModelScope.launchIn and ensure failure handling does not crash the
collector.

In `@feature/src/main/java/com/zcard/feature/cardeditor/util/CardEditorUtils.kt`:
- Line 1: CardEditorUtils의 단위테스트 파일 CardEditorUtilsTest.kt가 삭제되어 복구 또는 재작성해야
합니다; 복구가 불가능하면 새 테스트를 추가하여 toBase62()의 다양한 입력(0, 음수, 큰값, 경계값)과 예상 Base62 결과를
검증하고 getObjectThumbByKey()가 null/존재하는 키에 대해 올바른 썸네일 URL 또는 기본값을 반환하도록 테스트하세요;
테스트는 CardEditorUtils 클래스의 해당 정적/확장 메서드(toBase62, getObjectThumbByKey)를 직접 호출하고,
AssetBrowserPanel/ CardEditorFragment와의 통합 사용 사례가 있다면 필요한 의존성만 목(mock) 처리하거나 더미
데이터로 호출하여 실제 사용 시나리오를 재현하도록 작성하세요.

In `@feature/src/main/java/com/zcard/feature/cardshare/CardShareFragment.kt`:
- Around line 51-55: The navigationToMain() function currently pops the back
stack then immediately replaces the container with a new HomeFragment instance;
remove the extra replace/commit call and rely solely on
parentFragmentManager.popBackStack(null,
FragmentManager.POP_BACK_STACK_INCLUSIVE) to restore the existing HomeFragment
instance (i.e., delete the
parentFragmentManager.beginTransaction().replace(R.id.fragment_container,
HomeFragment()).commit() lines) so you don't discard the original HomeFragment
state or force a reload.

In `@feature/src/main/java/com/zcard/feature/cardshare/CardShareScreen.kt`:
- Around line 55-58: The Modifier chains in CardShareScreen.kt (used in the
Composable CardShareScreen) apply only fixed padding
(.fillMaxWidth().padding(...)) which can let content/CTAs be obscured by notches
or system bars; update the Modifier chains (both the one around the top block
and the one used near lines 132-135) to apply system insets first by calling
statusBarsPadding() and navigationBarsPadding() before your fixed .padding(...)
so the layout respects safe areas (e.g.,
Modifier.statusBarsPadding().navigationBarsPadding().fillMaxWidth().padding(...)).

In `@feature/src/main/java/com/zcard/feature/home/HomeScreen.kt`:
- Around line 69-72: HomeScreen의 LifecycleResumeEffect에서 매 resume마다
viewModel.loadCards()를 호출해 중복 요청이 발생하므로 HomeViewModel의 loadCards()에 인플라이트 중복 방어를
추가하세요: HomeViewModel에 private var loadJob: Job? = null 필드를 추가하고 loadCards() 시작 시
loadJob?.isActive를 체크해 활성화중이면 즉시 리턴하도록 하고, 그렇지 않으면 loadJob =
viewModelScope.launch { /* 기존 getAllCards 호출 및 처리 로직 유지 */ }로 할당해 중복 실행을 방지하세요;
필요시 이전 작업을 취소하려면 loadJob?.cancel() 호출을 고려하세요.

In `@feature/src/main/java/com/zcard/feature/MainActivity.kt`:
- Around line 95-105: The onUnityMessage method is vulnerable to R8 obfuscation;
protect it by either annotating the method with `@Keep` (ensure you import
androidx.annotation.Keep and add `@Keep` above fun onUnityMessage(jsonString:
String) in MainActivity) or by adding a ProGuard keep rule that preserves the
MainActivity.onUnityMessage(String) signature (add a -keep entry for class
com.zcard.feature.MainActivity with public void
onUnityMessage(java.lang.String); in the module proguard-rules.pro).

In `@feature/src/main/java/com/zcard/feature/MainViewModel.kt`:
- Around line 16-17: The current _mainSideEffect =
MutableSharedFlow<MainSideEffect>() can drop Unity messages emitted as
ReceivedUnityMessage before collectors start (repeatOnLifecycle(STARTED));
create a dedicated replayable stream for Unity messages (e.g., a dedicated
MutableSharedFlow or StateFlow) and/or configure _mainSideEffect with replay and
buffer (e.g., MutableSharedFlow(replay = 1, extraBufferCapacity = X,
onBufferOverflow = BufferOverflow.DROP_OLDEST)) so ReceivedUnityMessage
emissions are retained until collectors subscribe; update usages of
_mainSideEffect/mainSideEffect and places that emit ReceivedUnityMessage (lines
around the ReceivedUnityMessage emission) to publish to the new replayable flow
or to the reconfigured _mainSideEffect.

In `@feature/src/main/res/layout/activity_card_editor.xml`:
- Around line 18-21: The FragmentContainerView with id fragment_container is
using match_parent and, as a ConstraintLayout child, can overlap the Unity view
(unity_container); change fragment_container to use constraint-based sizing (set
layout_height to 0dp) and add constraints: constrain its top to the parent (or
the intended top anchor) and its bottom to the top of unity_container (or
constrain its bottom to parent and top to unity_container depending on desired
ordering) so the fragment area and unity_container are divided and cannot
overlap or receive touch conflicts.

---

Outside diff comments:
In `@feature/src/main/java/com/zcard/feature/cardeditor/util/CardEditorUtils.kt`:
- Around line 15-28: toBase62 currently returns an empty string for negative
inputs because the while (n > 0) loop is skipped; add explicit negative handling
in the toBase62 function: check if num < 0 and either throw an
IllegalArgumentException (e.g., "num must be non-negative") or support negatives
by using absolute value and prepending '-' to the result; update the function
(variables: num, n, result) so negatives are handled before entering the while
loop and the final returned string is correct.

In `@feature/src/main/java/com/zcard/feature/home/util/HomeUtils.kt`:
- Around line 13-16: The when block using
duration.toMinutes()/toHours()/toDays() should handle singular vs plural and
future (negative) durations: compute a signed value (e.g., minutes =
duration.toMinutes()), branch on sign first (if value == 0 -> "just now"; if
value < 0 -> return "in X minute(s)/hour(s)/day(s)" using absolute value), and
for positive values choose singular wording when the magnitude == 1 (e.g., "1
minute ago" vs "2 minutes ago"); update the comparisons and string construction
in the when block (the expressions using duration.toMinutes(),
duration.toHours(), duration.toDays()) to use these computed values and correct
pluralization and future phrasing.

---

Nitpick comments:
In `@feature/build.gradle.kts`:
- Around line 10-11: Update the android namespace in the module's
build.gradle.kts so it matches the module package structure (currently uses
"com.zcard.card"); change the namespace value in the android { namespace = ... }
declaration to the correct package root used by the code (e.g.,
"com.zcard.feature") so the namespace aligns with the com.zcard.feature.*
packages.

In `@feature/src/main/java/com/zcard/feature/cardeditor/CardEditorFragment.kt`:
- Around line 237-248: updateTransformPanel에서 temp(null 가능)와 active 플래그가 분산되어 있어
가독성이 떨어집니다; 함수 시작부에서 val temp = state.tempTransform을 유지하되 active가 true일 때만 temp를
안전하게 사용하도록 early return 또는 temp?.let { ... } 패턴으로 묶으세요 (메서드명:
updateTransformPanel, 변수: temp, active, state.isTransformPanelActive,
state.hasPendingTransform, 뷰 바인딩: binding.containerTransformOption,
imgBtnCameraFocusController, imgBtnTransformReset, imgObjectThumb,
textElementKey, 유틸: toBase62). 이렇게 하면 null 체크가 한 곳에 모여 의도가 명확해지고 temp.elementId
접근 시 NPE 우려가 제거됩니다.

In `@feature/src/main/java/com/zcard/feature/cardeditor/ui/common/BaseDialog.kt`:
- Around line 51-57: The dismissButton lambda currently returns an unnecessary
"else null"; update the lambda for dismissButton to only conditionally emit the
TextButton when dismissLabel.isNotBlank() (using the existing TextButton(...)
with onDismiss and MaterialTheme.typography.labelSmall) and remove the trailing
"else null" branch so the composable lambda is simplified (refer to
dismissButton, dismissLabel, TextButton and onDismiss).
- Around line 37-45: The expression in BaseDialog.kt's text assignment uses an
unnecessary let: replace the redundant "customContent?.let { customContent }"
with just "customContent" (i.e., simplify the nullable passthrough), keeping the
existing conditional logic around content.takeIf { it.isNotEmpty() } and the
surrounding structure in the same expression.

In `@feature/src/main/java/com/zcard/feature/MainActivity.kt`:
- Around line 32-45: Calling initUnity() eagerly in MainActivity.onCreate causes
Unity to cold-start and allocate baseline memory even when users land on
HomeFragment and never enter the editor; change to lazy-init or conditional
warm-up: remove the direct initUnity() call from onCreate (refer to onCreate and
initUnity), instead initialize Unity only when navigating to the editor (e.g.,
from HomeFragment entry point or editor launch handler) or provide a separate
warmUpUnity() that performs lightweight preparation and call full initUnity() on
actual editor entry; also apply the same lazy/conditional approach to the other
eager calls referenced around lines 68-70.

In `@feature/src/main/java/com/zcard/feature/MainViewModel.kt`:
- Around line 31-34: The emitUnityContainerHeightFraction function should clamp
the incoming fraction to the 0f–1f range to avoid invalid layout calculations;
update emitUnityContainerHeightFraction to call fraction.coerceIn(0f, 1f) (or
equivalent) before emitting MainSideEffect.UnityContainerHeightFraction via
_mainSideEffect.emit so the emitted value is always bounded.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 15cc0ce5-7f88-464f-89f4-0fb49ead8ee9

📥 Commits

Reviewing files that changed from the base of the PR and between 09ff2eb and 36cd7b2.

⛔ Files ignored due to path filters (10)
  • feature/src/main/res/drawable/gif_happy_2026.gif is excluded by !**/*.gif
  • feature/src/main/res/font/anton_regular.ttf is excluded by !**/*.ttf
  • feature/src/main/res/font/bebasneue_regular.ttf is excluded by !**/*.ttf
  • feature/src/main/res/font/irishgrover_regular.ttf is excluded by !**/*.ttf
  • feature/src/main/res/font/permanentmarker_regular.ttf is excluded by !**/*.ttf
  • feature/src/main/res/font/playfairdisplay_medium.ttf is excluded by !**/*.ttf
  • feature/src/main/res/font/playfairdisplay_medium_italic.ttf is excluded by !**/*.ttf
  • feature/src/main/res/font/playwriteustradguides_regular.ttf is excluded by !**/*.ttf
  • feature/src/main/res/font/shadowsintolight_regular.ttf is excluded by !**/*.ttf
  • feature/src/main/res/font/titanone_regular.ttf is excluded by !**/*.ttf
📒 Files selected for processing (108)
  • app/build.gradle.kts
  • app/src/main/AndroidManifest.xml
  • feature/.gitignore
  • feature/build.gradle.kts
  • feature/card/src/androidTest/java/com/zcard/card/ExampleInstrumentedTest.kt
  • feature/card/src/main/java/com/zcard/card/cardshare/CardShareActivity.kt
  • feature/card/src/test/java/com/zcard/card/ExampleUnitTest.kt
  • feature/card/src/test/java/com/zcard/card/cardeditor/CardEditorUtilsTest.kt
  • feature/consumer-rules.pro
  • feature/main/.gitignore
  • feature/main/build.gradle.kts
  • feature/main/consumer-rules.pro
  • feature/main/proguard-rules.pro
  • feature/main/src/main/AndroidManifest.xml
  • feature/main/src/main/java/com/zcard/main/MainActivity.kt
  • feature/main/src/main/res/values/strings.xml
  • feature/proguard-rules.pro
  • feature/src/androidTest/java/com/zcard/feature/ExampleInstrumentedTest.kt
  • feature/src/main/AndroidManifest.xml
  • feature/src/main/java/com/zcard/feature/MainActivity.kt
  • feature/src/main/java/com/zcard/feature/MainSideEffect.kt
  • feature/src/main/java/com/zcard/feature/MainViewModel.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/CardEditorFragment.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/CardEditorIntent.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/CardEditorSideEffect.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/CardEditorState.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/CardEditorViewModel.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/model/BaseTabItem.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/model/DialogState.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/model/Direction.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/model/PanelType.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/model/TempTextElement.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/model/TempTransform.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/ui/CardEditorBottomScreen.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/ui/CardEditorTextScreen.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/ui/common/BaseDialog.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/ui/common/BaseTabs.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/ui/common/BouncingLogoLoadingOverlay.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/ui/common/DirectionalController.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/ui/dialog/CardInfoDialog.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/ui/dialog/ObjectDeleteConfirmDialog.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/ui/dialog/SetCardTitleDialog.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/ui/dialog/UnsavedChangesDialog.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/ui/panels/AssetBrowserPanel.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/ui/panels/TextEditorPanel.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/ui/panels/TextListPanel.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/ui/panels/TransformControlPanel.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/ui/uimapper/AlignmentUiMapper.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/ui/uimapper/FontUiMapper.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/util/CardEditorUtils.kt
  • feature/src/main/java/com/zcard/feature/cardshare/CardShareFragment.kt
  • feature/src/main/java/com/zcard/feature/cardshare/CardShareScreen.kt
  • feature/src/main/java/com/zcard/feature/home/HomeFragment.kt
  • feature/src/main/java/com/zcard/feature/home/HomeScreen.kt
  • feature/src/main/java/com/zcard/feature/home/HomeViewModel.kt
  • feature/src/main/java/com/zcard/feature/home/model/CardItem.kt
  • feature/src/main/java/com/zcard/feature/home/util/HomeUtils.kt
  • feature/src/main/res/anim/slide_in_left.xml
  • feature/src/main/res/anim/slide_in_right.xml
  • feature/src/main/res/anim/slide_out_left.xml
  • feature/src/main/res/anim/slide_out_right.xml
  • feature/src/main/res/drawable/bg_circle_black_30_border_white.xml
  • feature/src/main/res/drawable/bg_circle_white.xml
  • feature/src/main/res/drawable/bg_circle_white_70.xml
  • feature/src/main/res/drawable/bg_rounded_rect_black_10.xml
  • feature/src/main/res/drawable/bg_rounded_rect_border.xml
  • feature/src/main/res/drawable/bg_rounded_rect_white.xml
  • feature/src/main/res/drawable/bg_rounded_rect_white_70.xml
  • feature/src/main/res/drawable/ic_arrow_left.xml
  • feature/src/main/res/drawable/ic_card_star.xml
  • feature/src/main/res/drawable/ic_copy.xml
  • feature/src/main/res/drawable/ic_fit_screen.xml
  • feature/src/main/res/drawable/ic_home.xml
  • feature/src/main/res/drawable/ic_link.xml
  • feature/src/main/res/drawable/ic_plus.xml
  • feature/src/main/res/drawable/ic_reset.xml
  • feature/src/main/res/drawable/ic_share.xml
  • feature/src/main/res/drawable/ic_target.xml
  • feature/src/main/res/drawable/ic_trash_bin.xml
  • feature/src/main/res/drawable/logo_z.webp
  • feature/src/main/res/drawable/thumb_m_001.webp
  • feature/src/main/res/drawable/thumb_m_002.webp
  • feature/src/main/res/drawable/thumb_m_003.webp
  • feature/src/main/res/drawable/thumb_m_004.webp
  • feature/src/main/res/drawable/thumb_m_005.webp
  • feature/src/main/res/drawable/thumb_m_006.webp
  • feature/src/main/res/drawable/thumb_m_007.webp
  • feature/src/main/res/drawable/thumb_m_008.webp
  • feature/src/main/res/drawable/thumb_m_009.webp
  • feature/src/main/res/drawable/thumb_m_010.webp
  • feature/src/main/res/drawable/thumb_m_011.webp
  • feature/src/main/res/drawable/thumb_m_012.webp
  • feature/src/main/res/drawable/thumb_m_013.webp
  • feature/src/main/res/drawable/thumb_m_014.webp
  • feature/src/main/res/drawable/thumb_m_015.webp
  • feature/src/main/res/drawable/thumb_m_016.webp
  • feature/src/main/res/drawable/thumb_m_017.webp
  • feature/src/main/res/drawable/thumb_m_018.webp
  • feature/src/main/res/drawable/thumb_m_019.webp
  • feature/src/main/res/drawable/thumb_m_020.webp
  • feature/src/main/res/drawable/thumb_m_placeholder.webp
  • feature/src/main/res/layout/activity_card_editor.xml
  • feature/src/main/res/layout/fragment_card_editor.xml
  • feature/src/main/res/values/strings.xml
  • feature/src/test/java/com/zcard/feature/ExampleUnitTest.kt
  • gradle/libs.versions.toml
  • libs/unityLibrary-release.aar
  • settings.gradle.kts
💤 Files with no reviewable changes (10)
  • feature/main/proguard-rules.pro
  • feature/main/src/main/AndroidManifest.xml
  • feature/main/.gitignore
  • feature/main/build.gradle.kts
  • feature/main/src/main/res/values/strings.xml
  • feature/card/src/test/java/com/zcard/card/ExampleUnitTest.kt
  • feature/card/src/androidTest/java/com/zcard/card/ExampleInstrumentedTest.kt
  • feature/card/src/main/java/com/zcard/card/cardshare/CardShareActivity.kt
  • feature/card/src/test/java/com/zcard/card/cardeditor/CardEditorUtilsTest.kt
  • feature/main/src/main/java/com/zcard/main/MainActivity.kt

// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.zcard.main.test", appContext.packageName)
assertEquals("com.zcard.feature.test", appContext.packageName)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Line 22의 패키지명 단정값이 현재 Gradle namespace와 불일치합니다.

feature/build.gradle.kts(Line 10-19) 기준 namespace가 com.zcard.card라서, 현재 값 com.zcard.feature.test는 계측 테스트에서 실패할 가능성이 큽니다.

수정 제안
-        assertEquals("com.zcard.feature.test", appContext.packageName)
+        assertEquals("com.zcard.card.test", appContext.packageName)

(만약 의도적으로 com.zcard.feature로 통일하려는 변경이라면, 이 테스트가 아니라 feature/build.gradle.ktsnamespace부터 함께 맞춰야 합니다.)

📝 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
assertEquals("com.zcard.feature.test", appContext.packageName)
assertEquals("com.zcard.card.test", appContext.packageName)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@feature/src/androidTest/java/com/zcard/feature/ExampleInstrumentedTest.kt` at
line 22, 테스트의 하드코딩된 패키지명(assertEquals call in ExampleInstrumentedTest.kt)과 실제
Gradle namespace(feature/build.gradle.kts의 namespace) 불일치로 계측 테스트가 실패합니다;
ExampleInstrumentedTest.kt에서 현재 assertEquals("com.zcard.feature.test",
appContext.packageName)을 실제 namespace인 "com.zcard.card"로 변경하거나(또는 의도적으로 테스트 값을
"com.zcard.feature"로 통일하려면 feature/build.gradle.kts의 namespace를 함께 변경) 둘 중 하나를
선택해 두 위치(assertEquals in ExampleInstrumentedTest.kt and namespace in
feature/build.gradle.kts)를 일치시키세요.

Comment on lines 72 to 73
val cardId = arguments?.getLong(ARG_CARD_ID) ?: -1
viewModel.onIntent(CardEditorIntent.Init(cardId))
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

cardId 기본값 -1 처리 검증 필요.

arguments가 null이거나 ARG_CARD_ID가 없을 경우 cardId가 -1로 설정됩니다. ViewModel의 Init intent가 -1을 적절히 처리하는지 확인이 필요합니다. 명시적인 에러 처리가 더 안전할 수 있습니다.

🛡️ 명시적 에러 처리 제안
-        val cardId = arguments?.getLong(ARG_CARD_ID) ?: -1
+        val cardId = arguments?.getLong(ARG_CARD_ID, -1L) ?: run {
+            Toast.makeText(requireContext(), "Invalid card ID", Toast.LENGTH_SHORT).show()
+            parentFragmentManager.popBackStack()
+            return binding.root
+        }
+        if (cardId == -1L) {
+            Toast.makeText(requireContext(), "Invalid card ID", Toast.LENGTH_SHORT).show()
+            parentFragmentManager.popBackStack()
+            return binding.root
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@feature/src/main/java/com/zcard/feature/cardeditor/CardEditorFragment.kt`
around lines 72 - 73, The fragment currently uses val cardId =
arguments?.getLong(ARG_CARD_ID) ?: -1 and blindly sends
CardEditorIntent.Init(cardId) to viewModel; update CardEditorFragment to
explicitly handle the missing/invalid card id instead of using -1: check
arguments and ARG_CARD_ID presence, and if absent or cardId is invalid, either
show an error UI/toast and return (prevent calling viewModel.onIntent) or
surface a clear error (e.g., throw IllegalArgumentException) so
CardEditorIntent.Init is only invoked with a valid id; refer to
CardEditorFragment, ARG_CARD_ID, viewModel.onIntent(...) and
CardEditorIntent.Init(...) when making the change.

Comment thread feature/src/main/java/com/zcard/feature/cardeditor/CardEditorFragment.kt Outdated
Comment on lines +184 to +190
private fun observeSpawnedObjects(flow: Flow<Result<List<CardElementWithAssetKeys>>>) {
// Room Flow는 cold flow 이므로 collect를 시작할 때마다 새로 데이터를 읽어서 emit
flow.onEach { result ->
result.onSuccess { objects ->
_cardEditorState.update { it.copy(spawnedObjects = objects) }
}
}.launchIn(viewModelScope)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

spawnedObjectsFlow 실패를 그냥 버리면 상태가 조용히 stale 됩니다.

Line 186-189는 성공 케이스만 반영해서 DB/매핑 오류가 나도 로그나 사용자 피드백 없이 에디터가 마지막 성공 상태에 머뭅니다. 이 스트림이 Result를 내보내는 계약이라면 failure도 최소한 기록하고 알려주는 편이 안전합니다.

🔧 제안 코드
 private fun observeSpawnedObjects(flow: Flow<Result<List<CardElementWithAssetKeys>>>) {
     // Room Flow는 cold flow 이므로 collect를 시작할 때마다 새로 데이터를 읽어서 emit
     flow.onEach { result ->
-        result.onSuccess { objects ->
-            _cardEditorState.update { it.copy(spawnedObjects = objects) }
-        }
+        result.onSuccess { objects ->
+            _cardEditorState.update { it.copy(spawnedObjects = objects) }
+        }
+        result.exceptionOrNull()?.let { error ->
+            Log.e(TAG, "observeSpawnedObjects: $error")
+            _cardEditorSideEffect.emit(
+                CardEditorSideEffect.ShowToast("Oops! Failed to refresh objects. Please try again.")
+            )
+        }
     }.launchIn(viewModelScope)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@feature/src/main/java/com/zcard/feature/cardeditor/CardEditorViewModel.kt`
around lines 184 - 190, The observeSpawnedObjects handler currently only applies
Result.success and silently ignores failures; update observeSpawnedObjects (the
flow.onEach block inside the observeSpawnedObjects function) to handle
result.onFailure as well by logging the Throwable (use your logger) and updating
_cardEditorState to reflect the error (e.g., set an error field or clear/mark
spawnedObjects stale) so the UI can show feedback; keep using
viewModelScope.launchIn and ensure failure handling does not crash the
collector.

@@ -1,4 +1,4 @@
package com.zcard.card.cardeditor.util
package com.zcard.feature.cardeditor.util
Copy link
Copy Markdown

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:

#!/bin/bash
# Description: 새 패키지 경로에 테스트 파일이 존재하는지 확인

# CardEditorUtils 관련 테스트 파일 검색
fd -i "cardeditorutils" --extension kt

# 전체 테스트 디렉토리에서 CardEditorUtils 참조 검색
rg -l "CardEditorUtils\|toBase62\|getObjectThumbByKey" --type kt -g '*test*' -g '*Test*'

Repository: nrj022/z-card

Length of output: 175


🏁 Script executed:

# CardEditorUtilsTest 파일 검색
find . -name "*CardEditorUtilsTest*" -type f

# 전체 테스트 파일 구조 확인
find . -path "*/test/*" -name "*CardEditorUtils*" -type f

# CardEditorUtils 관련 모든 파일 검색 (대소문자 무시)
find . -iname "*cardeditorutils*" -type f

Repository: nrj022/z-card

Length of output: 133


🏁 Script executed:

# 테스트 디렉토리 구조 파악
find . -type d -name "*test*" | head -20

# kotlin 테스트 파일들의 위치 패턴 확인
find . -name "*Test.kt" -type f | head -10

Repository: nrj022/z-card

Length of output: 906


🏁 Script executed:

# CardEditorUtils 클래스의 메서드들이 실제로 사용되는지 확인
rg "toBase62|getObjectThumbByKey" --type kotlin

Repository: nrj022/z-card

Length of output: 1698


🏁 Script executed:

# feature 모듈 테스트 디렉토리의 파일 목록 확인
find ./feature/src/test -type f -name "*.kt" 2>/dev/null

# CardEditorUtils 관련 테스트가 git 히스토리에서 삭제되었는지 확인
git log --all --full-history --oneline -- "*CardEditorUtilsTest*" 2>/dev/null | head -5

Repository: nrj022/z-card

Length of output: 313


🏁 Script executed:

# 더 광범위한 git 검색
git log --all --oneline -- "**/*CardEditorUtils*Test*" 2>/dev/null | head -10

Repository: nrj022/z-card

Length of output: 215


🏁 Script executed:

# 패키지 재정리 커밋의 변경 사항 확인
git show bfaf158 --stat | grep -i "cardeditor\|test"

Repository: nrj022/z-card

Length of output: 1154


🏁 Script executed:

# 더 구체적으로 삭제된 파일 확인
git show bfaf158 --name-status | grep -E "CardEditorUtils|Test" | head -20

Repository: nrj022/z-card

Length of output: 306


🏁 Script executed:

# 원래 경로의 테스트 파일 확인
find . -path "*cardeditor*" -name "CardEditorUtilsTest.kt" -type f

Repository: nrj022/z-card

Length of output: 39


🏁 Script executed:

# 모든 cardeditor 관련 테스트 파일 찾기
find . -name "CardEditorUtilsTest.kt" -type f 2>/dev/null

# feature-card 모듈 확인 (old module name)
find . -type d -name "card" 2>/dev/null | grep -i feature

Repository: nrj022/z-card

Length of output: 115


🏁 Script executed:

# 전체 리포지토리에서 CardEditorUtilsTest 검색
rg "CardEditorUtilsTest" --type kotlin

Repository: nrj022/z-card

Length of output: 39


🏁 Script executed:

# feature/card 모듈의 테스트 디렉토리 확인
find ./feature/card/src/test -type f -name "*.kt" 2>/dev/null | head -20

Repository: nrj022/z-card

Length of output: 39


🏁 Script executed:

# feature/card 테스트 패키지 구조 확인
find ./feature/card/src/test -type d 2>/dev/null

Repository: nrj022/z-card

Length of output: 39


🏁 Script executed:

# CardEditorUtilsTest 파일의 전체 git 히스토리 확인
git log --all --full-history --oneline -- "**/*CardEditorUtilsTest*" 2>/dev/null

# 더 광범위한 검색
git log --all --pretty=format:"%h %s" --name-status | grep -B1 "CardEditorUtilsTest" | head -20

Repository: nrj022/z-card

Length of output: 1014


🏁 Script executed:

# 최근 commits에서 test 삭제 확인
git log --all --oneline --follow -- "**/CardEditorUtilsTest.kt" 2>/dev/null

Repository: nrj022/z-card

Length of output: 215


🏁 Script executed:

# 예상 경로에서 테스트 파일 확인
find ./feature/src/test -name "CardEditorUtilsTest.kt" -type f

# feature/src/test 전체 구조 확인
find ./feature/src/test -type f -name "*.kt"

# cardeditor 관련 모든 파일 확인
find ./feature -path "*/cardeditor/*" -name "*.kt" -type f | sort

Repository: nrj022/z-card

Length of output: 2354


🏁 Script executed:

# 최신 커밋에서 test 파일 상태 확인
git ls-files | grep -i "cardeditorutils\|test" | grep -i cardeditor

Repository: nrj022/z-card

Length of output: 131


CardEditorUtilsTest.kt 테스트 파일 누락 - 복구 필요

CardEditorUtilsTest.kt이 코드베이스에서 누락되어 있습니다. Git 히스토리에서는 파일이 추적되었으나, 현재 feature/src/test/java/com/zcard/feature/cardeditor/ 경로에 파일이 존재하지 않습니다.

toBase62()getObjectThumbByKey() 유틸리티 메서드는 AssetBrowserPanel.kt과 CardEditorFragment.kt에서 활발히 사용되고 있으므로, 삭제된 테스트 파일을 복구하거나 새로운 단위 테스트를 작성해야 합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@feature/src/main/java/com/zcard/feature/cardeditor/util/CardEditorUtils.kt`
at line 1, CardEditorUtils의 단위테스트 파일 CardEditorUtilsTest.kt가 삭제되어 복구 또는 재작성해야
합니다; 복구가 불가능하면 새 테스트를 추가하여 toBase62()의 다양한 입력(0, 음수, 큰값, 경계값)과 예상 Base62 결과를
검증하고 getObjectThumbByKey()가 null/존재하는 키에 대해 올바른 썸네일 URL 또는 기본값을 반환하도록 테스트하세요;
테스트는 CardEditorUtils 클래스의 해당 정적/확장 메서드(toBase62, getObjectThumbByKey)를 직접 호출하고,
AssetBrowserPanel/ CardEditorFragment와의 통합 사용 사례가 있다면 필요한 의존성만 목(mock) 처리하거나 더미
데이터로 호출하여 실제 사용 시나리오를 재현하도록 작성하세요.

Comment thread feature/src/main/java/com/zcard/feature/home/HomeScreen.kt
Comment thread feature/src/main/java/com/zcard/feature/MainActivity.kt Outdated
Comment on lines +16 to +17
private val _mainSideEffect = MutableSharedFlow<MainSideEffect>()
val mainSideEffect: SharedFlow<MainSideEffect> = _mainSideEffect
Copy link
Copy Markdown

@coderabbitai coderabbitai bot Apr 3, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

MutableSharedFlow 기본 설정으로 Unity 메시지가 유실될 수 있습니다.

Line 16의 기본 MutableSharedFlow는 재생 버퍼가 없어, Line 25-28에서 발행된 ReceivedUnityMessage가 수집 시작 전이면 사라집니다. repeatOnLifecycle(STARTED) 수집 구조와 조합되면 간헐적 메시지 누락이 발생할 수 있습니다.

🔧 제안: Unity 메시지 전달 스트림을 버퍼/재생 가능한 경로로 분리
 class MainViewModel `@Inject` constructor(
 ) : ViewModel() {

     private val _mainSideEffect = MutableSharedFlow<MainSideEffect>()
     val mainSideEffect: SharedFlow<MainSideEffect> = _mainSideEffect
+    private val _unityMessages = MutableSharedFlow<UnityMessage>(replay = 1)
+    val unityMessages: SharedFlow<UnityMessage> = _unityMessages

     fun emitUnityMessage(message: UnityMessage) {
         viewModelScope.launch {
-            _mainSideEffect.emit(MainSideEffect.ReceivedUnityMessage(message))
+            _unityMessages.emit(message)
         }
     }
 }

Also applies to: 25-28

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@feature/src/main/java/com/zcard/feature/MainViewModel.kt` around lines 16 -
17, The current _mainSideEffect = MutableSharedFlow<MainSideEffect>() can drop
Unity messages emitted as ReceivedUnityMessage before collectors start
(repeatOnLifecycle(STARTED)); create a dedicated replayable stream for Unity
messages (e.g., a dedicated MutableSharedFlow or StateFlow) and/or configure
_mainSideEffect with replay and buffer (e.g., MutableSharedFlow(replay = 1,
extraBufferCapacity = X, onBufferOverflow = BufferOverflow.DROP_OLDEST)) so
ReceivedUnityMessage emissions are retained until collectors subscribe; update
usages of _mainSideEffect/mainSideEffect and places that emit
ReceivedUnityMessage (lines around the ReceivedUnityMessage emission) to publish
to the new replayable flow or to the reconfigured _mainSideEffect.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

#124 에서 전체적으로 리팩토링할 예정

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@nrj022, 알겠습니다! PR #124에서 전체 리팩토링 시 해당 사항도 함께 검토해 주세요. 그때 MutableSharedFlow의 재생 버퍼/ReceivedUnityMessage 유실 문제도 다시 확인하겠습니다.


✏️ Learnings added
Learnt from: nrj022
Repo: nrj022/z-card PR: 122
File: feature/src/main/java/com/zcard/feature/MainViewModel.kt:16-17
Timestamp: 2026-04-08T01:31:16.381Z
Learning: In `feature/src/main/java/com/zcard/feature/MainViewModel.kt` (nrj022/z-card), the `_mainSideEffect = MutableSharedFlow<MainSideEffect>()` has no replay buffer, which can cause `ReceivedUnityMessage` emissions to be dropped before collectors start (especially with `repeatOnLifecycle(STARTED)`). The author (nrj022) plans to address this with a full refactor in PR `#124`. Flag this issue again when reviewing PR `#124`.

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


🧠 Learnings used
Learnt from: nrj022
Repo: nrj022/z-card PR: 122
File: feature/src/main/java/com/zcard/feature/cardshare/CardShareScreen.kt:55-58
Timestamp: 2026-04-07T14:30:20.026Z
Learning: In `feature/src/main/java/com/zcard/feature/cardshare/CardShareScreen.kt`, the `TopSection` (around lines 55–58) and the bottom share button area use fixed padding without `statusBarsPadding()` or `navigationBarsPadding()`. This is intentional: Unity integration currently hides the status bar and home indicator entirely, so system inset padding is not needed. The author (nrj022) plans to add proper inset-aware padding in a future PR when Unity's status bar and navigation bar visibility settings are changed. Do not flag the missing safe-area padding in this file in future reviews.

Comment on lines +18 to +21
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
Copy link
Copy Markdown

@coderabbitai coderabbitai bot Apr 3, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

fragment_container가 전체 화면을 덮어 Unity 영역을 침범할 수 있습니다

Line 18-21은 ConstraintLayout 자식인데 제약 없이 match_parent를 사용하고 있어, unity_container 위까지 겹칠 가능성이 큽니다. 단일 화면 분할 의도(상단 Unity/하단 Fragment)가 깨지고 터치 충돌이 발생할 수 있습니다.

🧩 제약 기반 분할 레이아웃 수정안
-    <androidx.fragment.app.FragmentContainerView
-        android:id="@+id/fragment_container"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent" />
+    <androidx.fragment.app.FragmentContainerView
+        android:id="@+id/fragment_container"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        app:layout_constraintTop_toBottomOf="@id/unity_container"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent" />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@feature/src/main/res/layout/activity_card_editor.xml` around lines 18 - 21,
The FragmentContainerView with id fragment_container is using match_parent and,
as a ConstraintLayout child, can overlap the Unity view (unity_container);
change fragment_container to use constraint-based sizing (set layout_height to
0dp) and add constraints: constrain its top to the parent (or the intended top
anchor) and its bottom to the top of unity_container (or constrain its bottom to
parent and top to unity_container depending on desired ordering) so the fragment
area and unity_container are divided and cannot overlap or receive touch
conflicts.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Unity 영역 위로 겹치는 것은 의도된 화면 구성임

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@nrj022, 이해했습니다! fragment_container가 Unity 레이어 위를 전체적으로 덮는 오버레이 구조가 의도된 설계임을 확인했습니다. 향후 리뷰에서 동일한 지적이 발생하지 않도록 기록해 두겠습니다.


✏️ Learnings added
Learnt from: nrj022
Repo: nrj022/z-card PR: 122
File: feature/src/main/res/layout/activity_card_editor.xml:18-21
Timestamp: 2026-04-08T01:31:53.213Z
Learning: In `feature/src/main/res/layout/activity_card_editor.xml`, the `FragmentContainerView` (`@+id/fragment_container`) intentionally uses `match_parent` for both width and height inside the root `ConstraintLayout`, fully overlaying the `unity_container` (`@+id/unity_container`). This is a deliberate design: Fragment UI layers on top of the Unity view. Do not flag this overlap as a layout issue in future reviews.

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


🧠 Learnings used
Learnt from: nrj022
Repo: nrj022/z-card PR: 122
File: feature/src/main/java/com/zcard/feature/cardshare/CardShareScreen.kt:55-58
Timestamp: 2026-04-07T14:30:20.026Z
Learning: In `feature/src/main/java/com/zcard/feature/cardshare/CardShareScreen.kt`, the `TopSection` (around lines 55–58) and the bottom share button area use fixed padding without `statusBarsPadding()` or `navigationBarsPadding()`. This is intentional: Unity integration currently hides the status bar and home indicator entirely, so system inset padding is not needed. The author (nrj022) plans to add proper inset-aware padding in a future PR when Unity's status bar and navigation bar visibility settings are changed. Do not flag the missing safe-area padding in this file in future reviews.

Learnt from: nrj022
Repo: nrj022/z-card PR: 119
File: feature/card/src/main/java/com/zcard/card/cardeditor/CardEditorActivity.kt:299-305
Timestamp: 2026-03-28T02:15:39.286Z
Learning: In `CardEditorActivity.kt` (Android, Kotlin), `onLowMemory()` already handles severe memory pressure by calling `unityPlayer.onTrimMemory(UnityPlayerForActivityOrService.MemoryUsage.Critical)`. The `onTrimMemory()` override intentionally covers only background-transition levels (`TRIM_MEMORY_UI_HIDDEN` → Medium, `TRIM_MEMORY_BACKGROUND` → High, else → Medium). Do not flag the absence of `TRIM_MEMORY_COMPLETE` or `TRIM_MEMORY_MODERATE` handling here, as those constants are deprecated since Android API 34 and are no longer delivered to apps. The design correctly separates responsibilities between `onLowMemory()` and `onTrimMemory()`.

Learnt from: nrj022
Repo: nrj022/z-card PR: 98
File: feature/card/src/main/java/com/zcard/card/cardeditor/ui/common/BouncingLogoLoadingOverlay.kt:147-161
Timestamp: 2026-03-14T09:09:48.509Z
Learning: In `feature/card/src/main/java/com/zcard/card/cardeditor/ui/common/BouncingLogoLoadingOverlay.kt`, the `LoadingTextWithCyclingDots` composable intentionally shifts the main text label as the dot count changes (the Row width varies with dot count). This is a deliberate design choice by the author and should not be flagged as a layout issue in future reviews.

nrj022 added 4 commits April 7, 2026 23:19
- feature 모듈 네임스페이스 `com.zcard.card` -> `com.zcard.feature`로 변경
- 네임스페이스 변경에 따라 각 Fragment, Screen, Dialog 등 UI 컴포넌트 내 R 클래스 및 ViewBinding 참조 경로 수정
- 레이아웃 파일명 변경: `activity_card_editor.xml` → `activity_main.xml`
- MainActivity 내 ViewBinding 클래스를 `ActivityCardEditorBinding`에서 `ActivityMainBinding`으로 교체
- CardEditorFragment 내 시스템 뒤로가기 차단 로직 제거 및 Card ID 누락 관련 TODO 주석 추가
- HomeViewModel 내 카드 리스트 조회 방식 변경 관련 TODO 주석 추가
- MainActivity의 Unity 인터페이스 함수(`onUnityMessage`)에 난독화 방지를 위한 `@Keep` 어노테이션 적용
- CardEditorViewModel: spawnedObjects 데이터 로드 실패 시 에러 로그 출력 및 Toast 메시지(SideEffect) 노출 로직 추가
- CardEditorFragment: OnBackPressedCallback의 LifecycleOwner를 Fragment에서 viewLifecycleOwner로 변경하여 View 생명주기에 맞게 콜백이 해제되도록 수정
- CardShareFragment에서 홈 화면 전환 시 불필요한 commit 제거
Copy link
Copy Markdown

@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.

🧹 Nitpick comments (3)
feature/src/main/java/com/zcard/feature/cardeditor/CardEditorFragment.kt (1)

65-68: 반환 타입을 View로 변경하세요.

onCreateView가 항상 binding.root를 반환하므로 nullable 반환 타입 View?는 불필요합니다.

♻️ 제안
     override fun onCreateView(
         inflater: LayoutInflater, container: ViewGroup?,
         savedInstanceState: Bundle?
-    ): View? {
+    ): View {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@feature/src/main/java/com/zcard/feature/cardeditor/CardEditorFragment.kt`
around lines 65 - 68, Change the nullable return type of onCreateView to
non-nullable View since the method always returns binding.root; update the
method signature in CardEditorFragment (override fun onCreateView(...): View)
and ensure the implementation still returns binding.root and compiles with any
required imports or null-safety adjustments.
feature/src/main/java/com/zcard/feature/cardshare/CardShareScreen.kt (1)

96-127: WebView가 매 recomposition마다 URL을 다시 로드합니다.

AndroidViewupdate 블록은 recomposition이 발생할 때마다 호출됩니다. 현재 구현에서는 cardUrl이 변경되지 않아도 loadUrl()이 반복 호출되어 불필요한 네트워크 요청과 화면 깜빡임이 발생할 수 있습니다.

♻️ URL 변경 시에만 로드하도록 개선
     AndroidView(
         factory = { context ->
             val webView = WebView(context)
             webView.webViewClient = WebViewClient()

             webView.settings.apply {
                 javaScriptEnabled = true
                 domStorageEnabled = true
                 loadsImagesAutomatically = true
                 useWideViewPort = true
                 loadWithOverviewMode = true
                 builtInZoomControls = false
             }

             webView.apply {
                 layoutParams = ViewGroup.LayoutParams(
                     ViewGroup.LayoutParams.MATCH_PARENT,
                     ViewGroup.LayoutParams.MATCH_PARENT
                 )
+                loadUrl(cardUrl)
             }
         }, update = { webView ->
-            webView.loadUrl(cardUrl)
+            if (webView.url != cardUrl) {
+                webView.loadUrl(cardUrl)
+            }
         },
         onRelease = { webView ->
             webView.clearHistory()
             webView.clearCache(true)
             webView.stopLoading()
             webView.destroy()
         }
     )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@feature/src/main/java/com/zcard/feature/cardshare/CardShareScreen.kt` around
lines 96 - 127, The WebView is calling loadUrl on every recomposition because
update always invokes webView.loadUrl(cardUrl); change the logic so the WebView
is created once (CardShareWebView factory) and in the AndroidView update block
only call loadUrl when the URL actually changed — e.g., check webView.url (or a
stored lastLoadedUrl in webView.tag) against cardUrl and call
webView.loadUrl(cardUrl) only if they differ; ensure initial load happens in the
factory or on first composition and keep the onRelease cleanup as-is.
feature/src/main/java/com/zcard/feature/cardshare/CardShareFragment.kt (1)

13-13: 불필요한 import 제거 권장.

kotlin.apply는 Kotlin 표준 라이브러리에서 자동으로 사용 가능하므로 명시적 import가 필요하지 않습니다.

♻️ 제안
-import kotlin.apply
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@feature/src/main/java/com/zcard/feature/cardshare/CardShareFragment.kt` at
line 13, Remove the unnecessary explicit import "kotlin.apply" from
CardShareFragment.kt (the import at the top of CardShareFragment) since apply is
available from the standard library without import; delete that import line, run
the IDE's "optimize/organize imports" to clear any other unused imports, and
rebuild to confirm no references require the explicit import.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@feature/src/main/java/com/zcard/feature/cardeditor/CardEditorFragment.kt`:
- Around line 65-68: Change the nullable return type of onCreateView to
non-nullable View since the method always returns binding.root; update the
method signature in CardEditorFragment (override fun onCreateView(...): View)
and ensure the implementation still returns binding.root and compiles with any
required imports or null-safety adjustments.

In `@feature/src/main/java/com/zcard/feature/cardshare/CardShareFragment.kt`:
- Line 13: Remove the unnecessary explicit import "kotlin.apply" from
CardShareFragment.kt (the import at the top of CardShareFragment) since apply is
available from the standard library without import; delete that import line, run
the IDE's "optimize/organize imports" to clear any other unused imports, and
rebuild to confirm no references require the explicit import.

In `@feature/src/main/java/com/zcard/feature/cardshare/CardShareScreen.kt`:
- Around line 96-127: The WebView is calling loadUrl on every recomposition
because update always invokes webView.loadUrl(cardUrl); change the logic so the
WebView is created once (CardShareWebView factory) and in the AndroidView update
block only call loadUrl when the URL actually changed — e.g., check webView.url
(or a stored lastLoadedUrl in webView.tag) against cardUrl and call
webView.loadUrl(cardUrl) only if they differ; ensure initial load happens in the
factory or on first composition and keep the onRelease cleanup as-is.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 14e47796-baae-4acc-b867-a651fa39200e

📥 Commits

Reviewing files that changed from the base of the PR and between 36cd7b2 and 570e508.

📒 Files selected for processing (20)
  • feature/build.gradle.kts
  • feature/src/main/java/com/zcard/feature/MainActivity.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/CardEditorFragment.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/CardEditorViewModel.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/ui/common/BouncingLogoLoadingOverlay.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/ui/dialog/CardInfoDialog.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/ui/dialog/ObjectDeleteConfirmDialog.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/ui/dialog/SetCardTitleDialog.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/ui/dialog/UnsavedChangesDialog.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/ui/panels/AssetBrowserPanel.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/ui/panels/TextEditorPanel.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/ui/panels/TransformControlPanel.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/ui/uimapper/FontUiMapper.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/util/CardEditorUtils.kt
  • feature/src/main/java/com/zcard/feature/cardshare/CardShareFragment.kt
  • feature/src/main/java/com/zcard/feature/cardshare/CardShareScreen.kt
  • feature/src/main/java/com/zcard/feature/home/HomeFragment.kt
  • feature/src/main/java/com/zcard/feature/home/HomeScreen.kt
  • feature/src/main/java/com/zcard/feature/home/HomeViewModel.kt
  • feature/src/main/res/layout/activity_main.xml
✅ Files skipped from review due to trivial changes (9)
  • feature/src/main/java/com/zcard/feature/cardeditor/ui/dialog/SetCardTitleDialog.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/ui/dialog/ObjectDeleteConfirmDialog.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/util/CardEditorUtils.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/ui/panels/TransformControlPanel.kt
  • feature/src/main/res/layout/activity_main.xml
  • feature/src/main/java/com/zcard/feature/cardeditor/ui/panels/TextEditorPanel.kt
  • feature/src/main/java/com/zcard/feature/home/HomeViewModel.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/ui/dialog/CardInfoDialog.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/ui/dialog/UnsavedChangesDialog.kt
🚧 Files skipped from review as they are similar to previous changes (7)
  • feature/build.gradle.kts
  • feature/src/main/java/com/zcard/feature/cardeditor/ui/panels/AssetBrowserPanel.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/ui/common/BouncingLogoLoadingOverlay.kt
  • feature/src/main/java/com/zcard/feature/home/HomeScreen.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/ui/uimapper/FontUiMapper.kt
  • feature/src/main/java/com/zcard/feature/cardeditor/CardEditorViewModel.kt
  • feature/src/main/java/com/zcard/feature/home/HomeFragment.kt

@nrj022
Copy link
Copy Markdown
Owner Author

nrj022 commented Apr 10, 2026

@nrj022 nrj022 merged commit d70949b into develop Apr 10, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

refactor 패키지 구조, 파일 이름 등 수정

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Refactor] 단일 Activity + Fragment 구조로 앱 아키텍처 변경

1 participant