Skip to content

[Feat] common component 마이그레이션#26

Merged
oungsi2000 merged 6 commits intodevelopfrom
feat/25
Mar 2, 2026
Merged

[Feat] common component 마이그레이션#26
oungsi2000 merged 6 commits intodevelopfrom
feat/25

Conversation

@oungsi2000
Copy link

@oungsi2000 oungsi2000 commented Feb 22, 2026

#️⃣ 이슈 번호

#25


🛠️ 작업 내용

  • presentation/common 컴포넌트를 마이그레이션 하였습니다

🙇🏻 중점 리뷰 요청

landscapist가 2.3.10으로 컴파일되어 kotlin version을 2.2.20 -> 2.3.10으로 변경하였습니다

  1. 마이그레이션 하면서 사용되지 않는 Android XML 기반의 코드는 삭제하였습니다.
  2. PermissionUtil.kt에서 Legacy Snackbar를 사용하는 부분이 있습니다. 해당 부분을 ObserveAsEvents와 Snackbar Composable로 마이그레이션 하는 작업이 필요할 것 같습니다. (MainActivity에서 사용중) -> 일단 해당 부분은 떼고 마이그레이션 하였습니다
  3. ktlint가 실제로 적용되지 않아서, ktlint task를 추가했습니다
  4. 안드로이드 resources를 마이그레이션하여 파일 체인지가 많은 점 양해부탁드립니다.

일단 터지지 않게 마이그레이션 하되, 실제 뷰를 옮겼을 때, android, ios에서 문제가 되는 것은 그때 따로 수정하는 방향으로 가면 좋을 것 같습니다


📸 이미지 첨부 (Optional)

Summary by CodeRabbit

릴리스 노트

  • 새 기능
    • 재사용 가능한 UI 컴포넌트 추가: 이미지 표시(FestabookImage, CoilImage), 스위치, 상단 앱바, 로딩/오류/빈 상태 화면, 카드 배경, URL 자동 인식 텍스트, 풀투리프레시 컨테이너
  • 개선 사항
    • 앱 테마 전반 도입(색상·타이포·스페이싱·쉐이프) 및 한국어 문자열 추가
    • 다수 아이콘·배경·애니메이션 리소스 추가로 UI 강화, 이미지 로딩/처리 개선
  • 잡무(Chore)
    • 프로젝트 룰·정적검사 및 빌드 설정 추가 (.editorconfig·빌드 구성)

@oungsi2000 oungsi2000 self-assigned this Feb 22, 2026
@oungsi2000 oungsi2000 added the ✨ Feat 새로운 기능이나 기능 개선 추가 label Feb 22, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 22, 2026

📝 Walkthrough

Walkthrough

프로젝트에 ktlint 및 Gradle 설정이 추가되고 이미지 베이스 URL 빌드 설정이 도입되었습니다. 다수의 Android drawable, Lottie 애니메이션, 문자열 리소스가 추가되었고, Compose 기반 디자인 시스템(색상/모양/간격/타이포그래피)과 재사용 가능한 UI 컴포넌트·유틸리티(이미지, 상태 화면, 스낵바, URL 텍스트 등)가 새로 추가되었습니다. 일부 테스트 픽스처 및 포맷 변경도 포함됩니다.

Changes

Cohort / File(s) Summary
빌드·포맷 설정
\.editorconfig, composeApp/build.gradle.kts, gradle/libs.versions.toml, .github/workflows/cmp-ci.yml
ktlint 규칙·플러그인 및 버전/라이브러리(coil, compottie, landscapist 등) 추가, KSP/ktlint task 연결, local.properties에 IMAGE_BASE_URL_* 추가.
플랫폼·애플리케이션 진입부
composeApp/src/androidMain/kotlin/.../ImageUtil.android.kt, .../MainActivity.kt, composeApp/src/iosMain/kotlin/.../MainViewController.kt, .../Platform*.kt
Android 전용 vector→Bitmap 유틸 추가, Preview 가시성 변경(공개→private), iOS에 ktlint 억제 주석 추가 및 포맷 정리(무해한 변경).
리소스 — Drawable: 배경/셀렉터
composeApp/src/commonMain/composeResources/drawable/bg_*.xml, selector_*.xml, sel_time_tag.xml
다양한 배경(shape/gradient/stroke) 및 상태별 셀렉터 drawable 추가.
리소스 — Drawable: 아이콘/로고/버튼
composeApp/src/commonMain/composeResources/drawable/ic_*.xml, btn_*.xml, logo_*.xml, ic_launcher_*
앱 전반에서 사용할 벡터 아이콘, 선택 상태 아이콘, 로고 및 런처 전경/배경 그래픽 대량 추가.
리소스 — 애니메이션/파일
composeApp/src/commonMain/composeResources/files/loading.json, pulse_circle.json
Lottie 애니메이션 JSON 파일 추가(로딩/펄스).
리소스 — 문자열
composeApp/src/commonMain/composeResources/values/strings.xml
한국어 UI 문자열 리소스 대량 추가(앱 전반의 라벨·메시지).
설계 시스템 (Theme)
.../presentation/theme/FestabookColor.kt, FestabookShapes.kt, FestabookSpacing.kt, FestabookTypography.kt, FestabookTheme.kt
색상 팔레트, 모양(rounded shapes), 간격 토큰, 타이포그래피 및 FestabookTheme 컴포저블과 CompositionLocal 등록 추가.
공통 유틸리티
composeApp/src/commonMain/kotlin/.../ImageUtil.kt, TextUtil.kt, ObserveEvent.kt, OnMenuItemReClickListener.kt
이미지 URL 변환 확장, 페스티벌 기간 포맷터, Lifecycle-aware Flow 이벤트 관찰용 Composable, 메뉴 재클릭용 fun interface 추가.
재사용 컴포넌트 — 기본 UI
.../presentation/common/component/CardBackground.kt, CoilImage.kt, FestabookImage.kt, FestabookSwitch.kt, FestabookTopAppBar.kt
카드 배경 modifier, Coil 기반 이미지 컴포저블 및 팝업/줌 지원, 커스텀 스위치, 탑 앱바 등 UI 컴포넌트 추가.
재사용 컴포넌트 — 상태/상호작용
.../component/EmptyStateScreen.kt, ErrorStateScreen.kt, LoadingStateScreen.kt, PullToRefreshContainer.kt, SnackBar.kt, URLText.kt
빈·오류·로딩 상태 화면, 풀-투-리프레시 컨테이너, 스낵바 매니저/호스트, URL 자동 감지 텍스트 등 상호작용 컴포넌트 추가.
테스트 픽스처·테스트 파일
composeApp/src/commonTest/.../Place*Repository*.kt, fixture/*
테스트 픽스처 재구성(구조 변경 및 포맷 수정)과 일부 불필요한 import 제거 및 포맷 정리.
공개 API 변경 (호환성 주목)
composeApp/src/commonMain/kotlin/com/daedan/festabook/App.kt
App() 시그니처가 App(modifier: Modifier = Modifier)로 변경되어 호출부 영향 가능.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

🛠️ Refactor

Suggested reviewers

  • etama123
  • parkjiminnnn

Poem

🐰
아이콘 반짝, 코드에 당근 심었네,
색과 모양이 모여 테마가 되었네.
코일로 불빛 담고, 팝업은 톡톡,
빌드가 춤추네, 모두 축하해요! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목 '[Feat] common component 마이그레이션'은 주요 변경 사항인 공통 컴포넌트의 마이그레이션을 명확하게 요약하고 있으며, 개발자의 관점에서 가장 중요한 변경을 강조하고 있습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/25

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

@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: 19

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

context: Context,
vectorResId: Int,
): Bitmap {
val drawable = AppCompatResources.getDrawable(context, vectorResId)!!
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

!! 강제 언래핑으로 인한 NPE 위험

AppCompatResources.getDrawable은 잘못된 리소스 ID, 테마 불일치, 또는 drawable 로드 실패 시 null을 반환할 수 있습니다. 현재 !!를 사용하면 이 경우 NullPointerException이 발생하여 앱이 크래시됩니다.

🐛 수정 제안
-    val drawable = AppCompatResources.getDrawable(context, vectorResId)!!
+    val drawable =
+        requireNotNull(AppCompatResources.getDrawable(context, vectorResId)) {
+            "vectorResId($vectorResId)에 해당하는 drawable을 로드할 수 없습니다."
+        }

또는 호출부에서 null 처리가 가능하도록 반환 타입을 Bitmap?으로 변경하는 방법도 고려할 수 있습니다.

-fun vectorToBitmap(
+fun vectorToBitmap(
     context: Context,
     vectorResId: Int,
-): Bitmap {
-    val drawable = AppCompatResources.getDrawable(context, vectorResId)!!
+): Bitmap? {
+    val drawable = AppCompatResources.getDrawable(context, vectorResId) ?: return null
📝 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
val drawable = AppCompatResources.getDrawable(context, vectorResId)!!
val drawable =
requireNotNull(AppCompatResources.getDrawable(context, vectorResId)) {
"vectorResId($vectorResId)에 해당하는 drawable을 로드할 수 없습니다."
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@composeApp/src/androidMain/kotlin/com/daedan/festabook/ImageUtil.android.kt`
at line 13, The line forcing non-null on AppCompatResources.getDrawable (the
'drawable' variable) can throw NPE; replace the !! with a safe call and handle
the null case: call AppCompatResources.getDrawable(context, vectorResId) without
!!, check if the result is null, and then either log and return a safe fallback
(or a default bitmap) or propagate null by changing the function's return type
to Bitmap?; update any callers accordingly and reference the 'drawable' variable
(and the surrounding function that converts the drawable to a bitmap) when
implementing the null-check and fallback logic.

Comment on lines +1 to +11
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">

<solid android:color="@color/gray050" />

<stroke
android:width="1dp"
android:color="@color/gray200" />

<corners android:radius="10dp" />
</shape> 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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if the file exists and explore related drawable files
find . -type f -name "*.xml" -path "*/drawable*" | grep -E "(bg_gray|drawable)" | head -20

Repository: festabook/kotlin-multiplatform

Length of output: 1773


🏁 Script executed:

#!/bin/bash
# Search for the specific files mentioned in the review
fd "bg_gray.*\.xml" --type f

Repository: festabook/kotlin-multiplatform

Length of output: 1034


🏁 Script executed:

#!/bin/bash
# Check project structure to understand Compose Multiplatform setup
ls -la composeApp/src/commonMain/composeResources/drawable/ 2>/dev/null | head -20

Repository: festabook/kotlin-multiplatform

Length of output: 1629


🏁 Script executed:

#!/bin/bash
# Look for color resource definitions
find . -type f -name "colors.xml" -o -name "colors.kt" | head -10

Repository: festabook/kotlin-multiplatform

Length of output: 56


🏁 Script executed:

#!/bin/bash
# Read the target file content
cat -n composeApp/src/commonMain/composeResources/drawable/bg_gray_050_stroke_gray200_radius_10dp.xml

Repository: festabook/kotlin-multiplatform

Length of output: 424


🏁 Script executed:

#!/bin/bash
# Read the sibling file for comparison
cat -n composeApp/src/commonMain/composeResources/drawable/bg_gray050_radius_10dp.xml

Repository: festabook/kotlin-multiplatform

Length of output: 313


🏁 Script executed:

#!/bin/bash
# Search for color resource definitions
find . -type f \( -name "*.kt" -o -name "*.xml" \) | xargs grep -l "gray050\|gray200" | grep -i color | head -10

Repository: festabook/kotlin-multiplatform

Length of output: 166


🏁 Script executed:

#!/bin/bash
# Check build.gradle files for compose resources configuration
find . -name "build.gradle.kts" -o -name "build.gradle" | head -5

Repository: festabook/kotlin-multiplatform

Length of output: 122


🏁 Script executed:

#!/bin/bash
# Check FestabookColor.kt for color definitions
cat -n composeApp/src/commonMain/kotlin/com/daedan/festabook/presentation/theme/FestabookColor.kt

Repository: festabook/kotlin-multiplatform

Length of output: 1313


🏁 Script executed:

#!/bin/bash
# Check build.gradle.kts for compose resources
cat -n composeApp/build.gradle.kts | head -100

Repository: festabook/kotlin-multiplatform

Length of output: 4678


🏁 Script executed:

#!/bin/bash
# Search for references to these XML files in the codebase
rg "bg_gray_050_stroke_gray200_radius_10dp|bg_gray050_radius_10dp" --type kotlin --type xml

Repository: festabook/kotlin-multiplatform

Length of output: 56


🏁 Script executed:

#!/bin/bash
# Search for where color resources (gray050, gray200) are defined
find . -type f -name "*.xml" | xargs grep -l "gray050\|gray200" | head -20

Repository: festabook/kotlin-multiplatform

Length of output: 776


🏁 Script executed:

#!/bin/bash
# Search for values/colors.xml or similar
find . -path "*/values*" -name "*.xml" | head -10

Repository: festabook/kotlin-multiplatform

Length of output: 189


🏁 Script executed:

#!/bin/bash
# Check if there are any color resource files in androidMain
find ./composeApp/src/androidMain -type f -name "*.xml" | head -20

Repository: festabook/kotlin-multiplatform

Length of output: 456


🏁 Script executed:

#!/bin/bash
# Check what FestabookColor.kt actually defines for gray050
rg "gray050" --type kotlin --type xml -C 3

Repository: festabook/kotlin-multiplatform

Length of output: 3806


🏁 Script executed:

#!/bin/bash
# Check if there's a colors.xml file anywhere
find . -name "colors.xml" -type f

Repository: festabook/kotlin-multiplatform

Length of output: 56


🏁 Script executed:

#!/bin/bash
# Check if `@color` references in XML files are actually resolved
# Look for any Android resource files that might define these colors
find ./composeApp -type f -name "*.xml" | xargs grep -l "gray050\|gray200" | xargs cat

Repository: festabook/kotlin-multiplatform

Length of output: 2733


🏁 Script executed:

#!/bin/bash
# Check if there's a way colors are being defined in the compose resources
ls -la composeApp/src/commonMain/composeResources/values/

Repository: festabook/kotlin-multiplatform

Length of output: 246


🏁 Script executed:

#!/bin/bash
# Look for all color definitions in the project (Kotlin files)
rg "gray050|gray200" --type kotlin -B 2 -A 2

Repository: festabook/kotlin-multiplatform

Length of output: 4003


🏁 Script executed:

#!/bin/bash
# Search for where `@color` resources might be resolved
# Check if there's a mechanism to map `@color` references to FestabookColor
rg "@color" --type kotlin --type xml | head -30

Repository: festabook/kotlin-multiplatform

Length of output: 3976


🏁 Script executed:

#!/bin/bash
# Check the composeApp structure for any color resource mapping
find ./composeApp/src -type f -name "*.kt" | xargs grep -l "color.*gray050\|gray050.*color" | head -5

Repository: festabook/kotlin-multiplatform

Length of output: 56


🏁 Script executed:

#!/bin/bash
# Check how drawable resources are actually used in the codebase
rg "bg_gray050_radius_10dp|bg_gray_050_stroke_gray200_radius_10dp" --type kotlin

Repository: festabook/kotlin-multiplatform

Length of output: 56


🏁 Script executed:

#!/bin/bash
# Check Compose Multiplatform documentation about drawable support
# by looking at how resources are imported/used
rg "painterResource|DrawableResource" --type kotlin | head -20

Repository: festabook/kotlin-multiplatform

Length of output: 1340


🏁 Script executed:

#!/bin/bash
# Check if gray050 is supposed to be white or another color
# Look at the color values in FestabookColor.kt to find what could be gray050
cat -n composeApp/src/commonMain/kotlin/com/daedan/festabook/presentation/theme/FestabookColor.kt

Repository: festabook/kotlin-multiplatform

Length of output: 1313


🏁 Script executed:

#!/bin/bash
# Check if there's any mapping or bridge between `@color` references and FestabookColor
rg "colors\.xml|@color" --type kotlin | head -20

Repository: festabook/kotlin-multiplatform

Length of output: 56


🏁 Script executed:

#!/antml:thinking
Let me understand the issue better. The original review comment claims that:
1. <shape> drawables are not supported for cross-platform builds
2. `@color/`* references cause issues
3. Naming inconsistency between gray050 and gray_050

From my findings:
1. The `@color` references in XML are NOT resolved - there's no colors.xml defining them
2. The FestabookColor.kt doesn't have gray050 - only gray100 and up to gray800
3. XML drawables ARE being used in the codebase via painterResource()

This is a critical issue - the `@color` references won't work at runtime.
</thinking>

Repository: festabook/kotlin-multiplatform

Length of output: 492


🏁 Script executed:

#!/bin/bash
# Check how many files have `@color` references that won't resolve
find ./composeApp/src/commonMain/composeResources/drawable -name "*.xml" -exec grep -l "@color" {} \; | wc -l

Repository: festabook/kotlin-multiplatform

Length of output: 76


🏁 Script executed:

#!/bin/bash
# Look for any build time color processing or resource mapping
find . -name "*.gradle.kts" -o -name "*.gradle" | xargs grep -i "color\|resource" | grep -i "compose\|drawable" | head -20

Repository: festabook/kotlin-multiplatform

Length of output: 279


🏁 Script executed:

#!/bin/bash
# Check if these specific drawable files are being used/referenced in Kotlin code
rg "bg_gray050_radius_10dp|bg_gray_050_stroke_gray200_radius_10dp|bg_gray100_stroke_gray200|bg_gray050_stroke_gray200_radius_24dp" --type kotlin

Repository: festabook/kotlin-multiplatform

Length of output: 56


🏁 Script executed:

#!/bin/bash
# Check what gray050 should map to based on color values
# gray050 might be intended as a very light color (50 shade)
# Let's check if white could be the intended color
rg "white\|0xFFFAFAFA" --type kotlin | head -10

Repository: festabook/kotlin-multiplatform

Length of output: 56


🏁 Script executed:

#!/antml:thinking>
I need to understand if:
1. These XML drawables are actually being referenced in the code
2. If the `@color` references would fail at runtime
3. Whether gray050 might be intended as white or another color
</thinking>

Repository: festabook/kotlin-multiplatform

Length of output: 341


🏁 Script executed:

#!/bin/bash
# Search for Res.drawable references to these files
rg "Res\.drawable\.bg_gray" --type kotlin

Repository: festabook/kotlin-multiplatform

Length of output: 56


🏁 Script executed:

#!/bin/bash
# Check if there's any build-time color XML generation or processing
find . -name "*.kt" -path "*/build*" -o -name "*.kts" -path "*/build*" | xargs grep -i "color\|drawable" 2>/dev/null | head -20

Repository: festabook/kotlin-multiplatform

Length of output: 56


🏁 Script executed:

#!/bin/bash
# Check if these drawable resources are actually being compiled into Res
# by looking for generated Res.drawable code
find . -path "*/generated/*" -name "*.kt" 2>/dev/null | xargs grep -l "bg_gray050_radius_10dp\|bg_gray_050_stroke_gray200_radius_10dp" 2>/dev/null | head -5

Repository: festabook/kotlin-multiplatform

Length of output: 56


🏁 Script executed:

#!/bin/bash
# Check the actual build output or gradle build cache
find . -path "*/.gradle/*" -name "*Res*.kt" 2>/dev/null | head -5

Repository: festabook/kotlin-multiplatform

Length of output: 56


🏁 Script executed:

#!/bin/bash
# Look for any test or example files that might use these drawables
rg "drawable\." --type kotlin --glob "**/*Test*" --glob "**/*Example*" | grep bg_gray

Repository: festabook/kotlin-multiplatform

Length of output: 56


🏁 Script executed:

#!/bin/bash
# Check if there's a way colors are supposed to be mapped in compose resources
ls -la composeApp/build/ 2>/dev/null | head -20

Repository: festabook/kotlin-multiplatform

Length of output: 56


🏁 Script executed:

#!/bin/bash
# Verify that `@color/gray050` is truly undefined - check ALL possible locations
find . -type f \( -name "*.xml" -o -name "*.kt" \) | xargs grep -h "gray050" 2>/dev/null | sort | uniq

Repository: festabook/kotlin-multiplatform

Length of output: 215


🏁 Script executed:

#!/bin/bash
# Check if there's a colors resource file somewhere that would define these colors
find . -name "*.xml" -o -name "*.json" | xargs grep -l "gray050\|gray200" 2>/dev/null | grep -v drawable

Repository: festabook/kotlin-multiplatform

Length of output: 56


🏁 Script executed:

#!/bin/bash
# Understand Compose Multiplatform resource compilation - check if there's any docs or config
cat composeApp/build.gradle.kts | grep -A 20 "resources"

Repository: festabook/kotlin-multiplatform

Length of output: 1712


🏁 Script executed:

#!/bin/bash
# Final check: verify if gray050 exists anywhere as a defined color
rg "gray050\s*=" --type kotlin --type xml

Repository: festabook/kotlin-multiplatform

Length of output: 56


🏁 Script executed:

#!/bin/bash
# Check if these problematic XML files are actually imported/used anywhere
# by searching for Res.drawable references to these specific files
find . -type f -name "*.kt" | xargs grep -l "bg_gray050\|bg_gray_050" 2>/dev/null

Repository: festabook/kotlin-multiplatform

Length of output: 58


🏁 Script executed:

#!/antml:thinking>
I need to be careful here and look for all usages, not just direct references.
</thinking>

Repository: festabook/kotlin-multiplatform

Length of output: 218


🏁 Script executed:

#!/bin/bash
# Check the complete list of color definitions available
rg "val gray|Color\s*=" --type kotlin composeApp/src/commonMain/kotlin/com/daedan/festabook/presentation/theme/

Repository: festabook/kotlin-multiplatform

Length of output: 1990


🏁 Script executed:

#!/bin/bash
# Final verification: check if this file would compile by looking for any error logs or build failures
find . -name "*.log" -o -name "*error*" | head -5

Repository: festabook/kotlin-multiplatform

Length of output: 149


🏁 Script executed:

#!/bin/bash
# Verify the exact issue - gray050 vs gray100 (white is the lightest)
# Check if gray050 should be white or if it's truly missing
rg "white|0xFFFAFAFA" --type kotlin composeApp/src/commonMain/kotlin/com/daedan/festabook/presentation/theme/

Repository: festabook/kotlin-multiplatform

Length of output: 344


@color/gray050 참조 오류 + 파일명 불일치

이 XML 파일이 참조하는 @color/gray050는 프로젝트의 FestabookColor.kt에 정의되지 않았습니다. FestabookColor에는 white, gray100부터 gray800까지만 정의되어 있으므로, 이 파일은 컴파일 또는 런타임 시 색상 리소스를 찾을 수 없어 오류가 발생합니다.

추가로, 이 파일의 이름(bg_gray_050)이 자매 파일(bg_gray050)과 gray 뒤 언더스코어 유무가 달라 참조 시 혼란을 줄 수 있습니다.

색상을 Compose 코드에서 직접 정의하거나, FestabookColorgray050 색상을 추가하고 XML을 수정하세요.

🔧 Compose 코드로 대체 예시
Modifier.background(
    color = FestabookColor.white,  // 또는 추가할 gray050
    shape = RoundedCornerShape(10.dp)
).border(
    width = 1.dp,
    color = FestabookColor.gray200,
    shape = RoundedCornerShape(10.dp)
)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@composeApp/src/commonMain/composeResources/drawable/bg_gray_050_stroke_gray200_radius_10dp.xml`
around lines 1 - 11, The drawable references a non-existent `@color/gray050` and
the filename uses inconsistent underscore naming; either add a gray050 color to
FestabookColor (e.g., add a val gray050 = Color(...) in FestabookColor) and keep
the XML as-is (bg_gray_050...), or update the XML to use an existing color
(e.g., FestabookColor.white or gray100) and rename the file to match the
project's naming convention (align with bg_gray050) so references are
consistent; make sure any Compose usages reference the same FestabookColor
symbol (FestabookColor.gray050 or the chosen existing color).

Comment on lines +1 to +6
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/gray100" />
<stroke android:width="1dp" android:color="@color/gray200"/>
<corners android:radius="10dp" />
</shape> 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 | 🔴 Critical

<shape> 드로어블은 CMP composeResources에서 지원되지 않습니다

bg_gray400_radius_50dp.xml과 동일한 문제입니다. @color/gray100, @color/gray200은 Android 리소스 참조이므로 CMP XML 벡터 드로어블은 Android 리소스에 대한 외부 참조를 지원하지 않습니다.

추가로, <shape> 요소에 android:shape="rectangle" 속성이 누락되어 있습니다 (기본값은 rectangle이지만 명시적 선언이 일관성을 높입니다).

권장 대안: Compose API로 대체하세요.

♻️ 권장 대안 (Composable에서 직접 사용)
// bg_gray100_stroke_gray200_radius_10dp.xml 대신 Compose 코드로 대체
Modifier
    .background(
        color = FestabookColor.gray100,
        shape = RoundedCornerShape(10.dp)
    )
    .border(
        width = 1.dp,
        color = FestabookColor.gray200,
        shape = RoundedCornerShape(10.dp)
    )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@composeApp/src/commonMain/composeResources/drawable/bg_gray100_stroke_gray200_radius_10dp.xml`
around lines 1 - 6, The XML <shape> drawable
bg_gray100_stroke_gray200_radius_10dp is not supported by CMP composeResources
(and also missing the explicit android:shape="rectangle"), so remove this XML
and replace all usages of the resource name
bg_gray100_stroke_gray200_radius_10dp (and similar bg_gray400_radius_50dp) with
a Compose implementation using Modifier.background(color =
FestabookColor.gray100, shape = RoundedCornerShape(10.dp)) and .border(width =
1.dp, color = FestabookColor.gray200, shape = RoundedCornerShape(10.dp)); if you
absolutely must keep an XML fallback, add android:shape="rectangle" to the
<shape> and replace Android color references with hard-coded color values (no
`@color/`* references), but preferred fix is to migrate callers to the Compose
Modifier above.

Comment on lines +1 to +6
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="50dp" />
<solid android:color="@color/gray400" />
</shape> 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 | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Compose Multiplatform composeResources drawable support shape XML

💡 Result:

composeResources/drawable in Compose Multiplatform resources supports:

  • Raster images: .png, .jpg, .bmp, .webp (etc.) [1][2]
  • Android XML vector drawables (<vector>...</vector>), with the limitation that they can’t reference Android resources (like @color/..., ?attr/..., etc.) [1][2]

It does not support Android “shape” drawables (XML like <shape>...</shape> that compile to GradientDrawable) as a drawable resource for painterResource(Res.drawable.*)—those are a different Android drawable type than vector XML. [2][3]

What to do instead

  • Recreate the shape in Compose UI (e.g., Modifier.background(color, shape), RoundedCornerShape, BorderStroke, gradients/brushes).
  • Or convert the asset to a vector drawable (<vector>) or a raster image.
  • If you only need it on Android, keep it as an Android res/drawable/*.xml in androidMain and load it with Android-specific resource APIs (not Res.drawable).

Sources: Kotlin Multiplatform resources docs for supported drawable formats [1][2] and Android “shape drawable” definition [3].


🌐 Web query:

CMP Kotlin Multiplatform compose resources Android drawable limitations

💡 Result:

Compose Multiplatform (CMP) composeResources drawables on Android — key limitations

  • Supported image formats in composeResources/drawable: raster (.png, .jpg, .bmp, .webp) and Android XML vector drawables (.xml). SVG is supported on all targets except Android. [1][2]
  • Android XML vector drawable restrictions: vectors must be “Android-style,” but they cannot use external references to Android resources (for example @color/..., ?attr/..., @dimen/..., etc.). Inline values are required. [1][2]
  • Packaging on Android: starting with Compose Multiplatform 1.7.0, multiplatform resources are packaged into Android assets (not res/drawable), so they’re accessed via CMP APIs like painterResource(Res.drawable...) / Res.getUri(...), not Android’s R.drawable. [1]
  • If you actually need Android res/drawable behavior: with the newer Android KMP Gradle plugin, Android resource processing isn’t enabled by default—you must opt in (androidResources { enable = true }) and place files under src/androidMain/res/.... This is separate from composeResources. [3]
  • Practical consequence for SVG on Android: attempting to use SVG as a drawable resource on Android won’t work (it’s a known limitation; users often need to convert SVG → Android vector XML or use another SVG loading approach). [1][4]

Sources:
[1] Kotlin Multiplatform docs – “Using multiplatform resources” (images, SVG exception on Android, vector restrictions, Android assets packaging)
[2] Kotlin Multiplatform docs – “Setup and configuration for multiplatform resources” (supported drawable types, vector restriction)
[3] Android Developers – “Set up the Android Gradle Library Plugin for KMP” (opt-in Android resources processing)
[4] JetBrains compose-multiplatform GitHub issue #4670 (Android doesn’t support SVG resources)


🌐 Web query:

Compose Multiplatform shape drawable @color reference support

💡 Result:

Compose Multiplatform’s composeResources does not support Android XML shape drawables (like <shape>, <selector>, <layer-list>). The resource loader APIs (painterResource, vectorResource) only handle raster images and Android XML vector drawables. [1]

For Android XML vector drawables in composeResources, Compose Multiplatform also does not support external Android resource references, so things like android:fillColor="@color/...", @android:color/..., or theme attrs won’t work. You need to inline literal colors (e.g. #RRGGBB) and apply tinting from Compose (for example via colorFilter = ColorFilter.tint(...)). [1]

If you specifically need shape drawables with @color references, that’s an Android platform feature (supported by Android resource system), but it’s not portable through Compose Multiplatform resources. [2]

Sources:
[1] Kotlin Multiplatform docs – Compose Multiplatform resources (drawable/vector limitations, no external references) (kotlinlang.org)
[2] Android Developers – Drawable resources / shape drawable and color resource references (developer.android.com)

Citations:


<shape> 드로어블은 CMP composeResources에서 지원되지 않습니다

Compose Multiplatform의 composeResources는 래스터 이미지(PNG, JPG 등)와 벡터 Android XML 드로어블만 지원하며, <shape> 드로어블은 포함되지 않습니다. 또한 @color/gray400 같은 Android 리소스 참조는 CMP 드로어블에서 사용할 수 없습니다. 이 파일은 빌드 실패 또는 런타임 크래시를 유발할 수 있습니다.

해결 방법: 배경이 필요한 Composable에서 Compose API로 직접 구현하세요.

권장 대안
Modifier.background(
    color = FestabookColor.gray400,
    shape = RoundedCornerShape(50.dp)
)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@composeApp/src/commonMain/composeResources/drawable/bg_gray400_radius_50dp.xml`
around lines 1 - 6, The bg_gray400_radius_50dp.xml uses an Android <shape>
drawable and `@color` resource which Compose Multiplatform composeResources
doesn't support; remove this XML and replace usages with a Compose background
implementation (e.g., use Modifier.background with FestabookColor.gray400 and
RoundedCornerShape(50.dp)) in the Composables that referenced
bg_gray400_radius_50dp.xml, updating any imports/usages accordingly so no
Android XML drawable or `@color/gray400` resource is relied upon at runtime.

Comment on lines +1 to +3
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">

<path android:fillColor="@android:color/white" android:pathData="M480,616L240,376L296,320L480,504L664,320L720,376L480,616Z"/>
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

🧩 Analysis chain

🏁 Script executed:

# Check if the file exists and examine its content
cat -n "composeApp/src/commonMain/composeResources/drawable/ic_arrow_down.xml"

Repository: festabook/kotlin-multiplatform

Length of output: 451


🏁 Script executed:

# Search for similar patterns in other drawable files
rg --type xml 'android:tint|@android:color' --glob '**/composeResources/drawable/**' -A 2

Repository: festabook/kotlin-multiplatform

Length of output: 5785


🏁 Script executed:

# Check for other vector drawable files in the commonMain resources
fd -e xml '\.xml$' 'composeApp/src/commonMain/composeResources/drawable/'

Repository: festabook/kotlin-multiplatform

Length of output: 7426


🌐 Web query:

JetBrains Compose Multiplatform composeResources vector drawable android:tint support specification

💡 Result:

JetBrains Compose Multiplatform composeResources supports Android XML Vector Drawable files only as a cross‑platform subset:

  • No Android resource references inside the XML (for example ?attr/..., @android:color/..., @color/...). Vector XMLs must be self-contained. [1]
  • android:tint is not supported for these multiplatform-loaded vector XMLs; JetBrains’ official guidance is to remove android:tint and instead set a concrete android:fillColor (commonly #000000) in the vector XML. [2]
  • Apply tint in Compose code instead:
    • With Image(...) use colorFilter = ColorFilter.tint(...). [2]
    • Or use Icon(...) and its tint parameter. [3]

(For reference, android:tint is an Android VectorDrawable / VectorDrawableCompat attribute on Android itself, but Compose Multiplatform’s resource vector loader doesn’t honor it.) [4]

Sources: [1] [2] [3] [4]


🌐 Web query:

Compose Multiplatform composeResources XML vector drawable @android:color resource reference support

💡 Result:

Compose Multiplatform composeResources XML vector drawables do not support external references to Android resources, so references like:

  • android:fillColor="@android:color/white"
  • android:tint="?attr/colorControlNormal"
  • (and similarly @color/..., ?attr/..., etc.)

are not supported when the vector is loaded from composeResources (e.g., via painterResource(Res.drawable.xxx)). (kotlinlang.org)

What to do instead

  • Inline the color in the vector (e.g., android:fillColor="#FFFFFFFF"), and remove Android-only tint/theme attributes. (kotlinlang.org)
  • Tint in Compose code (cross-platform) using colorFilter = ColorFilter.tint(...) when displaying the painter. (kotlinlang.org)
  • If you truly need Android resource/theme resolution, keep an Android-specific drawable under androidMain/res/drawable and load it with Android’s resource mechanisms (separate from composeResources). (developer.android.com)

Citations:


android:tint 제거 및 @android:color/white → 하드코딩 HEX 값으로 교체 필수

두 가지 문제가 모두 존재하며, 이는 Compose Multiplatform 공식 사양을 위반합니다:

  1. android:tint="#000000": Compose Multiplatform의 composeResources에서 android:tint 속성은 지원되지 않습니다. JetBrains 공식 문서는 이를 제거하고 android:fillColor에 구체적인 HEX 값을 설정하도록 명시합니다. Compose 코드에서 colorFilter = ColorFilter.tint(...)로 색상을 동적으로 적용하세요.

  2. android:fillColor="@android:color/white": composeResources의 XML 벡터 드로어블은 @android:color/..., @color/..., ?attr/... 등 Android 리소스에 대한 외부 참조를 지원하지 않습니다. 비-Android 플랫폼(iOS, Desktop, Web)에서 렌더링이 실패합니다. 모든 색상은 #FFFFFFFF 같은 하드코딩된 HEX 값이어야 합니다.

🐛 제안된 수정사항
-<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
       
-    <path android:fillColor="@android:color/white" android:pathData="M480,616L240,376L296,320L480,504L664,320L720,376L480,616Z"/>
+    <path android:fillColor="#000000" android:pathData="M480,616L240,376L296,320L480,504L664,320L720,376L480,616Z"/>

사용 측 Composable에서 필요한 색상으로 tint를 적용하세요:

Image(
    painter = painterResource(Res.drawable.ic_arrow_down),
    contentDescription = "arrow down",
    colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurface)
)

다른 아이콘 XML 파일에도 동일한 패턴이 있는지 확인하세요. rg --type xml 'android:tint|@android:color' --glob '**/composeResources/drawable/**' 실행 결과 ic_arrow_down.xml 외에 최소 5개 파일(ic_close.xml, ic_close_24.xml, ic_arrow_back.xml, ic_arrow_forward_right.xml, ic_arrow_right.xml)이 같은 문제를 가집니다.

📝 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
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M480,616L240,376L296,320L480,504L664,320L720,376L480,616Z"/>
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
<path android:fillColor="#000000" android:pathData="M480,616L240,376L296,320L480,504L664,320L720,376L480,616Z"/>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@composeApp/src/commonMain/composeResources/drawable/ic_arrow_down.xml` around
lines 1 - 3, Remove the unsupported android:tint attribute from the vector
(e.g., in ic_arrow_down.xml) and replace any resource color references like
android:fillColor="@android:color/white" with a hardcoded ARGB hex (e.g.,
android:fillColor="#FFFFFFFF"); then ensure the runtime tint is applied from
Compose using colorFilter = ColorFilter.tint(...) in the Image/ImagePainter call
(e.g., when referencing Res.drawable.ic_arrow_down) and scan other
composeResources/drawable XMLs (ic_close.xml, ic_close_24.xml,
ic_arrow_back.xml, ic_arrow_forward_right.xml, ic_arrow_right.xml) for the same
two patterns to fix them similarly.

@@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
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

🧩 Analysis chain

🌐 Web query:

Compose Multiplatform composeResources drawable support aapt:attr XML vector

💡 Result:

composeResources in Compose Multiplatform can load Android XML VectorDrawable files, but it does not support “external references to Android resources” inside those XML vectors. (kotlinlang.org)

That restriction typically breaks vectors that use things like:

  • theme attrs / resource refs: ?attr/..., @color/..., @android:color/...
  • android:tint="?attr/..." (common in Material icon downloads)
  • aapt:attr blocks (used for resource/theming indirection in some exported vectors)

What to do instead (works with composeResources)

  1. Make the vector self-contained:

    • Replace theme/resource-based colors with a literal color (often #000000).
    • Remove android:tint and similar attribute-based styling.
      Kotlin’s docs show this exact adjustment pattern for downloaded icons (remove android:tint, set a fixed android:fillColor). (kotlinlang.org)
  2. Apply tint/styling in Compose, not in the XML:

    • Use colorFilter = ColorFilter.tint(...) with Image(...), or tint = ... with Icon(...) (depending on which API you’re using). (kotlinlang.org)

If you paste the problematic vector XML (especially the part with aapt:attr), I can rewrite it into a CMP-compatible version.

Citations:


🌐 Web query:

ic_launcher_foreground.xml placement KMP Kotlin Multiplatform Android main resources

💡 Result:

In a Kotlin Multiplatform (KMP) project, the launcher icon resources are still Android resources, so they should live under the Android source set (not commonMain/composeResources).

Where to put them (recommended)

Put your adaptive icon files exactly like a normal Android app, but under androidMain:

  • composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml
  • composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml (optional)
  • composeApp/src/androidMain/res/drawable/ic_launcher_foreground.xml
  • composeApp/src/androidMain/res/drawable/ic_launcher_background.xml (or res/color/ic_launcher_background.xml, depending on how you generate it)

This matches how adaptive icons reference their layers (foreground / background) and where Android expects them. [3]

Why androidMain/res (and not multiplatform composeResources)?

KMP/Compose Multiplatform “resources” (commonMain/composeResources → generated Res) are a separate resource system intended for cross-platform usage in code, while the launcher icon is resolved by Android’s packaging/resource system (R.*) during install/launch. So these icon XMLs belong in Android’s res/ for the Android target. [4][6][1]

Note on the KMP Android layout

With the “new Android source set layout”, Android-specific files (like AndroidManifest.xml, res/, etc.) are expected under src/androidMain/.... [6]


🏁 Script executed:

# Check if the file exists and examine its content
if [ -f "composeApp/src/commonMain/composeResources/drawable/ic_launcher_foreground.xml" ]; then
  echo "=== File found ==="
  cat -n "composeApp/src/commonMain/composeResources/drawable/ic_launcher_foreground.xml"
else
  echo "File not found at specified location"
  # Search for similar files
  find . -name "ic_launcher*.xml" -type f 2>/dev/null | head -20
fi

Repository: festabook/kotlin-multiplatform

Length of output: 2005


🌐 Web query:

Compose Multiplatform resources drawable non-Android targets iOS Desktop Web vector XML

💡 Result:

Compose Multiplatform drawable on non-Android targets (iOS / Desktop / Web)

Where to put images

  • Put shared images in commonMain/composeResources/drawable. Compose Multiplatform supports raster (.png, .jpg, .bmp, .webp) and Android VectorDrawable XML (.xml) there (but XML must not reference Android resources). [1][2]

How to load them (works cross-platform)

  • Use the generated type-safe accessors (Res.drawable.*) and load with:
    • painterResource(Res.drawable.my_image) → returns a Painter (bitmap or vector). [1]
    • imageResource(...) → returns ImageBitmap (raster). [1]
    • vectorResource(...) → returns ImageVector (XML vector). [1]

Web-specific behavior

  • On Web, painterResource() is async: it may return an empty painter on the first recomposition and then replace it once loaded. [1]

SVG note

  • SVG is supported on all platforms except Android (so it’s fine for iOS/Desktop/Web, but you’ll need an alternative for Android such as raster or XML vector). [1]

Key limitation for Android vector XML (all targets)

  • Android XML vectors are supported, but external references to Android resources aren’t supported (for example colors like @android:color/..., theme attrs, etc.). Inline values instead (common when importing Material Symbols). [1][2]

References

  • [1] Kotlin Multiplatform Docs – “Using multiplatform resources in your app” (drawable loading, XML vectors, web loading behavior, SVG note) (kotlinlang.org)
  • [2] Kotlin Multiplatform Docs – “Setup and configuration for multiplatform resources” (supported drawable formats + “no Android resource references”) (jetbrains.com)

Citations:


aapt:attr 그래디언트는 Compose Multiplatform의 composeResources에서 지원되지 않음

Compose Multiplatform 공식 문서에 따르면, composeResources는 "Android 리소스에 대한 외부 참조가 없는" Android XML 벡터 이미지만 지원합니다. xmlns:aapt 네임스페이스와 <aapt:attr name="android:fillColor"> 요소는 Android 리소스 참조로 간주되며 CMP에서 지원되지 않습니다. 결과적으로 첫 번째 <path>의 그래디언트 채우기(lines 8-22)는 비-Android 플랫폼(iOS, Desktop, Web)에서 빌드 실패 또는 파싱 오류를 유발합니다.

또한, ic_launcher_foreground.xml은 Android 런처 아이콘이므로 commonMain/composeResources/drawable/이 아닌 androidMain/res/drawable/에 배치해야 합니다. 런처 아이콘은 CMP의 리소스 시스템이 아닌 Android의 리소스 시스템(R.*)에 의해 처리됩니다.

제안: aapt:attr 제거 및 단색 또는 고정 색상 사용
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:aapt="http://schemas.android.com/aapt"
-    android:width="108dp"
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
     android:height="108dp"
     android:viewportWidth="108"
     android:viewportHeight="108">
-    <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
-        <aapt:attr name="android:fillColor">
-            <gradient
-                android:endX="85.84757"
-                android:endY="92.4963"
-                android:startX="42.9492"
-                android:startY="49.59793"
-                android:type="linear">
-                <item
-                    android:color="#44000000"
-                    android:offset="0.0" />
-                <item
-                    android:color="#00000000"
-                    android:offset="1.0" />
-            </gradient>
-        </aapt:attr>
-    </path>
+    <path
+        android:fillColor="#22000000"
+        android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z" />

또한 line 28-29의 투명한 strokeColor="#00000000"strokeWidth="1"은 시각적 효과가 없으므로 제거하세요.

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

In
`@composeApp/src/commonMain/composeResources/drawable/ic_launcher_foreground.xml`
at line 2, Remove the unsupported aapt namespace and any aapt:attr usage in
ic_launcher_foreground.xml (delete xmlns:aapt and replace the gradient fill that
uses <aapt:attr name="android:fillColor"> with a solid/fixed android:fillColor
or single-color vector), move the launcher vector resource out of
composeResources into the Android resource set so the Android toolchain handles
it (place it under the Android resources area), and remove the no-op transparent
stroke attributes (strokeColor="#00000000" and strokeWidth="1") from the
affected <path> elements.

android:viewportHeight="16">
<path
android:fillColor="#00000000"
android:strokeColor="@color/gray500"
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for `@color/`, `@dimen/`, `@string/` references in commonMain drawables
echo "=== Android resource references in commonMain ==="
rg -n '@color/|@dimen/|@string/' --type xml composeApp/src/commonMain/ 2>/dev/null || echo "No matches found or path issue"

Repository: festabook/kotlin-multiplatform

Length of output: 4091


🏁 Script executed:

#!/bin/bash
# Examine the target file and comparison file
echo "=== ic_place_detail_clock.xml ==="
cat -n composeApp/src/commonMain/composeResources/drawable/ic_place_detail_clock.xml 2>/dev/null || echo "File not found"

echo ""
echo "=== ic_clock.xml for comparison ==="
find . -name "ic_clock.xml" -type f 2>/dev/null | head -5 | xargs -I {} cat -n {} || echo "File not found"

Repository: festabook/kotlin-multiplatform

Length of output: 2654


🏁 Script executed:

#!/bin/bash
# Search for any hardcoded gray color values in drawable resources
echo "=== Searching for gray color patterns in drawable resources ==="
rg -n '#[A-Fa-f0-9]{6}.*gray|gray.*#[A-Fa-f0-9]{6}' --type xml composeApp/src/commonMain/composeResources/drawable/ 2>/dev/null | head -20

Repository: festabook/kotlin-multiplatform

Length of output: 137


🏁 Script executed:

#!/bin/bash
# Check if there's a color definition for gray500 in the project
echo "=== Looking for color definitions ==="
find . -name "colors.xml" -o -name "color.xml" | head -10 | xargs cat 2>/dev/null

echo ""
echo "=== Checking Compose resources configuration ==="
find . -path "*/composeResources/*" -name "*.xml" -type f | head -5 | xargs dirname | sort -u | head -10

Repository: festabook/kotlin-multiplatform

Length of output: 215


🏁 Script executed:

#!/bin/bash
# Check gradle/build configuration for resource handling
echo "=== Checking build configuration ==="
find . -name "build.gradle*" -o -name "build.gradle.kts" | head -5 | xargs -I {} sh -c 'echo "=== {} ===" && head -50 {}'

Repository: festabook/kotlin-multiplatform

Length of output: 2567


🌐 Web query:

Compose Multiplatform commonMain drawable resources @color references compatibility

💡 Result:

In Compose Multiplatform (CMP) commonMain resources (the composeResources/drawable/*.xml Android-vector format), @color/..., @android:color/..., and theme/attr references (like ?attr/...) are not supported. CMP’s Android-XML vector support requires fully self-contained vectors, so external Android resource references won’t resolve cross-platform. (kotlinlang.org)

What to do instead (compatible approach)

  • Inline literal colors in the vector XML (e.g., android:fillColor="#FF000000"), and
  • Apply dynamic/themed coloring in Compose using tinting (e.g., Image(..., colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.primary))). Kotlin’s docs explicitly recommend removing android:tint and setting a literal fillColor, then tinting in Compose if needed. (kotlinlang.org)

If you specifically need @color/... references

That’s Android resources behavior, not CMP resources. Keep those drawables in Android’s androidMain/res/drawable (or the Android app module) and load them with Android resource APIs on Android only (e.g., painterResource(R.drawable...), colorResource(R.color...)). (developer.android.com)

Citations:


@color/gray500 Android 리소스 참조가 commonMain에서 크로스 플랫폼 호환성을 차단합니다.

Compose Multiplatform은 commonMain 리소스의 벡터 드로어블에서 @color/, @android:color/, ?attr/ 같은 Android 리소스 참조를 지원하지 않습니다. commonMain의 벡터는 완전히 자립형이어야 하므로 외부 리소스 참조가 해석되지 않아 모든 플랫폼에서 실패합니다. 동일한 시계 아이콘인 ic_clock.xml처럼 #ADADAD의 하드코딩된 색상값으로 변경해야 합니다.

🐛 수정 제안
     <path
         android:fillColor="#00000000"
-        android:strokeColor="@color/gray500"
+        android:strokeColor="#ADADAD"
         android:strokeWidth="1"
         android:strokeLineCap="round"
         android:strokeLineJoin="round"
         android:pathData="M2 8C2 8.78793 ..." />
     <path
         android:fillColor="#00000000"
-        android:strokeColor="@color/gray500"
+        android:strokeColor="#ADADAD"
         android:strokeWidth="1"
         android:strokeLineCap="round"
         android:strokeLineJoin="round"
         android:pathData="M8 4.66699V8.00033L10 10.0003" />

주의: 이와 동일한 문제가 composeApp/src/commonMain/composeResources/drawable/의 30개 이상 파일에서 확인되었습니다 (예: ic_uncaught_error.xml, bg_gray400_radius_50dp.xml, sel_time_tag.xml 등). 이 파일들도 함께 수정이 필요합니다.

📝 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
android:strokeColor="@color/gray500"
<path
android:fillColor="#00000000"
android:strokeColor="#ADADAD"
android:strokeWidth="1"
android:strokeLineCap="round"
android:strokeLineJoin="round"
android:pathData="M2 8C2 8.78793 ..." />
<path
android:fillColor="#00000000"
android:strokeColor="#ADADAD"
android:strokeWidth="1"
android:strokeLineCap="round"
android:strokeLineJoin="round"
android:pathData="M8 4.66699V8.00033L10 10.0003" />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@composeApp/src/commonMain/composeResources/drawable/ic_place_detail_clock.xml`
at line 8, The vector drawable in commonMain uses an Android resource reference
android:strokeColor="@color/gray500" which breaks cross-platform compatibility;
replace that attribute value with the literal hex color used elsewhere (e.g.,
"#ADADAD") so the drawable is self-contained. Update the android:strokeColor in
ic_place_detail_clock.xml to a hardcoded color string and scan the other
drawables in composeApp/src/commonMain/composeResources/drawable (examples:
ic_uncaught_error.xml, bg_gray400_radius_50dp.xml, sel_time_tag.xml) to replace
any `@color/`, `@android`:color/ or ?attr/ references with equivalent hex literals.

Comment on lines +6 to +12
<path
android:pathData="M8.478,8.478C8.666,8.29 8.92,8.185 9.185,8.185C9.45,8.185 9.705,8.29 9.892,8.478L12.046,10.631L14.199,8.478C14.292,8.382 14.402,8.306 14.524,8.254C14.646,8.201 14.777,8.174 14.91,8.173C15.043,8.171 15.174,8.197 15.297,8.247C15.42,8.297 15.532,8.372 15.626,8.465C15.72,8.559 15.794,8.671 15.844,8.794C15.894,8.917 15.92,9.049 15.919,9.181C15.917,9.314 15.89,9.445 15.837,9.567C15.785,9.689 15.709,9.8 15.613,9.892L13.46,12.046L15.613,14.199C15.795,14.387 15.896,14.64 15.894,14.902C15.892,15.165 15.787,15.415 15.601,15.601C15.416,15.786 15.165,15.891 14.903,15.894C14.641,15.896 14.388,15.795 14.199,15.613L12.046,13.46L9.892,15.613C9.703,15.791 9.452,15.888 9.192,15.884C8.933,15.88 8.685,15.775 8.501,15.591C8.317,15.408 8.212,15.16 8.208,14.9C8.204,14.641 8.301,14.389 8.478,14.2L10.631,12.047L8.478,9.893C8.291,9.705 8.186,9.451 8.186,9.186C8.186,8.921 8.291,8.666 8.478,8.479"
android:fillColor="@color/gray400"/>
<path
android:pathData="M12,2C6.477,2 2,6.477 2,12C2,17.523 6.477,22 12,22C17.523,22 22,17.523 22,12C22,6.477 17.523,2 12,2ZM4,12C4,9.878 4.843,7.843 6.343,6.343C7.843,4.843 9.878,4 12,4C14.122,4 16.157,4.843 17.657,6.343C19.157,7.843 20,9.878 20,12C20,14.122 19.157,16.157 17.657,17.657C16.157,19.157 14.122,20 12,20C9.878,20 7.843,19.157 6.343,17.657C4.843,16.157 4,14.122 4,12Z"
android:fillColor="@color/gray400"
android:fillType="evenOdd"/>
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

🧩 Analysis chain

🏁 Script executed:

# Check if the file exists and examine its contents
find . -name "ic_uncaught_error.xml" -type f 2>/dev/null

Repository: festabook/kotlin-multiplatform

Length of output: 149


🏁 Script executed:

# Search for color definitions, particularly gray400
rg -n "gray400" --type-add 'kotlin:*.kt' --type kotlin -C 2

Repository: festabook/kotlin-multiplatform

Length of output: 763


🏁 Script executed:

# Also search for color definitions in resources files
find . -name "*.xml" -o -name "*.kt" | xargs grep -l "gray400" 2>/dev/null | head -20

Repository: festabook/kotlin-multiplatform

Length of output: 507


🏁 Script executed:

# Read the actual content of the drawable file
cat ./composeApp/src/commonMain/composeResources/drawable/ic_uncaught_error.xml

Repository: festabook/kotlin-multiplatform

Length of output: 1690


🏁 Script executed:

# Search for Android colors resource definitions
find . -name "colors.xml" -type f 2>/dev/null | head -10

Repository: festabook/kotlin-multiplatform

Length of output: 56


🏁 Script executed:

# Check one of the other drawable files to see if they also use `@color` references
cat ./composeApp/src/commonMain/composeResources/drawable/bg_gray400_radius_50dp.xml

Repository: festabook/kotlin-multiplatform

Length of output: 301


🏁 Script executed:

# Search for commonMain colors.xml specifically
find ./composeApp/src/commonMain -name "colors.xml" -o -name "colors.kt" 2>/dev/null

Repository: festabook/kotlin-multiplatform

Length of output: 56


🏁 Script executed:

# Search for composeResources configuration or how colors are handled
rg -n "composeResources" --type-add 'toml:*.toml' --type toml -C 2 | head -30

Repository: festabook/kotlin-multiplatform

Length of output: 56


🏁 Script executed:

# Check for colors.xml in Android-specific resources
find ./composeApp/src -path "*androidMain*" -name "colors.xml" 2>/dev/null

Repository: festabook/kotlin-multiplatform

Length of output: 56


🏁 Script executed:

# Check build.gradle or build.gradle.kts for composeResources configuration
find . -name "build.gradle.kts" -o -name "build.gradle" | xargs grep -l "composeResources" 2>/dev/null

Repository: festabook/kotlin-multiplatform

Length of output: 58


🏁 Script executed:

# Search for colors definition in Android main resources
find ./composeApp/src/androidMain -type f -name "*.xml" 2>/dev/null | head -20

Repository: festabook/kotlin-multiplatform

Length of output: 456


🏁 Script executed:

# Check if there are any Android resource files in commonMain that define colors
find ./composeApp/src/commonMain -type d -name "res" 2>/dev/null

Repository: festabook/kotlin-multiplatform

Length of output: 56


🏁 Script executed:

# Look for any values folder or colors definition in commonMain
find ./composeApp/src/commonMain -type f \( -name "values" -o -name "colors.xml" \) 2>/dev/null

Repository: festabook/kotlin-multiplatform

Length of output: 56


🏁 Script executed:

# Check project structure to understand commonMain resources organization
ls -la ./composeApp/src/commonMain/composeResources/ 2>/dev/null

Repository: festabook/kotlin-multiplatform

Length of output: 407


🏁 Script executed:

# Check if there's a way colors are provided to composeResources
rg -n "composeResources" --type kotlin -B2 -A2 | head -40

Repository: festabook/kotlin-multiplatform

Length of output: 56


🏁 Script executed:

# Look for how the app actually uses these drawable resources
rg -n "ic_uncaught_error" --type kotlin

Repository: festabook/kotlin-multiplatform

Length of output: 56


🏁 Script executed:

# Check what's in the values folder for composeResources
ls -la ./composeApp/src/commonMain/composeResources/values/ 2>/dev/null

Repository: festabook/kotlin-multiplatform

Length of output: 246


🏁 Script executed:

# Check if there's a colors definition file in values
find ./composeApp/src/commonMain/composeResources/values -type f 2>/dev/null | xargs cat

Repository: festabook/kotlin-multiplatform

Length of output: 7959


🏁 Script executed:

# Search for how Compose Multiplatform handles resources - check if there's documentation in the project
find . -name "README*" -o -name "*.md" | xargs grep -l "composeResources\|drawable" 2>/dev/null | head -5

Repository: festabook/kotlin-multiplatform

Length of output: 56


🏁 Script executed:

# Check build.gradle.kts to understand resource setup
find ./composeApp -name "build.gradle.kts" | xargs cat 2>/dev/null | head -100

Repository: festabook/kotlin-multiplatform

Length of output: 3978


🏁 Script executed:

# Check if there's a colors.xml in androidMain resources
find ./composeApp/src/androidMain -name "colors.xml" -type f 2>/dev/null | xargs cat 2>/dev/null

Repository: festabook/kotlin-multiplatform

Length of output: 56


🏁 Script executed:

# Verify how other Compose Multiplatform vector drawables handle colors
rg -n "android:fillColor\|android:color" ./composeApp/src/commonMain/composeResources/drawable/ -A1 -B1 | head -50

Repository: festabook/kotlin-multiplatform

Length of output: 56


🏁 Script executed:

# Check if there's a colors definition file or how colors are provided to composeResources
find ./composeApp/src -name "*.xml" -type f | xargs grep -l "name=\"gray" 2>/dev/null

Repository: festabook/kotlin-multiplatform

Length of output: 58


🏁 Script executed:

# Check for any resource files that might define colors
find ./composeApp/src/commonMain/composeResources -type f | xargs ls -lah

Repository: festabook/kotlin-multiplatform

Length of output: 13944


🏁 Script executed:

# Verify how the drawable files are actually used in the Compose code
rg -n "painterResource\|drawable" --type kotlin ./composeApp/src/commonMain -A2 -B2 | head -50

Repository: festabook/kotlin-multiplatform

Length of output: 56


🏁 Script executed:

# Check if there's an Android resources directory that provides color definitions
find ./composeApp/src/androidMain/res -type d -name "values*" 2>/dev/null

Repository: festabook/kotlin-multiplatform

Length of output: 113


🏁 Script executed:

# Look for any colors.xml or color resource definitions in androidMain
find ./composeApp/src/androidMain -name "*.xml" | xargs grep -l "gray" 2>/dev/null

Repository: festabook/kotlin-multiplatform

Length of output: 58


🏁 Script executed:

# Check if Compose Multiplatform has any special handling or documentation about resources
find . -name "*.md" -o -name "README*" | xargs grep -l "composeResources\|drawable" 2>/dev/null

Repository: festabook/kotlin-multiplatform

Length of output: 58


🏁 Script executed:

# Verify by checking the actual structure - see if gray400 color is resolved during build
rg -n "@color" ./composeApp/src/commonMain/composeResources/drawable/ | wc -l

Repository: festabook/kotlin-multiplatform

Length of output: 76


🏁 Script executed:

# Check what files are in androidMain/res/values
ls -la ./composeApp/src/androidMain/res/values/

Repository: festabook/kotlin-multiplatform

Length of output: 245


🏁 Script executed:

# If colors.xml exists, read it to see if gray400 is defined there
cat ./composeApp/src/androidMain/res/values/colors.xml 2>/dev/null || echo "No colors.xml found"

Repository: festabook/kotlin-multiplatform

Length of output: 93


🏁 Script executed:

# Check how many drawable files use `@color` references
rg "@color" ./composeApp/src/commonMain/composeResources/drawable/ -c | sort | uniq -c

Repository: festabook/kotlin-multiplatform

Length of output: 2101


🏁 Script executed:

# Look at a sample of the drawable files to see the pattern
head -20 ./composeApp/src/commonMain/composeResources/drawable/bg_gray400_radius_50dp.xml && echo "---" && head -20 ./composeApp/src/commonMain/composeResources/drawable/bg_gray100_stroke_gray400_radius_10dp.xml

Repository: festabook/kotlin-multiplatform

Length of output: 614


🏁 Script executed:

# Check if there's a build task that generates colors.xml or resource files
rg -n "generateResources\|colors\.xml" --type kotlin ./composeApp

Repository: festabook/kotlin-multiplatform

Length of output: 56


🏁 Script executed:

# Look for any gradle tasks or plugins related to resource generation
grep -r "colors\|resource" ./composeApp/build.gradle.kts 2>/dev/null | head -20

Repository: festabook/kotlin-multiplatform

Length of output: 150


🏁 Script executed:

# Check if the app actually compiles and runs - look for test or build logs
find . -name "*.log" -type f 2>/dev/null | head -5

Repository: festabook/kotlin-multiplatform

Length of output: 56


🏁 Script executed:

# Verify by checking the actual Android manifest or gradle config for resource definitions
rg -n "res/values\|colors" --type kotlin -C 2 | head -40

Repository: festabook/kotlin-multiplatform

Length of output: 56


🏁 Script executed:

# Check if composeResources has a way to define colors - look for color resources
find ./composeApp/src/commonMain/composeResources -name "*.xml" -type f | xargs grep -l "name=\"gray" 2>/dev/null

Repository: festabook/kotlin-multiplatform

Length of output: 58


🏁 Script executed:

# Verify the Compose Multiplatform version being used
grep -i "compose\|multiplatform" ./composeApp/build.gradle.kts | head -20

Repository: festabook/kotlin-multiplatform

Length of output: 924


🏁 Script executed:

# Check if there's any documentation or comments about resource handling in the project
find . -name "*.md" -o -name "*.txt" | xargs grep -i "@color\|composeResources" 2>/dev/null | head -20

Repository: festabook/kotlin-multiplatform

Length of output: 56


🏁 Script executed:

# Look at all drawable files to see if they're actually being used
rg -n "painterResource" ./composeApp/src/commonMain/kotlin -B2 -A2 | head -40

Repository: festabook/kotlin-multiplatform

Length of output: 5015


🏁 Script executed:

# Check the Res generated resources to see if drawables are actually included
find . -path "*generated/resources*" -type f 2>/dev/null | head -20

Repository: festabook/kotlin-multiplatform

Length of output: 56


🏁 Script executed:

# Search for how colors are resolved in a Compose Multiplatform context
rg -n "Res.drawable\|Res.color" ./composeApp/src/commonMain/kotlin -C1 | head -30

Repository: festabook/kotlin-multiplatform

Length of output: 56


🏁 Script executed:

# Look for any build configuration that might process Android resources
cat ./composeApp/build.gradle.kts | grep -A 10 -B 5 "resources"

Repository: festabook/kotlin-multiplatform

Length of output: 1343


🏁 Script executed:

# Check if there are any build reports or gradle tasks that would show if this works
find . -name "*.gradle.kts" -o -name "gradle.properties" | xargs grep -l "compose.resources" 2>/dev/null

Repository: festabook/kotlin-multiplatform

Length of output: 58


🌐 Web query:

Compose Multiplatform @color references in composeResources vector drawables support

💡 Result:

Compose Multiplatform composeResources Android XML vector drawables do not support @color/... (or other Android resource) references. The XML must be self-contained: “vector Android XML images (without references to Android resources)”. [1][3]

What to do instead

  • Inline the color in the vector XML (e.g., android:fillColor="#FF000000"), and remove resource/theme refs like android:tint="?attr/..." or android:fillColor="@android:color/...". JetBrains’ guide explicitly shows converting vectors this way. [3]
  • Tint in Compose code when you need dynamic/theme colors:
    • Load with painterResource(Res.drawable.some_vector) and apply colorFilter = ColorFilter.tint(...). [3]

If you share your vector XML snippet, I can rewrite it into a Compose Multiplatform–compatible version.

Sources: [1] setup docs, [3] resources usage docs.


@color/gray400 참조는 Compose Multiplatform의 commonMain composeResources에서 지원되지 않습니다.

Compose Multiplatform 공식 문서에 따르면 composeResources의 Android XML 벡터 드로어블은 "Android 리소스에 대한 참조가 없어야(without references to Android resources)" 합니다. @color/gray400와 같은 Android 리소스 참조는 iOS, Desktop, Web 등 비-Android 플랫폼에서 해석되지 않아 아이콘이 렌더링되지 않거나 기본값으로 표시됩니다.

해결 방법: @color/gray400를 실제 hex 색상값 #AAAAAA로 교체하세요. 추가로 프로젝트의 다른 드로어블 파일들(bg_gray*.xml, ic_*.xml 등)도 동일한 문제를 가지고 있으므로 모두 수정이 필요합니다.

수정 예시
  <path
      android:pathData="M8.478,8.478..."
-     android:fillColor="@color/gray400"/>
+     android:fillColor="#AAAAAA"/>
  <path
      android:pathData="M12,2C6.477,2..."
-     android:fillColor="@color/gray400"
+     android:fillColor="#AAAAAA"
      android:fillType="evenOdd"/>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@composeApp/src/commonMain/composeResources/drawable/ic_uncaught_error.xml`
around lines 6 - 12, The vector drawable ic_uncaught_error.xml uses an Android
resource reference android:fillColor="@color/gray400" which is unsupported in
commonMain composeResources; replace that reference with a literal hex color
(e.g. android:fillColor="#AAAAAA") in both <path> elements in
ic_uncaught_error.xml, and scan/replace the same pattern in other drawable files
mentioned (bg_gray*.xml, ic_*.xml) so no composeResources vector references
Android resources.

Comment on lines +1 to +23
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

<item android:state_selected="true">
<shape android:shape="rectangle">
<solid android:color="#F4FCFF" />
<corners android:radius="10dp" />
</shape>
</item>

<item android:state_pressed="true">
<shape android:shape="rectangle">
<solid android:color="#E0EBFB" />
<corners android:radius="10dp" />
</shape>
</item>

<item>
<shape android:shape="rectangle">
<solid android:color="@color/gray050" />
</shape>
</item>
</selector> 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 | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Compose Multiplatform composeResources drawable supported types selector XML

💡 Result:

In Compose Multiplatform composeResources/drawable, the supported drawable types are image files, not general Android drawable/*.xml resources:

  • Raster images: *.png, *.jpg/jpeg, *.bmp, *.webp (and similar raster formats) (kotlinlang.org)
  • Vector images: Android XML vector drawable files (the <vector ...> format), but they must not reference Android resources (no @drawable/..., @color/..., etc.). (kotlinlang.org)
  • SVG: supported on all platforms except Android (so it’s not a universal “works everywhere” drawable type). (kotlinlang.org)

Selector XML?

Android selector XML (state list drawables like <selector>...</selector>) is not supported by Compose Multiplatform composeResources as a drawable type—only XML vector drawables are. (kotlinlang.org)

Citations:


🌐 Web query:

Compose Multiplatform composeResources Android resource references @color allowed

💡 Result:

No—in composeResources (Compose Multiplatform Resources API), Android XML vector drawables must not reference Android resources, so values like @color/..., @android:color/..., ?attr/..., or android:tint="?attr/..." are not supported. JetBrains explicitly notes that XML vector drawables are supported only “without references to Android resources”. [1] They also show converting vectors by replacing android:fillColor="@android:color/white" with a literal color like #000000 and removing android:tint/other Android-specific color attributes. [2]

What to do instead:

  • Inline colors in the XML (e.g., android:fillColor="#RRGGBB"), and/or
  • Apply tint in Compose code (e.g., colorFilter = ColorFilter.tint(...)) when you draw the resource. [2]
  • If you specifically need Android-only @color resources, keep that drawable in Android’s androidMain/res and use Android APIs such as colorResource(R.color...) in Android-specific code. [3]

Sources:
[1] JetBrains docs: resources setup (vector drawables “without references to Android resources”) (jetbrains.com)
[2] JetBrains docs: resources usage (vector example removing @android:color/... and android:tint) (jetbrains.com)
[3] Android Developers: colorResource() from R.color (developer.android.com)

Citations:


🌐 Web query:

Compose Multiplatform state handling InteractionSource selected pressed alternative

💡 Result:

In Compose Multiplatform, treat these as two different kinds of state:

  • Pressed = an interaction you can observe from an InteractionSource.
  • Selected = your own UI state (e.g., current choice), which you pass into components/modifiers like selectable.

Pressed (via InteractionSource)

InteractionSource exposes collectIsPressedAsState() (and focused/hovered/dragged equivalents) as Compose state. It’s available in common code (Cmn). [1][2]

val interactionSource = remember { MutableInteractionSource() }
val pressed by interactionSource.collectIsPressedAsState()

Box(
  Modifier
    .clickable(
      interactionSource = interactionSource,
      indication = LocalIndication.current
    ) { /* click */ }
) {
  // use `pressed` to change UI
}

[1][2]

Note: these State updates are frame-batched, so a press that starts+ends within the same frame may never be observed as true. If you need guaranteed “press started / ended” timing (e.g., to run an animation), collect the interaction flow directly and start/stop animation inside the collector instead of relying on State. [2]

Selected (hoist your own state; use selectable)

Selection isn’t an InteractionSource boolean—model it explicitly and pass it to Modifier.selectable(selected = ...) (also Cmn). [4]

var selected by remember { mutableStateOf(false) }
val interactionSource = remember { MutableInteractionSource() }
val pressed by interactionSource.collectIsPressedAsState()

Box(
  Modifier.selectable(
    selected = selected,
    interactionSource = interactionSource,
    enabled = true,
    role = null,
  ) { selected = !selected }
) {
  // use `selected` and `pressed` independently
}

[1][4]

“Alternative” if you don’t need to observe interactions

If you’re only using default ripple/indication and don’t read pressed/hovered/focused, pass interactionSource = null to let Compose create it lazily (less overhead). [3]

[1] https://developer.android.com/reference/kotlin/androidx/compose/foundation/interaction/InteractionSource
[2] https://developer.android.com/develop/ui/compose/touch-input/user-interactions/handling-interactions
[3] https://developer.android.com/reference/kotlin/androidx/compose/foundation/package-summary
[4] https://developer.android.com/reference/kotlin/androidx/compose/foundation/selection/package-summary


<selector> drawable는 Compose Multiplatform의 composeResources에서 지원되지 않음

이 파일은 두 가지 근본적인 문제가 있습니다:

  1. 지원되지 않는 XML 타입 (<selector>)

    • Compose Multiplatform은 래스터 이미지(PNG, JPEG, WebP 등)와 <vector> XML만 지원합니다.
    • <selector> drawable은 Android View 시스템 전용 타입으로, composeResources에서는 지원되지 않습니다.
    • iOS, Desktop 등 비-Android 플랫폼에서 빌드 실패 또는 런타임 오류가 발생합니다.
  2. Android 리소스 참조 사용 금지 (Line 20: @color/gray050)

    • composeResources의 XML 리소스는 Android 리소스 참조(@color/..., @drawable/... 등)를 포함할 수 없습니다.
    • 공식 문서: "vector Android XML images (without references to Android resources)"
    • 멀티플랫폼 공통 리소스로는 이 파일이 작동하지 않습니다.

해결 방법

선택/프레스 상태는 Compose 코드에서 InteractionSource와 호이스팅된 상태로 처리해야 합니다:

val interactionSource = remember { MutableInteractionSource() }
val isPressed by interactionSource.collectIsPressedAsState()
var isSelected by remember { mutableStateOf(false) }

val backgroundColor = when {
    isSelected -> Color(0xFFF4FCFF)
    isPressed  -> Color(0xFFE0EBFB)
    else       -> Color(0xFFF0F0F0) // gray050에 해당하는 inline color
}

Box(
    modifier = Modifier
        .background(backgroundColor, RoundedCornerShape(10.dp))
        .selectable(
            selected = isSelected,
            interactionSource = interactionSource,
            indication = ripple()
        ) { isSelected = !isSelected }
)

이 파일은 삭제하고 위의 Composable 코드로 대체하세요.

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

In `@composeApp/src/commonMain/composeResources/drawable/sel_time_tag.xml` around
lines 1 - 23, The sel_time_tag.xml uses an Android-only <selector> drawable and
an Android resource reference (`@color/gray050`), both unsupported by Compose
Multiplatform composeResources; remove this XML and implement the
selection/press visuals in Compose using an InteractionSource (remember {
MutableInteractionSource() }), collectIsPressedAsState(), and a hoisted
isSelected state, computing a background color (inline ARGB for gray050 instead
of `@color/gray050`) and applying it with Modifier.background(...,
RoundedCornerShape(10.dp)) and Modifier.selectable(...) to replicate
selected/pressed/default states across platforms.

*   **feat: 신규 아이콘 및 이미지 리소스 추가**
    *   로고, 마커, UI 컴포넌트 등 다양한 벡터 및 비트맵 이미지를 추가했습니다.
    *   지도 카테고리별 마커 아이콘(일반, 선택 상태)을 추가했습니다.
    *   로딩 및 위치 표시를 위한 Lottie 애니메이션 파일을 추가했습니다.

*   **feat: Pretendard 폰트 추가**
    *   앱 전체에 사용될 Pretendard 폰트(Bold, Medium, Regular)를 리소스에 추가했습니다.

*   **build: 라이브러리 의존성 추가 및 버전 업데이트**
    *   Lottie 애니메이션을 위한 `Compottie` 라이브러리를 추가했습니다.
    *   이미지 로딩을 위해 `Coil`, `Landscapist` 관련 라이브러리를 추가했습니다.
    *   Kotlin 및 Mokkery 라이브러리 버전을 업데이트했습니다.

*   **feat: 문자열 리소스 추가**
    *   앱 전반에 사용될 문자열(화면 제목, 버튼 텍스트, 에러 메시지 등)을 `strings.xml`에 정의했습니다.
*   **feat: 공용 컴포저블(Composable) 추가**
    *   `FestabookImage`: Shimmer 효과, 이미지 확대 및 팝업 기능을 지원하는 이미지 컴포저블을 추가했습니다.
    *   `CoilImage`: Coil3를 사용하여 URL로부터 이미지를 표시하는 기본 이미지 컴포저블을 추가했습니다.
    *   `FestabookTopAppBar`: 화면 상단 타이틀을 표시하는 `TopAppBar`를 추가했습니다.
    *   `FestabookSwitch`: 앱의 디자인 시스템에 맞는 커스텀 스위치 컴포넌트를 추가했습니다.
    *   `PullToRefreshContainer`: `Pull-to-Refresh` 기능을 구현하고, 로딩 인디케이터를 커스텀했습니다.
    *   `SnackBar`: 앱 전반에서 사용할 수 있는 `SnackbarManager`와 커스텀 스낵바를 추가하여, `ApiResultException` 타입에 따라 다른 에러 메시지를 표시하도록 구현했습니다.
    *   `LoadingStateScreen`: Lottie 애니메이션을 사용한 로딩 화면을 추가했습니다.
    *   `EmptyStateScreen`, `ErrorStateScreen`: 데이터가 없거나 에러가 발생했을 때 표시할 화면을 추가했습니다.
    *   `URLText`: 텍스트 내의 URL을 감지하여 클릭 시 해당 링크로 이동하는 기능을 추가했습니다.
    *   `CardBackground`: 공통으로 사용될 카드 배경 스타일을 `Modifier` 확장 함수로 추가했습니다.

*   **feat: 공용 유틸리티 및 인터페이스 추가**
    *   `ImageUtil.kt`: 이미지 URL을 실제 주소로 변환하는 `convertImageUrl` 함수를 추가했습니다.
    *   `TextUtil.kt`: 축제 기간(`LocalDate`)을 형식에 맞게 포맷하는 `formatFestivalPeriod` 함수를 추가했습니다.
    *   `ObserveEvent.kt`: `Flow`의 이벤트를 UI에서 관찰하기 위한 `ObserveAsEvents` 유틸리티를 추가했습니다.
    *   `OnMenuItemReClickListener`: 메뉴 아이템 재클릭 이벤트를 처리하기 위한 인터페이스를 추가했습니다.
    *   `ImageUtil.android.kt`: Android에서 Vector 이미지를 Bitmap으로 변환하는 유틸리티를 추가했습니다.

*   **feat: 테마 및 디자인 시스템 정의**
    *   `FestabookTheme`: `MaterialTheme`을 기반으로 앱의 전체 테마를 설정하고 `Color`, `Spacing`, `Typography`, `Shapes`를 `CompositionLocalProvider`로 제공합니다.
    *   `FestabookColor`: 앱의 색상 팔레트를 정의했습니다.
    *   `FestabookTypography`: Pretendard 폰트를 적용한 타이포그래피 시스템을 정의했습니다.
    *   `FestabookSpacing`: 앱 전체에서 사용할 간격(Spacing) 값을 정의했습니다.
    *   `FestabookShapes`: 앱에서 사용할 `Shape`(모서리 둥글기 등) 값을 정의했습니다.

*   **build: 라이브러리 의존성 추가**
    *   `Coil`, `Landscapist` (이미지 로딩), `Compottie` (Lottie 애니메이션) 라이브러리를 추가했습니다.
    *   `BuildKonfig`에 이미지 URL(`FESTABOOK_IMAGE_URL`) 설정을 추가하여 빌드 환경(debug/release)에 따라 다른 URL을 사용하도록 구성했습니다.
    *   `ktorfit` 플러그인의 컴파일러 버전을 명시했습니다.
* build: ktlint 의존성 추가
    * `gradle/libs.versions.toml`: ktlint 플러그인 및 라이브러리 버전(`0.5.0`)을 추가했습니다.
    * `composeApp/build.gradle.kts`: ktlint 플러그인 및 `ktlintRuleset` 의존성을 설정했습니다.

* build: ksp 실행 후 ktlint가 실행되도록 태스크 의존성 설정
    * `KtLintCheckTask`와 `KtLintFormatTask`가 `KspAATask` 이후에 실행되도록 `dependsOn`을 추가했습니다.

* feat: `.editorconfig` 파일 추가
    * ktlint 규칙을 프로젝트에 맞게 설정했습니다.
    * Composable 함수는 `ktlint_function_naming` 규칙에서 제외하도록 설정했습니다.

* style: 코드 포맷팅 적용 및 스타일 수정
    * 전체 코드에 대해 ktlint 포맷을 적용했습니다.
    * 불필요한 공백을 제거하고, `private` 접근 제어자를 추가하며, 표현식 본문(expression body)으로 함수를 변경하는 등의 스타일을 수정했습니다.
*   CI 워크플로우(`.github/workflows/cmp-ci.yml`)에 개발 및 프로덕션 환경의 이미지 기본 URL(`IMAGE_BASE_URL_DEV`, `IMAGE_BASE_URL`)을 `local.properties` 파일에 추가하는 단계를 포함시켰습니다.
*   이는 빌드 시 BuildKonfig가 해당 환경 변수를 참조할 수 있도록 하기 위함입니다.
- 테스트 코드 및 Fixture 파일의 코드 스타일을 일관성 있게 수정했습니다.
- Compose UI 구현으로 대체되면서 더 이상 사용되지 않는 XML 드로어블 파일들을 삭제했습니다.
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: 18

🧹 Nitpick comments (18)
composeApp/src/commonMain/composeResources/drawable/ic_trash_selected.xml (1)

1-5: 캔버스와 뷰포트의 종횡비 불일치 (미세한 왜곡 가능성)

캔버스(30dp × 51dp)와 뷰포트(24 × 41)의 종횡비가 정확히 일치하지 않습니다.

  • 캔버스 종횡비: 30/51 ≈ 0.5882
  • 뷰포트 종횡비: 24/41 ≈ 0.5854

Android VectorDrawable은 뷰포트를 캔버스 크기에 맞게 가로/세로를 독립적으로 스케일링하므로(가로 배율 1.25, 세로 배율 ≈ 1.2439), 의도한 비율 대비 약 0.49%의 수평 늘어남이 발생합니다. 육안으로는 거의 식별 불가능하지만, 의도적인 설계가 아니라면 뷰포트 비율을 캔버스에 맞춰 정렬하는 것이 권장됩니다.

🔧 종횡비 통일 예시 (뷰포트를 캔버스 비율에 맞춤)
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:width="30dp"
     android:height="51dp"
-    android:viewportWidth="24"
-    android:viewportHeight="41">
+    android:viewportWidth="24"
+    android:viewportHeight="40.8">

또는 캔버스를 뷰포트 비율에 맞춰 조정:

 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="30dp"
-    android:height="51dp"
+    android:width="30dp"
+    android:height="51.25dp"
     android:viewportWidth="24"
     android:viewportHeight="41">

실제 경로 데이터(pathData)는 그대로 유지하며, 어느 쪽으로 통일하든 시각적 차이는 미미합니다.

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

In `@composeApp/src/commonMain/composeResources/drawable/ic_trash_selected.xml`
around lines 1 - 5, The VectorDrawable's canvas aspect ratio
(android:width="30dp" android:height="51dp") does not exactly match the viewport
aspect ratio (android:viewportWidth="24" android:viewportHeight="41"), causing a
~0.49% horizontal distortion; fix by making the ratios identical—either adjust
android:viewportWidth/android:viewportHeight to match the 30/51 canvas ratio or
change android:width/android:height to match the 24/41 viewport ratio (keep the
existing pathData unchanged) so scaling is uniform.
composeApp/src/commonMain/composeResources/drawable/ic_place_detail_host.xml (1)

1-9: LGTM — CMP 공통 리소스로서 벡터 드로어블 형식이 올바릅니다.

composeResources/drawable/에 Android 벡터 드로어블 XML 형식을 사용하는 것은 Compose Multiplatform 리소스 플러그인이 요구하는 올바른 방식입니다. 뷰포트(16×16)와 dp 크기(16×16)도 일치합니다.

다만, android:fillColor="#474747"가 하드코딩되어 있어 다크 모드나 테마 변경 시 자동 적용이 되지 않습니다. CMP 리소스 XML에서는 @color/ 참조가 지원되지 않으므로, 다크 모드 대응이 필요한 경우 이 아이콘을 사용하는 Icon / Image 컴포저블의 tint 파라미터를 통해 테마 색상을 주입하는 방식을 권장합니다.

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

In `@composeApp/src/commonMain/composeResources/drawable/ic_place_detail_host.xml`
around lines 1 - 9, The vector drawable currently hardcodes color via the path
attribute android:fillColor="#474747", which prevents theme/tinting; remove the
android:fillColor attribute from the <path> in ic_place_detail_host.xml (or make
it empty) so the drawable can be tinted by the consuming Icon/Image composable
via its tint parameter; locate the <path> element in the vector and delete the
android:fillColor="#474747" entry and ensure callers apply the theme color
through the composable's tint.
composeApp/src/commonMain/composeResources/drawable/ic_chevron_down.xml (1)

11-11: 하드코딩된 스트로크 색상은 다크 모드 테마를 지원하지 않습니다.

#5C5C5C는 라이트 모드에 맞춘 고정 색상으로, 다크 모드에서는 배경과 대비가 부족할 수 있습니다. Icon() composable을 통해 사용하면 colorFilter로 오버라이드되지만, Image()나 기타 방식으로 렌더링할 경우에는 XML에 구워진 색상이 그대로 노출됩니다.

PR에서 Compose 기반 디자인 시스템(색상/타이포그래피 등)을 구축하는 만큼, strokeColor를 중립값(#000000 또는 #FFFFFF)으로 설정하고 색상을 호출 지점의 tint/colorFilter로 제어하거나, 공통 색상 리소스를 참조하는 방식을 고려해보세요.

🎨 색상 중립화 제안
-      android:strokeColor="#5C5C5C"
+      android:strokeColor="#000000"

그런 다음 Compose 호출 지점에서:

Icon(
    painter = painterResource(Res.drawable.ic_chevron_down),
    tint = MaterialTheme.colorScheme.onSurface,
    contentDescription = null
)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@composeApp/src/commonMain/composeResources/drawable/ic_chevron_down.xml` at
line 11, The hardcoded stroke color in ic_chevron_down.xml
(android:strokeColor="#5C5C5C") breaks dark mode; change the XML to use a
neutral color (e.g., `#000000` or `#FFFFFF`) or reference a color resource (e.g.,
`@color/ic_neutral`) instead of the literal "#5C5C5C", and control actual display
color from the Compose call site (e.g., when using Icon/painterResource set tint
= MaterialTheme.colorScheme.onSurface or a colorFilter) so Image usages also
adapt to theme.
composeApp/src/commonMain/composeResources/drawable/ic_map_category_stage.xml (1)

8-8: 런타임 테마 적용을 위해 색상 전략 검토 권장

fillColor="#B910D6"가 하드코딩되어 있습니다. 다른 카테고리 아이콘들을 확인한 결과:

  • 유색 아이콘 (bar, food_truck, photo_booth, primary, stage): 각각 고유한 브랜드 색상 사용
  • 투명색 아이콘 (booth, smoking, toilet, trash): #00000000(투명 검정색) 사용하여 런타임 tint 적용 가능

현재 stage 아이콘의 하드코딩된 색상은 의도적 설계로 보입니다. 그러나 향후 다크 모드나 선택/비선택 상태에서 색상 변경이 필요하다면, 투명색(#FFFFFF 또는 #00000000)으로 변경하고 Compose 호출부에서 tint = MapCategoryColor.Stage 같은 디자인 토큰으로 관리하는 방식을 고려해 보세요.

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

In
`@composeApp/src/commonMain/composeResources/drawable/ic_map_category_stage.xml`
at line 8, The drawable ic_map_category_stage.xml currently hardcodes
android:fillColor="#B910D6", preventing runtime tinting; change the fillColor to
a transparent value (e.g. "#00000000" or "#FFFFFF" as used by other tintable
icons) so the Compose caller can apply MapCategoryColor.Stage via tint; update
the drawable pattern to match the transparent icons (booth, smoking, toilet,
trash) and ensure Compose code uses tint = MapCategoryColor.Stage to control
brand/dark-mode/selected states at runtime.
composeApp/src/commonMain/composeResources/drawable/ic_stage.xml (1)

1-5: 뷰포트 비율과 물리적 크기 비율 미세 불일치 (선택적)

android:width/height의 종횡비(28/34 ≈ 0.8235)와 android:viewportWidth/Height의 종횡비(24/29 ≈ 0.8276)가 약 0.5% 차이납니다. painterResource()로 렌더링 시 뷰포트 좌표계에서 dp 크기로 스케일링할 때 비균일 스케일링이 적용되어 아이콘이 미세하게 가로 또는 세로로 왜곡됩니다. 시각적으로 거의 감지할 수 없는 수준이지만, 정밀한 아이콘 디자인이 필요한 경우 두 비율을 일치시키는 것을 고려할 수 있습니다.

예: 뷰포트를 24 × 29로 유지하면서 물리적 크기를 24/29 비율에 맞게 조정하거나, 반대로 물리적 크기 28×34에 맞게 뷰포트를 조정합니다.

✏️ 제안: 물리적 크기를 뷰포트 비율에 맞게 조정하는 예시
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="28dp"
-    android:height="34dp"
+    android:width="28dp"
+    android:height="33.833dp"
     android:viewportWidth="24"
     android:viewportHeight="29">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@composeApp/src/commonMain/composeResources/drawable/ic_stage.xml` around
lines 1 - 5, 뷰포트 비율(24×29)과 물리적 크기(android:width="28dp" android:height="34dp")의
종횡비가 미세하게 달라져 painterResource()로 렌더링 시 비균일 스케일이 생길 수 있으니 일치시키세요: 수정 방법은 vector
태그의 android:width/android:height를 viewport 비율에 맞게 24dp×29dp로 바꾸거나 반대로
android:viewportWidth/android:viewportHeight를 28×34로 바꿔 뷰포트와 물리적 크기의 비율을 일치시키는
것입니다; 관련 속성은 vector 태그의 android:width, android:height, android:viewportWidth,
android:viewportHeight입니다.
composeApp/src/commonMain/composeResources/drawable/ic_booth.xml (1)

1-1: xmlns:aapt 네임스페이스 선언이 사용되지 않음

xmlns:aapt="http://schemas.android.com/aapt" 가 선언되어 있지만, 파일 내 어디에서도 사용되지 않습니다. 이 네임스페이스는 <aapt:attr> 태그를 통해 벡터 내에 그래디언트 리소스를 인라인으로 정의할 때 사용되는데, 현재 파일에서는 해당 태그가 없습니다. 제거하는 것을 권장합니다.

♻️ 제안 수정
-<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:viewportWidth="24"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@composeApp/src/commonMain/composeResources/drawable/ic_booth.xml` at line 1,
The vector root element declares an unused namespace
xmlns:aapt="http://schemas.android.com/aapt"; remove that attribute from the
<vector> element (the vector XML root) so the file no longer includes the unused
aapt namespace declaration.
composeApp/src/commonMain/composeResources/drawable/ic_cluster_marker.xml (1)

9-10: 하드코딩된 색상으로 인해 다크/라이트 테마에 적응하지 못합니다.

fillColor="#1F1F1F"strokeColor="#ffffff"가 하드코딩되어 있어 테마 변경에 대응하지 못합니다.

Compose Multiplatform은 Android XML 벡터 이미지를 지원하지만, 외부 Android 리소스 참조는 사용할 수 없습니다. 또한 공식 문서는 android:fillColor#000000으로 설정하고, android:tint와 같은 색상 조정용 Android 전용 속성을 제거할 것을 권장합니다. 대신 사용처에서 colorFilter = ColorFilter.tint(...) 파라미터로 색상을 적용하거나, CMP의 테마 한정자(drawable-dark / drawable-light 디렉터리)로 변형을 제공하는 방법을 고려해 보세요.

🎨 테마 한정자를 이용한 변형 제공 예시
# composeApp/src/commonMain/composeResources/drawable/ic_cluster_marker.xml  (라이트 기본값)
-      android:fillColor="#1F1F1F"
-      android:strokeColor="#ffffff"/>
+      android:fillColor="#000000"
+      android:strokeColor="#ffffff"/>

또는 사용처에서 테마 색상을 colorFilter로 적용:

Image(
    painter = painterResource(Res.drawable.ic_cluster_marker),
    contentDescription = null,
    colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurface)
)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@composeApp/src/commonMain/composeResources/drawable/ic_cluster_marker.xml`
around lines 9 - 10, The vector drawable ic_cluster_marker.xml currently
hardcodes android:fillColor="#1F1F1F" and android:strokeColor="#ffffff",
preventing theme adaptation; change android:fillColor to "#000000" and remove or
avoid hardcoded android:strokeColor (and any android:tint attributes) so the
drawable is neutral, then apply theming at usage (e.g., via colorFilter =
ColorFilter.tint(...) when calling painterResource) or provide theme-specific
variants (drawable-dark / drawable-light) for ic_cluster_marker; update the file
to only use neutral/default colors and rely on external tinting or variant
resources.
composeApp/src/commonMain/composeResources/drawable/btn_back_to_previous.xml (2)

6-17: 파일 명명 규칙 불일치 및 ic_arrow_back.xml과의 기능 중복 가능성

동일 PR에서 추가된 다른 아이콘들(ic_alarm.xml, ic_arrow_back.xml, ic_arrow_right.xml, ic_chevron_up.xml, ic_chevron_down.xml)은 모두 ic_ 접두사를 사용하는 반면, 이 파일만 btn_ 접두사를 사용하고 있어 명명 규칙이 일관되지 않습니다. 또한 ic_arrow_back.xml과 기능상 중복될 가능성이 있으므로, 원형 배경을 포함한 복합 에셋이라면 명확히 구분하거나, 그렇지 않다면 통합을 검토하는 것이 좋습니다.

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

In `@composeApp/src/commonMain/composeResources/drawable/btn_back_to_previous.xml`
around lines 6 - 17, The file btn_back_to_previous.xml breaks the project's icon
naming convention and may duplicate ic_arrow_back.xml; rename or consolidate to
follow the ic_ prefix convention and clarify intent: either rename
btn_back_to_previous.xml to ic_arrow_back_circle.xml (or similar) if it
represents an arrow-with-circle compound asset, update any references to that
new name, and remove or merge with ic_arrow_back.xml if they are redundant;
ensure the drawable content (the two <path> elements) matches the chosen
canonical asset and update resource usages across the codebase to reference the
unified ic_* name.

9-10: 하드코딩된 색상으로 다크 모드 미지원

원형 배경(#ffffff)과 화살표(#000000) 색상이 하드코딩되어 있어 다크 모드 및 디자인 시스템 테마 변경에 자동으로 대응되지 않습니다. CMP composeResources XML에서는 테마 속성을 직접 참조할 수 없으므로, 사용 측 Composable에서 ColorFilter 또는 tint 파라미터를 활용해 테마 색상을 적용하는 방식을 권장합니다.

Also applies to: 15-16

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

In `@composeApp/src/commonMain/composeResources/drawable/btn_back_to_previous.xml`
around lines 9 - 10, 버튼 백 아이콘 벡터에서 하드코딩된 색상(android:fillColor="#ffffff",
android:strokeColor="#EDEDED" 및 화살표의 "#000000")을 제거하고 아이콘
XML(btn_back_to_previous.xml)의 path 및 group 요소에서 색상 속성을 비우거나 기본(투명/무색)으로 변경해 호출
측 Composable에서 ColorFilter 또는 tint로 색상을 주입하도록 변경하세요; 대상 식별자:
btn_back_to_previous.xml 파일의 android:fillColor 및 android:strokeColor 속성(및 화살표
path의 fill/stroke 색상)을 수정하면 됩니다. Ensure the drawable contains no hardcoded
colors so theme-aware tinting from the Composable can be applied.
composeApp/src/commonMain/composeResources/drawable/ic_home.xml (1)

6-12: <group><clip-path> 래퍼 제거 가능 (불필요한 구조)

<clip-path>pathData="M0,0h24v24h-24z"는 뷰포트(24×24) 전체를 덮는 직사각형으로, 실제로 아무것도 클리핑하지 않습니다. 단 하나의 <path>를 감싸는 <group>도 없애면 파일 구조가 단순해집니다.

♻️ Proposed refactor: 불필요한 group/clip-path 제거
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:width="24dp"
     android:height="24dp"
     android:viewportWidth="24"
     android:viewportHeight="24">
-    <group>
-        <clip-path android:pathData="M0,0h24v24h-24z" />
-        <path
-            android:pathData="M3,10.394L1.654,11.557C1.236,11.918 0.604,11.872 0.243,11.454C-0.118,11.036 -0.072,10.404 0.346,10.043L10.039,1.672C11.165,0.699 12.835,0.699 13.961,1.672L23.654,10.043C24.072,10.404 24.118,11.036 23.757,11.454C23.396,11.872 22.764,11.918 22.346,11.557L21,10.394V19C21,21.209 19.209,23 17,23H7C4.791,23 3,21.209 3,19V10.394ZM10,15C10,14.448 10.448,14 11,14H13C13.552,14 14,14.448 14,15V21H10V15Z"
-            android:fillColor="?attr/colorControlNormal"
-            android:fillType="evenOdd" />
-    </group>
+    <path
+        android:pathData="M3,10.394L1.654,11.557C1.236,11.918 0.604,11.872 0.243,11.454C-0.118,11.036 -0.072,10.404 0.346,10.043L10.039,1.672C11.165,0.699 12.835,0.699 13.961,1.672L23.654,10.043C24.072,10.404 24.118,11.036 23.757,11.454C23.396,11.872 22.764,11.918 22.346,11.557L21,10.394V19C21,21.209 19.209,23 17,23H7C4.791,23 3,21.209 3,19V10.394ZM10,15C10,14.448 10.448,14 11,14H13C13.552,14 14,14.448 14,15V21H10V15Z"
+        android:fillColor="#FF000000"
+        android:fillType="evenOdd" />
 </vector>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@composeApp/src/commonMain/composeResources/drawable/ic_home.xml` around lines
6 - 12, Remove the unnecessary <group> wrapper and the no-op <clip-path
android:pathData="M0,0h24v24h-24z">; keep the single <path
android:pathData="M3,10.394L1.654,11.557..."> at the root of the vector drawable
and preserve its android:fillColor and android:fillType attributes so the visual
output is unchanged while simplifying the XML structure.
composeApp/src/commonMain/kotlin/com/daedan/festabook/presentation/common/component/CoilImage.kt (1)

36-43: Preview에서 빈 URL 사용 시 Coil3의 네트워크 요청 시도 가능성

Coil 3.0.0부터 프리뷰 동작이 변경되어 요청을 실제로 실행하게 되었으며, URL에 대한 네트워크 접근은 프리뷰 환경에서 허용되지 않습니다. 해결책으로는 커스텀 ImageLoader를 제공하거나 커스텀 프리뷰 핸들러를 설정하는 방법이 있습니다.

현재는 url = ""으로 비어 있어 실제 네트워크 요청이 발생하지 않을 가능성이 높으나, 추후 유효한 URL로 바뀌거나 다른 Preview에서 재사용될 경우를 대비해 LocalAsyncImagePreviewHandler를 설정하는 것을 고려해 보세요.

♻️ 수정 제안: Preview 핸들러 설정
+import coil3.annotation.ExperimentalCoilApi
+import coil3.compose.AsyncImagePreviewHandler
+import coil3.compose.LocalAsyncImagePreviewHandler
+import coil3.ColorImage
+import androidx.compose.runtime.CompositionLocalProvider

 `@Composable`
 `@Preview`
+@OptIn(ExperimentalCoilApi::class)
 private fun CoilImagePreview() {
+    CompositionLocalProvider(
+        LocalAsyncImagePreviewHandler provides AsyncImagePreviewHandler {
+            ColorImage(android.graphics.Color.LTGRAY)
+        }
+    ) {
         CoilImage(
             url = "https://example.com/image.jpg",
-            contentDescription = "",
+            contentDescription = null,
         )
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@composeApp/src/commonMain/kotlin/com/daedan/festabook/presentation/common/component/CoilImage.kt`
around lines 36 - 43, The Preview currently calls CoilImage from
CoilImagePreview which can trigger Coil 3 network requests in preview; wrap the
preview call in a CompositionLocalProvider that supplies a preview-safe handler
(use LocalAsyncImagePreviewHandler to provide a no-op/preview handler) or
alternatively supply a custom preview ImageLoader to CoilImage so no real
network requests occur; update CoilImagePreview to provide
LocalAsyncImagePreviewHandler (or a preview ImageLoader) before invoking
CoilImage to ensure previews never perform network I/O.
composeApp/src/commonMain/kotlin/com/daedan/festabook/App.kt (1)

29-33: [선택 사항] Modifier 체인 순서 — 배경색이 safe inset 영역을 덮지 않음

현재 순서 modifier.background().safeContentPadding().fillMaxSize()에서는 safeContentPadding()fillMaxSize() 앞에 위치하므로 background가 그려지는 최종 크기는 safe inset이 제외된 영역으로 한정됩니다. 즉, 상태바/내비게이션바 영역에 배경색이 채워지지 않을 수 있습니다.

전체 화면을 배경색으로 채우고 내부 콘텐츠에만 safe padding을 적용하려면 순서를 아래와 같이 변경하는 것이 일반적입니다:

♻️ Modifier 체인 순서 수정 제안
         modifier =
             modifier
-                .background(MaterialTheme.colorScheme.primaryContainer)
-                .safeContentPadding()
-                .fillMaxSize(),
+                .fillMaxSize()
+                .background(MaterialTheme.colorScheme.primaryContainer)
+                .safeContentPadding(),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@composeApp/src/commonMain/kotlin/com/daedan/festabook/App.kt` around lines 29
- 33, The modifier chain applies background before sizing and padding, so the
background is confined to the safe area; reorder it so the composable first
fills the screen, then paints the background, then applies safe insets for inner
content — change the chain around the modifier in App.kt from
modifier.background(...).safeContentPadding().fillMaxSize() to
modifier.fillMaxSize().background(MaterialTheme.colorScheme.primaryContainer).safeContentPadding()
(keep the same color/theme reference).
composeApp/src/commonMain/kotlin/com/daedan/festabook/presentation/common/component/FestabookImage.kt (2)

67-73: builder 적용 순서가 CoilImage.kt와 반대 — URL을 덮어쓸 수 있음

CoilImage.kt에서는 .apply(builder) 이후 .data(url) 를 호출하여 URL이 항상 우선권을 갖지만, 여기서는 .data(convertedUrl) 이후 .apply(builder) 를 호출합니다. 호출자가 builder에서 .data()를 설정하면 convertedUrl이 덮어씌워지는 미묘한 footgun이 됩니다.

♻️ 수정 제안
 imageRequest = {
     ImageRequest
         .Builder(context)
-        .data(convertedUrl)
         .apply(builder)
+        .data(convertedUrl)
         .build()
 },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@composeApp/src/commonMain/kotlin/com/daedan/festabook/presentation/common/component/FestabookImage.kt`
around lines 67 - 73, The imageRequest builder currently calls
.data(convertedUrl) before .apply(builder), so a caller-provided builder can
overwrite the URL; change the call order in the ImageRequest creation inside
imageRequest (the ImageRequest.Builder(context) block) to invoke .apply(builder)
first and then .data(convertedUrl) so convertedUrl always takes precedence
(mirroring CoilImage.kt where .apply(builder) is called before .data(url)).

51-51: isZoomable = false일 때도 rememberZoomableState() 불필요하게 생성됨

zoomableStateisZoomable = true인 경우에만 사용되지만, 항상 Compose 슬롯 테이블에 할당됩니다. 조건부로 선언하면 불필요한 객체 생성을 방지할 수 있습니다.

♻️ 수정 제안
-val zoomableState = rememberZoomableState()
+val zoomableState = if (isZoomable) rememberZoomableState() else null

그리고 사용처도 함께 수정:

-if (isZoomable) {
-    +ZoomablePlugin(state = zoomableState)
-}
+if (isZoomable && zoomableState != null) {
+    +ZoomablePlugin(state = zoomableState)
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@composeApp/src/commonMain/kotlin/com/daedan/festabook/presentation/common/component/FestabookImage.kt`
at line 51, zoomableState를 항상 생성하지 말고 isZoomable이 true일 때만 생성하도록 변경하세요: 대신
rememberZoomableState() 호출을 조건부로 실행(예: val zoomableState = if (isZoomable)
rememberZoomableState() else null)하고, 그 후 사용처(예: zoomableState 관련 호출/전달)를
null-safe하게 처리하거나 let/?.를 사용해 isZoomable이 true일 때만 접근하도록 수정하세요; 관련 심볼:
rememberZoomableState, zoomableState, isZoomable.
composeApp/src/commonMain/kotlin/com/daedan/festabook/presentation/common/component/URLText.kt (1)

53-73: buildAnnotatedStringremember(text)로 캐싱하세요.

text가 바뀌지 않아도 매 리컴포지션마다 buildAnnotatedString 블록과 WEB_REGEX.findAll(text) 정규식 탐색이 재실행됩니다. remember(text)로 감싸면 불필요한 재계산을 방지할 수 있습니다.

♻️ 제안 수정
-    val linkedText =
-        buildAnnotatedString {
+    val linkedText = remember(text) {
+        buildAnnotatedString {
             append(text)
             WEB_REGEX.findAll(text).forEach { result ->
                 addStyle(
                     style =
                         SpanStyle(
                             color = FestabookColor.gray500,
                             textDecoration = TextDecoration.Underline,
                         ),
                     start = result.range.first,
                     end = result.range.last + 1,
                 )
                 addStringAnnotation(
                     tag = "URL",
                     annotation = result.value,
                     start = result.range.first,
                     end = result.range.last + 1,
                 )
             }
-        }
+        }
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@composeApp/src/commonMain/kotlin/com/daedan/festabook/presentation/common/component/URLText.kt`
around lines 53 - 73, The linkedText annotated string is rebuilt on every
recomposition because buildAnnotatedString and WEB_REGEX.findAll(text) run
unconditionally; wrap the buildAnnotatedString call with remember(text) (i.e.
val linkedText = remember(text) { buildAnnotatedString { ... } }) so the
annotation and regex scanning are cached until text changes, keeping the
existing use of WEB_REGEX.findAll and the "URL" string annotation.
composeApp/src/commonMain/kotlin/com/daedan/festabook/presentation/theme/FestabookColor.kt (1)

26-29: 테마 토큰 접근자 네이밍 불일치

FestabookColor (PascalCase)와 festabookSpacing / festabookShapes (camelCase) 사이에 명명 규칙이 일관되지 않습니다. Kotlin에서 PascalCase는 타입/클래스에 사용하는 관례이므로, FestabookColorfestabookColor로, 또는 반대로 festabookSpacing / festabookShapes를 PascalCase로 통일하는 것을 권장합니다. (FestabookTypography.ktFestabookTypography 접근자도 동일하게 통일 대상입니다.)

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

In
`@composeApp/src/commonMain/kotlin/com/daedan/festabook/presentation/theme/FestabookColor.kt`
around lines 26 - 29, The accessor naming is inconsistent: change the
FestabookColor accessor to camelCase to match existing tokens (festabookSpacing,
festabookShapes) or rename the others to PascalCase; specifically update the
property/getter symbol FestabookColor in FestabookColor.kt (and similarly
FestabookTypography in FestabookTypography.kt if you choose PascalCase) so all
theme token accessors use the same convention (prefer festabookColor to follow
existing festabookSpacing/festabookShapes), and update any call sites that
reference FestabookColor to the new name.
composeApp/src/commonMain/kotlin/com/daedan/festabook/presentation/common/component/ErrorStateScreen.kt (1)

45-49: 프리뷰가 FestabookTheme로 감싸지지 않아 커스텀 폰트가 적용되지 않음

LocalTypography의 기본값은 Typography()(Material3 기본값)이므로, FestabookTheme 없이 프리뷰를 렌더링하면 Pretendard 폰트가 표시되지 않습니다. 실제 디자인과 다른 프리뷰가 표출됩니다.

♻️ 수정 제안
 `@Preview`(showBackground = true)
 `@Composable`
 private fun ErrorStateScreenPreview() {
-    ErrorStateScreen()
+    FestabookTheme {
+        ErrorStateScreen()
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@composeApp/src/commonMain/kotlin/com/daedan/festabook/presentation/common/component/ErrorStateScreen.kt`
around lines 45 - 49, The preview ErrorStateScreenPreview is rendering without
your FestabookTheme so LocalTypography falls back to Material3 defaults (losing
Pretendard); wrap the preview body in FestabookTheme and call ErrorStateScreen
inside it (i.e., update the ErrorStateScreenPreview composable to invoke
FestabookTheme { ErrorStateScreen() }) so the custom typography and fonts are
applied in preview.
composeApp/src/commonMain/kotlin/com/daedan/festabook/presentation/theme/FestabookTypography.kt (1)

16-90: FestabookTypographies 호출 시마다 새 인스턴스 생성 → staticCompositionLocalOf 소비자 불필요 리컴포지션 유발

PretendardBold / PretendardMedium / PretendardRegular@Composable getter로, 각 접근 시마다 새 FontFamily 객체를 생성합니다. 이를 사용하는 FestabookTypographies 역시 매 호출마다 새 Typography 인스턴스를 반환합니다.

FestabookTheme에서는 다음과 같이 사용됩니다.

// FestabookTheme.kt (L17)
val typography = FestabookTypographies  // 매 리컴포지션마다 새 인스턴스
CompositionLocalProvider(
    LocalTypography provides typography, // 새 참조 → staticCompositionLocalOf 소비자 전체 리컴포지션
)

staticCompositionLocalOf는 등가성 비교를 하지 않으므로, 제공된 값의 참조가 바뀌면 LocalTypography 소비자 전체가 리컴포지션됩니다. FestabookTheme가 리컴포지션될 때마다 이 연쇄가 발생합니다.

Font()@Composable이므로 remember {} 내부에서 직접 호출할 수 없지만, 아래와 같이 FestabookTheme 내에서 결과를 재사용하거나 compositionLocalOf를 사용해 동일 참조일 때 소비자 리컴포지션을 건너뛰도록 개선할 수 있습니다.

♻️ 개선 방향 제안
-val LocalTypography = staticCompositionLocalOf { Typography() }
+// 동일 참조 시 소비자 리컴포지션을 건너뛸 수 있도록 compositionLocalOf 사용
+// (단, Typography equality 확보를 위해 FestabookTheme에서도 캐싱 전략 필요)
+val LocalTypography = compositionLocalOf { Typography() }

또한 FestabookTypographiesFestabookTypography의 이름이 단수/복수로만 구분되어 혼동하기 쉽습니다. FestabookTypographiesfestabookTypographyTokens 등으로 구분하거나, 인스턴스 생성 로직을 별도 함수(@Composable fun rememberFestabookTypography(): Typography)로 분리하는 것을 권장합니다.

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

In
`@composeApp/src/commonMain/kotlin/com/daedan/festabook/presentation/theme/FestabookTypography.kt`
around lines 16 - 90, FestabookTypographies currently builds new
FontFamily/Typeface objects on every access (PretendardBold, PretendardMedium,
PretendardRegular are `@Composable` getters), causing FestabookTypographies to
return a fresh Typography each call and triggering full re-composition of
LocalTypography consumers in FestabookTheme; fix by extracting the creation into
a remembered composable (e.g., add `@Composable` fun
rememberFestabookTypography(): Typography) that constructs FontFamily(Font(...))
for PretendardBold/Medium/Regular once and returns a remembered Typography
instance (use remember inside the composable) and then use
rememberFestabookTypography() in FestabookTheme when providing LocalTypography
so the provided Typography reference stays stable across recompositions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@composeApp/src/commonMain/composeResources/drawable/bg_gray050_radius_10dp.xml`:
- Around line 1-7: The drawable uses an unsupported <shape> XML and an external
Android color reference (`@color/gray050`), so Res.drawable.bg_gray050_radius_10dp
cannot be loaded by Compose Multiplatform; either replace usages with a Compose
Modifier background using Gray050 and RoundedCornerShape(10.dp) in the
Composable that consumes this resource, or move this XML into an Android-only
resource directory (androidMain/res/drawable/) and remove the `@color/` reference
(or inline the color) for multiplatform compatibility; also audit the other bg_*
and ic_*.xml files in the PR for the same <shape> and `@android`:color/ or `@color/`
patterns and apply the same change.

In
`@composeApp/src/commonMain/composeResources/drawable/bg_gray100_stroke_gray400_radius_10dp.xml`:
- Around line 1-9: This XML uses unsupported Android shape drawables and `@color`
references in commonMain/composeResources; either replace its usage with a
Compose-native Modifier (use Modifier.clip(RoundedCornerShape(10.dp)),
.background(Gray100), and .border(1.dp, Gray400, RoundedCornerShape(10.dp)) in
your commonMain composables referencing Gray100/Gray400 color objects) or move
the XML into the Android-specific source set (androidMain/res/drawable/) so the
Android resource pipeline can handle the <shape> and `@color/` references; update
any other similar files (e.g., bg_gray100_stroke_gray200_radius_10dp.xml) the
same way.

In `@composeApp/src/commonMain/composeResources/drawable/btn_fab_manu.xml`:
- Line 1: The drawable filename btn_fab_manu.xml appears to be a typo and should
likely be btn_fab_menu.xml; rename the file to btn_fab_menu.xml and update every
reference to it (e.g., in XML layouts, Compose resources, styles, or any
imports) to use btn_fab_menu instead of btn_fab_manu, and verify the internal
resource name (if defined) matches the new filename so the build and resource
lookups (refs to btn_fab_manu) are all replaced.
- Around line 2-19: Remove the unsupported aapt:attr inline gradient block and
replace it with a direct fill color on the VectorDrawable so it can be compiled
in commonMain; locate the <path> element that currently contains the <aapt:attr
name="android:fillColor">…<gradient>…</gradient></aapt:attr> (note the
xmlns:aapt declaration) and delete the entire aapt:attr/gradient subtree, then
add a simple android:fillColor="...hex..." attribute on that same <path> element
(or on a top-level element if appropriate) to use a single solid color instead
of the unsupported radial gradient.

In `@composeApp/src/commonMain/composeResources/drawable/ic_home.xml`:
- Line 10: Replace the Android theme attribute reference
android:fillColor="?attr/colorControlNormal" in the commonMain vector drawable
(ic_home.xml) with a platform-neutral solution: either hard-code a hex fillColor
value (e.g., android:fillColor="#FF000000") or remove the theme reference and
control tinting from Compose using ColorFilter.tint when painting the Resource
(see usage with painterResource/ColorFilter). Apply the same fix to the other
common drawables listed (ic_setting.xml, ic_schedule.xml, ic_news.xml,
bg_skeleton_*). Also consider removing the unnecessary <group> + <clip-path>
wrapper around the 24×24 viewport (lines ~6-12) since it is redundant.

In
`@composeApp/src/commonMain/composeResources/drawable/ic_map_category_photo_booth.xml`:
- Around line 1-9: The icon's physical size (android:width="18dp",
android:height="17dp") and its viewport (android:viewportWidth="12",
android:viewportHeight="11") have mismatched aspect ratios causing ~3%
non-uniform scaling; fix by either 1) keeping the current pathData and make the
physical size match the viewport ratio (change android:width/android:height so
width/height == 12/11), or 2) keep the physical size and change the viewport to
match it (update android:viewportWidth/android:viewportHeight and proportionally
rescale the pathData coordinates in the <path android:pathData="..."> by X scale
= newViewportWidth / 12 and Y scale = newViewportHeight / 11); update the vector
tag attributes (android:width, android:height, android:viewportWidth,
android:viewportHeight) and/or rescale the pathData accordingly.

In `@composeApp/src/commonMain/composeResources/drawable/ic_primary_selected.xml`:
- Around line 9-13: The white stroke on the <path> (android:pathData centered at
12,12 with radius 11.5 and android:strokeWidth="1") may be clipped by the
current viewport; update the vector's android:viewportWidth to 25 (or
equivalently increase viewportHeight if symmetrical) or reduce the circle
radius/center so the outer stroke stays inside the viewport—adjust the
android:viewportWidth attribute or the values in the android:pathData (radius
from 11.5 to ~11.0 or shift center slightly) to ensure the stroke does not touch
the viewport edge.

In
`@composeApp/src/commonMain/kotlin/com/daedan/festabook/presentation/common/component/CoilImage.kt`:
- Around line 22-33: CoilImage currently only sets placeholder inside
AsyncImage, causing silent empty UI on load failure; update the CoilImage
composable to accept Painter parameters for placeholder and error (e.g.,
placeholder: Painter = ColorPainter(Color.LightGray), error: Painter? = null),
expose them in the function signature, and pass both to AsyncImage's placeholder
and error properties while keeping the existing ImageRequest construction
(ImageRequest.Builder(LocalPlatformContext.current)....data(url.convertImageUrl()).build());
ensure imports for Painter/ColorPainter remain available and the new params are
optional so callers can override.

In
`@composeApp/src/commonMain/kotlin/com/daedan/festabook/presentation/common/component/FestabookImage.kt`:
- Around line 93-100: 폴백 이미지 블록에서 contentDescription이 하드코딩("fallback_image")되어
있어 스크린 리더가 원래 전달된 설명을 읽지 못하므로, FestabookImage 컴포저(혹은 해당 함수)의 failure 람다 안 Image
호출에서 contentDescription 매개변수(예: contentDescription)를 그대로 사용하도록 변경하세요; 전달된
contentDescription이 nullable일 수 있으므로 필요하면 적절한 기본값(null 또는 빈 문자열) 처리만 추가하고 하드코딩된
문자열을 제거해 접근성을 복원하세요.
- Line 161: Rename the preview composable function from DiaplogPreview to
DialogPreview to fix the typo; update the function declaration private fun
DiaplogPreview() { to private fun DialogPreview() { and adjust any
references/usages of DiaplogPreview elsewhere in the file to the new
DialogPreview identifier so the preview runs correctly.

In
`@composeApp/src/commonMain/kotlin/com/daedan/festabook/presentation/common/component/FestabookTopAppBar.kt`:
- Around line 22-29: The top padding in FestabookTopAppBar incorrectly uses the
horizontal token paddingTitleHorizontal from FestabookSpacing; either replace
the top argument with the correct vertical token (e.g., paddingTitleTop) or
rename paddingTitleHorizontal in FestabookSpacing to a vertical name if its
intended purpose is top padding. Locate the usage in FestabookTopAppBar
(modifier.padding(top = paddingTitleHorizontal, ...)) and update it to use the
vertically-intended token (or rename the token in FestabookSpacing and update
all call sites) so the token name matches its directional intent.

In
`@composeApp/src/commonMain/kotlin/com/daedan/festabook/presentation/common/component/PullToRefreshContainer.kt`:
- Around line 100-108: The Icon composable using
painterResource(Res.drawable.logo_title) currently sets contentDescription =
stringResource(Res.string.logo_splash), which is the wrong string; update the
contentDescription to the correct string resource that describes logo_title
(e.g., stringResource(Res.string.logo_title)) or, if the icon is purely
decorative, set contentDescription = null; locate the Icon block in
PullToRefreshContainer.kt and change the stringResource reference or null
accordingly to fix accessibility.
- Around line 59-62: Clamp the translation amount so content cannot shift beyond
pullOffsetLimit by applying coerceIn(0f, 1f) to
pullToRefreshState.distanceFraction before multiplying by pullOffsetLimit;
update the Modifier.graphicsLayer block in PullToRefreshContainer to use the
clamped fraction (same behavior as the indicator logic) to prevent translationY
from exceeding pullOffsetLimit.
- Around line 46-55: PullToRefreshIndicator가 내부적으로 pullToRefresh 수식자를 적용해 이중 제스처
리스너를 만들어 onRefresh가 두 번 호출되므로 PullToRefreshIndicator 내부 Box에서 pullToRefresh 수식자를
제거하고, 그에 따라 PullToRefreshIndicator의 시그니처에서 onRefresh와 threshold 파라미터를 제거하고
PullToRefreshContainer에서 indicator 호출 시 해당 인자들을 제거하세요; 또한 컨텐츠의 이동
계산부(translationY = pullToRefreshState.distanceFraction * pullOffsetLimit)에서
distanceFraction을 coerceIn(0f, 1f)로 제한하고 아이콘의 contentDescription을
Res.string.logo_title로 변경하여 접근성을 맞추세요 (참조: PullToRefreshIndicator,
PullToRefreshBox, PullToRefreshContainer, pullToRefresh, distanceFraction,
pullOffsetLimit, Res.drawable.logo_title, Res.string.logo_title).

In
`@composeApp/src/commonMain/kotlin/com/daedan/festabook/presentation/common/component/URLText.kt`:
- Around line 57-62: The URL span color is hardcoded to FestabookColor.gray500
which ignores the composable's color parameter and breaks dark/theme support;
update the URL styling in the addStyle/SpanStyle usage inside URLText (or the
function that builds spans) to use a theme-aware color (e.g.
MaterialTheme.colorScheme.primary) or add a new linkColor parameter (propagate
it through URLText and use it when creating the SpanStyle) so the link color is
not fixed to FestabookColor.gray500 and respects passed-in color or theme.
- Around line 76-89: Replace the stale-closure usage inside the
pointerInput/detectTapGestures block by wrapping the changing values in
rememberUpdatedState so the coroutine always sees the latest values: create
currentLinkedText = rememberUpdatedState(linkedText) and currentOnClick =
rememberUpdatedState(onClick) and then inside the pointerInput/detectTapGestures
handler use currentLinkedText.value and currentOnClick.value (together with
existing layoutResult and uriHandler) instead of directly referencing linkedText
and onClick.
- Around line 112-113: The WEB_REGEX in URLText.kt is wrong: it omits '-' in the
character class and wrongly includes the 'file' scheme; update WEB_REGEX to only
allow safe schemes (remove "file" from "(https?|ftp|file)") and add '-' into the
allowed character class (either escape it or place it at the end/beginning) so
domains/paths with hyphens match correctly and file:// URIs are not matched to
avoid Android FileUriExposedException; locate the WEB_REGEX constant and adjust
the scheme group and character class accordingly.

In
`@composeApp/src/commonTest/kotlin/com/daedan/festabook/data/repository/fixture/PlaceListRepositoryTestFixture.kt`:
- Around line 44-54: The test fixture PlaceListRepositoryTestFixture defines
polygonHoleBoundary with only two FestivalGeographyResponse.PolygonHoleBoundary
points which is invalid for a polygon; update polygonHoleBoundary in
PlaceListRepositoryTestFixture to include at least one more
FestivalGeographyResponse.PolygonHoleBoundary (a third coordinate with
latitude/longitude) so the hole is a valid polygon (minimum 3 vertices) for
geometry/boundary validation and rendering tests.

---

Duplicate comments:
In `@composeApp/src/androidMain/kotlin/com/daedan/festabook/ImageUtil.android.kt`:
- Line 13: The code force-unwraps the result of AppCompatResources.getDrawable
(val drawable = AppCompatResources.getDrawable(context, vectorResId)!!) which
can be null and causes an NPE; update the function that uses this (the code
creating "drawable") to safely handle null by removing the "!!" and either (a)
check for null and return a safe fallback or log and return null (change the
return type to Bitmap? if appropriate), or (b) throw a descriptive exception
with context; ensure you reference AppCompatResources.getDrawable and the local
variable drawable when adding the null-check and downstream handling so callers
no longer crash on missing drawables.

In
`@composeApp/src/commonMain/composeResources/drawable/bg_gray_050_stroke_gray200_radius_10dp.xml`:
- Around line 1-11: The drawable uses Android `@color` references that don't exist
in the multiplatform resource set and the filename is inconsistent: update
either by (A) replacing this XML drawable
(bg_gray_050_stroke_gray200_radius_10dp.xml) with Compose-based styling in
commonMain (use color values from FestabookColor) or (B) add the missing colors
to your Android resources and FestabookColor: create gray050 and gray200
definitions in FestabookColor and add corresponding entries in Android
res/values/colors.xml so `@color/gray050` and `@color/gray200` resolve at build
time; also rename the file to match the sibling naming convention (e.g., make it
bg_gray050_stroke_gray200_radius_10dp.xml) so filenames are consistent.

In
`@composeApp/src/commonMain/composeResources/drawable/bg_skeleton_radius_16dp.xml`:
- Around line 1-5: The bg_skeleton_radius_16dp.xml in composeResources/drawable
is invalid for CMP: remove this Android <shape> drawable (it also lacks the XML
declaration and uses unsupported ?attr/SkeletonBackground theme reference) and
replace any references to bg_skeleton_radius_16dp with a Compose-based
background (e.g., use Modifier.background with RoundedCornerShape(16.dp) and a
platform-agnostic color from your Compose theme or a SkeletonBackground color
defined in commonMain). Ensure you delete the XML file and update call sites to
stop referencing the drawable and instead use the Compose modifier-based
styling.

In `@composeApp/src/commonMain/composeResources/drawable/bg_spinner_dropdown.xml`:
- Around line 1-21: The XML <shape> drawable bg_spinner_dropdown.xml and its
color references (`@color/gray050`, `@color/gray300`) are not supported by the CMP
composeResources system; remove this shape drawable and replace its usage with
Compose-native resources — for example, convert the visual (background color,
border width/color, corner radius, padding) into a Compose Modifier/Box or a
Material/Surface with Modifier.background/border/clip and use color values from
your Compose color tokens (not `@color` XML); ensure any references to
bg_spinner_dropdown.xml are updated to use the new Compose component or a
theming color (replace `@color/gray050/`@color/gray300 with the Compose color
variables you maintain) and delete the XML file.

---

Nitpick comments:
In
`@composeApp/src/commonMain/composeResources/drawable/btn_back_to_previous.xml`:
- Around line 6-17: The file btn_back_to_previous.xml breaks the project's icon
naming convention and may duplicate ic_arrow_back.xml; rename or consolidate to
follow the ic_ prefix convention and clarify intent: either rename
btn_back_to_previous.xml to ic_arrow_back_circle.xml (or similar) if it
represents an arrow-with-circle compound asset, update any references to that
new name, and remove or merge with ic_arrow_back.xml if they are redundant;
ensure the drawable content (the two <path> elements) matches the chosen
canonical asset and update resource usages across the codebase to reference the
unified ic_* name.
- Around line 9-10: 버튼 백 아이콘 벡터에서 하드코딩된 색상(android:fillColor="#ffffff",
android:strokeColor="#EDEDED" 및 화살표의 "#000000")을 제거하고 아이콘
XML(btn_back_to_previous.xml)의 path 및 group 요소에서 색상 속성을 비우거나 기본(투명/무색)으로 변경해 호출
측 Composable에서 ColorFilter 또는 tint로 색상을 주입하도록 변경하세요; 대상 식별자:
btn_back_to_previous.xml 파일의 android:fillColor 및 android:strokeColor 속성(및 화살표
path의 fill/stroke 색상)을 수정하면 됩니다. Ensure the drawable contains no hardcoded
colors so theme-aware tinting from the Composable can be applied.

In `@composeApp/src/commonMain/composeResources/drawable/ic_booth.xml`:
- Line 1: The vector root element declares an unused namespace
xmlns:aapt="http://schemas.android.com/aapt"; remove that attribute from the
<vector> element (the vector XML root) so the file no longer includes the unused
aapt namespace declaration.

In `@composeApp/src/commonMain/composeResources/drawable/ic_chevron_down.xml`:
- Line 11: The hardcoded stroke color in ic_chevron_down.xml
(android:strokeColor="#5C5C5C") breaks dark mode; change the XML to use a
neutral color (e.g., `#000000` or `#FFFFFF`) or reference a color resource (e.g.,
`@color/ic_neutral`) instead of the literal "#5C5C5C", and control actual display
color from the Compose call site (e.g., when using Icon/painterResource set tint
= MaterialTheme.colorScheme.onSurface or a colorFilter) so Image usages also
adapt to theme.

In `@composeApp/src/commonMain/composeResources/drawable/ic_cluster_marker.xml`:
- Around line 9-10: The vector drawable ic_cluster_marker.xml currently
hardcodes android:fillColor="#1F1F1F" and android:strokeColor="#ffffff",
preventing theme adaptation; change android:fillColor to "#000000" and remove or
avoid hardcoded android:strokeColor (and any android:tint attributes) so the
drawable is neutral, then apply theming at usage (e.g., via colorFilter =
ColorFilter.tint(...) when calling painterResource) or provide theme-specific
variants (drawable-dark / drawable-light) for ic_cluster_marker; update the file
to only use neutral/default colors and rely on external tinting or variant
resources.

In `@composeApp/src/commonMain/composeResources/drawable/ic_home.xml`:
- Around line 6-12: Remove the unnecessary <group> wrapper and the no-op
<clip-path android:pathData="M0,0h24v24h-24z">; keep the single <path
android:pathData="M3,10.394L1.654,11.557..."> at the root of the vector drawable
and preserve its android:fillColor and android:fillType attributes so the visual
output is unchanged while simplifying the XML structure.

In
`@composeApp/src/commonMain/composeResources/drawable/ic_map_category_stage.xml`:
- Line 8: The drawable ic_map_category_stage.xml currently hardcodes
android:fillColor="#B910D6", preventing runtime tinting; change the fillColor to
a transparent value (e.g. "#00000000" or "#FFFFFF" as used by other tintable
icons) so the Compose caller can apply MapCategoryColor.Stage via tint; update
the drawable pattern to match the transparent icons (booth, smoking, toilet,
trash) and ensure Compose code uses tint = MapCategoryColor.Stage to control
brand/dark-mode/selected states at runtime.

In
`@composeApp/src/commonMain/composeResources/drawable/ic_place_detail_host.xml`:
- Around line 1-9: The vector drawable currently hardcodes color via the path
attribute android:fillColor="#474747", which prevents theme/tinting; remove the
android:fillColor attribute from the <path> in ic_place_detail_host.xml (or make
it empty) so the drawable can be tinted by the consuming Icon/Image composable
via its tint parameter; locate the <path> element in the vector and delete the
android:fillColor="#474747" entry and ensure callers apply the theme color
through the composable's tint.

In `@composeApp/src/commonMain/composeResources/drawable/ic_stage.xml`:
- Around line 1-5: 뷰포트 비율(24×29)과 물리적 크기(android:width="28dp"
android:height="34dp")의 종횡비가 미세하게 달라져 painterResource()로 렌더링 시 비균일 스케일이 생길 수 있으니
일치시키세요: 수정 방법은 vector 태그의 android:width/android:height를 viewport 비율에 맞게
24dp×29dp로 바꾸거나 반대로 android:viewportWidth/android:viewportHeight를 28×34로 바꿔 뷰포트와
물리적 크기의 비율을 일치시키는 것입니다; 관련 속성은 vector 태그의 android:width, android:height,
android:viewportWidth, android:viewportHeight입니다.

In `@composeApp/src/commonMain/composeResources/drawable/ic_trash_selected.xml`:
- Around line 1-5: The VectorDrawable's canvas aspect ratio
(android:width="30dp" android:height="51dp") does not exactly match the viewport
aspect ratio (android:viewportWidth="24" android:viewportHeight="41"), causing a
~0.49% horizontal distortion; fix by making the ratios identical—either adjust
android:viewportWidth/android:viewportHeight to match the 30/51 canvas ratio or
change android:width/android:height to match the 24/41 viewport ratio (keep the
existing pathData unchanged) so scaling is uniform.

In `@composeApp/src/commonMain/kotlin/com/daedan/festabook/App.kt`:
- Around line 29-33: The modifier chain applies background before sizing and
padding, so the background is confined to the safe area; reorder it so the
composable first fills the screen, then paints the background, then applies safe
insets for inner content — change the chain around the modifier in App.kt from
modifier.background(...).safeContentPadding().fillMaxSize() to
modifier.fillMaxSize().background(MaterialTheme.colorScheme.primaryContainer).safeContentPadding()
(keep the same color/theme reference).

In
`@composeApp/src/commonMain/kotlin/com/daedan/festabook/presentation/common/component/CoilImage.kt`:
- Around line 36-43: The Preview currently calls CoilImage from CoilImagePreview
which can trigger Coil 3 network requests in preview; wrap the preview call in a
CompositionLocalProvider that supplies a preview-safe handler (use
LocalAsyncImagePreviewHandler to provide a no-op/preview handler) or
alternatively supply a custom preview ImageLoader to CoilImage so no real
network requests occur; update CoilImagePreview to provide
LocalAsyncImagePreviewHandler (or a preview ImageLoader) before invoking
CoilImage to ensure previews never perform network I/O.

In
`@composeApp/src/commonMain/kotlin/com/daedan/festabook/presentation/common/component/ErrorStateScreen.kt`:
- Around line 45-49: The preview ErrorStateScreenPreview is rendering without
your FestabookTheme so LocalTypography falls back to Material3 defaults (losing
Pretendard); wrap the preview body in FestabookTheme and call ErrorStateScreen
inside it (i.e., update the ErrorStateScreenPreview composable to invoke
FestabookTheme { ErrorStateScreen() }) so the custom typography and fonts are
applied in preview.

In
`@composeApp/src/commonMain/kotlin/com/daedan/festabook/presentation/common/component/FestabookImage.kt`:
- Around line 67-73: The imageRequest builder currently calls
.data(convertedUrl) before .apply(builder), so a caller-provided builder can
overwrite the URL; change the call order in the ImageRequest creation inside
imageRequest (the ImageRequest.Builder(context) block) to invoke .apply(builder)
first and then .data(convertedUrl) so convertedUrl always takes precedence
(mirroring CoilImage.kt where .apply(builder) is called before .data(url)).
- Line 51: zoomableState를 항상 생성하지 말고 isZoomable이 true일 때만 생성하도록 변경하세요: 대신
rememberZoomableState() 호출을 조건부로 실행(예: val zoomableState = if (isZoomable)
rememberZoomableState() else null)하고, 그 후 사용처(예: zoomableState 관련 호출/전달)를
null-safe하게 처리하거나 let/?.를 사용해 isZoomable이 true일 때만 접근하도록 수정하세요; 관련 심볼:
rememberZoomableState, zoomableState, isZoomable.

In
`@composeApp/src/commonMain/kotlin/com/daedan/festabook/presentation/common/component/URLText.kt`:
- Around line 53-73: The linkedText annotated string is rebuilt on every
recomposition because buildAnnotatedString and WEB_REGEX.findAll(text) run
unconditionally; wrap the buildAnnotatedString call with remember(text) (i.e.
val linkedText = remember(text) { buildAnnotatedString { ... } }) so the
annotation and regex scanning are cached until text changes, keeping the
existing use of WEB_REGEX.findAll and the "URL" string annotation.

In
`@composeApp/src/commonMain/kotlin/com/daedan/festabook/presentation/theme/FestabookColor.kt`:
- Around line 26-29: The accessor naming is inconsistent: change the
FestabookColor accessor to camelCase to match existing tokens (festabookSpacing,
festabookShapes) or rename the others to PascalCase; specifically update the
property/getter symbol FestabookColor in FestabookColor.kt (and similarly
FestabookTypography in FestabookTypography.kt if you choose PascalCase) so all
theme token accessors use the same convention (prefer festabookColor to follow
existing festabookSpacing/festabookShapes), and update any call sites that
reference FestabookColor to the new name.

In
`@composeApp/src/commonMain/kotlin/com/daedan/festabook/presentation/theme/FestabookTypography.kt`:
- Around line 16-90: FestabookTypographies currently builds new
FontFamily/Typeface objects on every access (PretendardBold, PretendardMedium,
PretendardRegular are `@Composable` getters), causing FestabookTypographies to
return a fresh Typography each call and triggering full re-composition of
LocalTypography consumers in FestabookTheme; fix by extracting the creation into
a remembered composable (e.g., add `@Composable` fun
rememberFestabookTypography(): Typography) that constructs FontFamily(Font(...))
for PretendardBold/Medium/Regular once and returns a remembered Typography
instance (use remember inside the composable) and then use
rememberFestabookTypography() in FestabookTheme when providing LocalTypography
so the provided Typography reference stays stable across recompositions.

Comment on lines +1 to +9
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="10dp" />
<solid android:color="@color/gray100" />
<stroke
android:width="1dp"
android:color="@color/gray400" />
</shape> 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 | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Compose Multiplatform composeResources supported drawable formats vector shape XML

💡 Result:

In Compose Multiplatform composeResources/drawable, the supported “drawable” file formats are:

  • Raster images: JPEG/JPG, PNG, BMP, WebP (and “bitmap” in general). [1]
  • Android Vector Drawable XML (.xml vector) only, and it must not reference Android resources (no @drawable/..., @color/..., theme attrs, etc.). [1][2]

What’s not supported as “drawable XML”:

  • Android shape/selector/layer-list/inset/level-list (i.e., non-vector drawable XML). Only VectorDrawable XML is supported. [1][2]

SVG note:

  • SVG is supported on all platforms except Android, but it’s typically handled by reading bytes and decoding (e.g., decodeToSvgPainter), not as an Android “drawable XML” shape. [2][3]

Sources:
[1] JetBrains KMP docs – resources setup (supported drawable formats) (jetbrains.com)
[2] Kotlin Multiplatform docs – resources usage (painterResource loads raster or XML vector; SVG exception on Android) (kotlinlang.org)
[3] JetBrains KMP docs – decoding images from bytes (includes SVG decoding) (jetbrains.com)

Citations:


commonMain/composeResources에서 지원되지 않는 <shape> 포맷 및 @color/ 참조 사용

이 파일은 Compose Multiplatform 리소스 시스템에서 두 가지 치명적인 문제를 갖고 있습니다:

문제 1 — 지원되지 않는 XML 포맷: composeResources/drawable은 래스터 이미지(JPEG, PNG, BMP, WebP)와 Android Vector Drawable XML만 지원합니다. 이 파일의 <shape> 엘리먼트는 지원되지 않으며, build 시 리소스 생성기에서 처리되지 않거나 런타임 오류가 발생할 수 있습니다.

문제 2 — Android 리소스 참조 불허: lines 5, 8의 @color/gray100, @color/gray400은 Android 리소스 시스템 참조이며, Vector Drawable XML에서도 명시적으로 금지되어 있습니다. composeResources 리소스 파이프라인에서는 이러한 참조를 해석할 수 없습니다.

권장 해결 방안: commonMain Kotlin 코드에서 Compose Modifier로 동일한 시각적 효과를 구현하세요:

Modifier
    .clip(RoundedCornerShape(10.dp))
    .background(Gray100)
    .border(1.dp, Gray400, RoundedCornerShape(10.dp))

대신 Android 플랫폼 전용이라면 파일을 androidMain/res/drawable/로 이동해야 합니다. PR 설명에 언급된 다른 shape drawable 파일들(bg_gray100_stroke_gray200_radius_10dp.xml 등)도 동일한 문제를 가질 것으로 예상됩니다.

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

In
`@composeApp/src/commonMain/composeResources/drawable/bg_gray100_stroke_gray400_radius_10dp.xml`
around lines 1 - 9, This XML uses unsupported Android shape drawables and `@color`
references in commonMain/composeResources; either replace its usage with a
Compose-native Modifier (use Modifier.clip(RoundedCornerShape(10.dp)),
.background(Gray100), and .border(1.dp, Gray400, RoundedCornerShape(10.dp)) in
your commonMain composables referencing Gray100/Gray400 color objects) or move
the XML into the Android-specific source set (androidMain/res/drawable/) so the
Android resource pipeline can handle the <shape> and `@color/` references; update
any other similar files (e.g., bg_gray100_stroke_gray200_radius_10dp.xml) the
same way.

@@ -0,0 +1,25 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
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

파일명 오타 가능성: btn_fab_manubtn_fab_menu

파일명의 manumenu(메뉴)의 오타일 가능성이 있습니다. PR 내 다른 drawable 파일명 컨벤션과 함께 의도한 이름이 맞는지 확인해주세요.

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

In `@composeApp/src/commonMain/composeResources/drawable/btn_fab_manu.xml` at line
1, The drawable filename btn_fab_manu.xml appears to be a typo and should likely
be btn_fab_menu.xml; rename the file to btn_fab_menu.xml and update every
reference to it (e.g., in XML layouts, Compose resources, styles, or any
imports) to use btn_fab_menu instead of btn_fab_manu, and verify the internal
resource name (if defined) matches the new filename so the build and resource
lookups (refs to btn_fab_manu) are all replaced.

Comment on lines +2 to +19
xmlns:aapt="http://schemas.android.com/aapt"
android:width="68dp"
android:height="68dp"
android:viewportWidth="68"
android:viewportHeight="68">
<path
android:pathData="M34,2L34,2A30,30 0,0 1,64 32L64,32A30,30 0,0 1,34 62L34,62A30,30 0,0 1,4 32L4,32A30,30 0,0 1,34 2z">
<aapt:attr name="android:fillColor">
<gradient
android:centerX="50"
android:centerY="42.5"
android:gradientRadius="58.829"
android:type="radial">
<item android:offset="0" android:color="#FF1B1B1B"/>
<item android:offset="0.535" android:color="#FF2D2D2D"/>
<item android:offset="1" android:color="#FF818181"/>
</gradient>
</aapt:attr>
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

🌐 Web query:

Compose Multiplatform vector drawable Android XML aapt:attr support commonMain

💡 Result:

In Compose Multiplatform, you can use Android VectorDrawable XML from commonMain, but only a subset of the format is supported: the XML must be self‑contained and must not reference Android resources/attrs (anything that requires aapt, like ?attr/..., is not supported). (kotlinlang.org)

What to do about aapt:attr / ?attr/... (the usual cause)

If your vector contains things like:

  • android:tint="?attr/colorControlNormal"
  • android:fillColor="@color/..." or @android:color/...
  • <aapt:attr name="android:fillColor"> ... </aapt:attr>

…edit the file to remove those and use literal colors (or no tint), e.g.:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24"
    android:viewportHeight="24">
    <path
        android:fillColor="#000000"
        android:pathData="..."/>
</vector>

Then apply tint in Compose, not in XML:

Image(
  painter = painterResource(Res.drawable.ic_sample),
  contentDescription = null,
  colorFilter = ColorFilter.tint(Color.Red)
)

This is the approach JetBrains documents for icons that ship with android:tint / theme attrs. (kotlinlang.org)

Where it must live for commonMain

Put the vector in:

  • commonMain/composeResources/drawable/ic_sample.xml

…and load it from commonMain with:

  • painterResource(Res.drawable.ic_sample) (Painter)
  • or vectorResource(Res.drawable.ic_sample) (ImageVector)

(kotlinlang.org)

Citations:


🌐 Web query:

Compose Multiplatform official documentation composeResources drawable Android resources

💡 Result:

In Compose Multiplatform, the official resources system is based on the composeResources directory (typically under commonMain), not Android’s src/androidMain/res/drawable.

  • Put images in: .../commonMain/composeResources/drawable/ (PNG/JPG/BMP/WebP + Android XML vector drawables, as long as they don’t reference Android resources). [1]
  • After building, Compose Multiplatform generates a type-safe Res accessor, and you use images like:
    • Image(painter = painterResource(Res.drawable.my_image), ...) [2]

Android-specific note:

  • Starting with Compose Multiplatform 1.7.0, these multiplatform resources are packed into Android assets (useful for Android Studio previews and WebView/media access via paths like Res.getUri("files/...")). [2]

Official docs to read:

  • “Setup and configuration for multiplatform resources” (directory structure, including composeResources/drawable). [1]
  • “Using multiplatform resources in your app” (Res, painterResource, Android assets packaging). [2]

Sources:
[1] JetBrains Kotlin Multiplatform Docs — Setup & configuration for multiplatform resources
[2] JetBrains Kotlin Multiplatform Docs — Using multiplatform resources in your app


🌐 Web query:

Android aapt:attr inline gradient how it works AAPT2

💡 Result:

<aapt:attr> is AAPT2 “inline complex XML resource” syntax: it lets you embed (“inline”) a child resource (like a <vector>, <objectAnimator>, or a <gradient>) inside another XML file, even when the parent attribute normally expects only a simple value. During the build, AAPT2 extracts that child XML into its own generated resource and replaces the inline block with a reference to the generated resource. This is done at build time, not at runtime. [1][2]

Why it’s used for inline gradients in VectorDrawable

In a VectorDrawable, android:fillColor / android:strokeColor are attributes. Starting on API 24, those attributes can accept not only a simple color, but also a “complex color” such as a gradient (internally represented as a GradientColor). That’s why you’ll see:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:aapt="http://schemas.android.com/aapt" ...>

  <path ...>
    <aapt:attr name="android:fillColor">
      <gradient android:type="linear" ...>
        <item android:offset="0.0" android:color="#FF..." />
        <item android:offset="1.0" android:color="#FF..." />
      </gradient>
    </aapt:attr>
  </path>
</vector>

AAPT2 extracts the <gradient> into a generated resource and wires it into the fillColor attribute location you named in aapt:attr. [1][2]

The key limitation (why it “doesn’t work” on older Android)

Even though AAPT2 can package the resource, VectorDrawable gradients are only supported from API 24+. On older devices, inflating that vector can fail with errors like “invalid … tag gradient” (because the framework / support inflation path doesn’t accept <gradient> as a complex color there). [3]

Common fix: put the gradient vector in drawable-v24/ and provide a non-gradient fallback (solid color / different asset) in drawable/ for <24. [3]

Sources: Android “Inline complex XML resources” docs (explains extraction behavior) [1], AAPT source comment describing extraction of <aapt:attr> into new top-level resources [2], VectorDrawable gradient compatibility discussion [3].


aapt:attr 인라인 그라디언트는 Compose Multiplatform commonMain 리소스에서 지원되지 않습니다.

Compose Multiplatform 공식 문서에 따르면, commonMain/composeResources/drawable/에 위치한 VectorDrawable XML은 "자체 포함되어야 하며 Android 리소스를 참조하지 않아야" 합니다. xmlns:aapt 네임스페이스와 <aapt:attr> 인라인 그라디언트는 명시적으로 지원되지 않는 AAPT2 빌드 도구 전용 구문입니다. 이 파일이 commonMain에 위치하므로 iOS 및 Desktop 플랫폼에서 리소스 컴파일러가 aapt:attr을 처리하지 못해 그라디언트가 표시되지 않거나 파싱 오류가 발생합니다.

권장 수정 방향: aapt:attr 인라인 그라디언트를 제거하고 android:fillColor에 직접 색상값을 지정하세요.

수정 예시
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:aapt="http://schemas.android.com/aapt"
     android:width="68dp"
     android:height="68dp"
     android:viewportWidth="68"
     android:viewportHeight="68">
   <path
       android:pathData="M34,2L34,2A30,30 0,0 1,64 32L64,32A30,30 0,0 1,34 62L34,62A30,30 0,0 1,4 32L4,32A30,30 0,0 1,34 2z"
-      >
-    <aapt:attr name="android:fillColor">
-      <gradient
-          android:centerX="50"
-          android:centerY="42.5"
-          android:gradientRadius="58.829"
-          android:type="radial">
-        <item android:offset="0" android:color="#FF1B1B1B"/>
-        <item android:offset="0.535" android:color="#FF2D2D2D"/>
-        <item android:offset="1" android:color="#FF818181"/>
-      </gradient>
-    </aapt:attr>
+      android:fillColor="#FF2D2D2D"/>
   </path>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@composeApp/src/commonMain/composeResources/drawable/btn_fab_manu.xml` around
lines 2 - 19, Remove the unsupported aapt:attr inline gradient block and replace
it with a direct fill color on the VectorDrawable so it can be compiled in
commonMain; locate the <path> element that currently contains the <aapt:attr
name="android:fillColor">…<gradient>…</gradient></aapt:attr> (note the
xmlns:aapt declaration) and delete the entire aapt:attr/gradient subtree, then
add a simple android:fillColor="...hex..." attribute on that same <path> element
(or on a top-level element if appropriate) to use a single solid color instead
of the unsupported radial gradient.

<clip-path android:pathData="M0,0h24v24h-24z" />
<path
android:pathData="M3,10.394L1.654,11.557C1.236,11.918 0.604,11.872 0.243,11.454C-0.118,11.036 -0.072,10.404 0.346,10.043L10.039,1.672C11.165,0.699 12.835,0.699 13.961,1.672L23.654,10.043C24.072,10.404 24.118,11.036 23.757,11.454C23.396,11.872 22.764,11.918 22.346,11.557L21,10.394V19C21,21.209 19.209,23 17,23H7C4.791,23 3,21.209 3,19V10.394ZM10,15C10,14.448 10.448,14 11,14H13C13.552,14 14,14.448 14,15V21H10V15Z"
android:fillColor="?attr/colorControlNormal"
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, check the actual file content to confirm the current state
cat -n composeApp/src/commonMain/composeResources/drawable/ic_home.xml

Repository: festabook/kotlin-multiplatform

Length of output: 978


🏁 Script executed:

# Search for other drawable files that might have the same ?attr/ references
find composeApp/src/commonMain/composeResources/drawable -name "*.xml" -type f | head -20

Repository: festabook/kotlin-multiplatform

Length of output: 1766


🏁 Script executed:

# Check for ?attr/ references in drawable files
rg '\?attr/' --glob '*.xml' composeApp/src/commonMain/composeResources/drawable 2>/dev/null || echo "No results found"

Repository: festabook/kotlin-multiplatform

Length of output: 1932


🌐 Web query:

Compose Multiplatform vector drawable Android resource references support composeResources

💡 Result:

composeResources (the org.jetbrains.compose.resources / Res.drawable.* API in Compose Multiplatform) can load Android XML VectorDrawable files, but it does not support vectors that reference Android resources (for example @color/..., @android:color/..., ?attr/..., @dimen/..., etc.). Vectors must be “self-contained”. [1], [2]

What to do instead

Option A (recommended for shared UI): make the vector self-contained

  • Replace referenced colors with literal values (for example android:fillColor="#000000").
  • Remove theme-based attributes like android:tint="?attr/...".
  • Apply color in Compose instead (for example colorFilter = ColorFilter.tint(...)). [2]

Option B: keep Android-only VectorDrawable references

  • Put the referencing vector in androidMain/src/main/res/drawable/.
  • Load it on Android with Android’s resource APIs (e.g., androidx.compose.ui.res.painterResource(R.drawable.xxx)), and use an expect/actual (or platform-conditional) wrapper so common code doesn’t depend on it. (This is necessary because composeResources won’t resolve those Android references.) [1], [2]

Bottom line: “Android resource references inside VectorDrawable” are not supported by composeResources; only vectors without external Android references work. [1], [2]

Sources:
[1] Kotlin Multiplatform docs – resources setup (vector XML supported “without references to Android resources”)
[2] Kotlin Multiplatform docs – resources usage (XML vectors don’t support external Android resource references; example fix)


?attr/colorControlNormalcommonMain 공유 리소스에 사용 불가 — 하드코딩된 색상 또는 Compose 코드 컬러 처리로 교체 필요

Compose Multiplatform의 composeResources는 Android XML VectorDrawable을 로드할 수 있지만, Android 리소스 참조(?attr/..., @color/... 등)를 지원하지 않습니다. ?attr/colorControlNormal은 Android 테마 속성 참조로, iOS/Desktop 타겟에서 resolve되지 않아 아이콘이 투명하게 렌더링되거나 런타임 크래시가 발생할 수 있습니다.

이 파일은 androidMain이 아닌 commonMain에 위치하므로, 모든 플랫폼에서 호환되는 자체 포함된 벡터를 사용해야 합니다. 아이콘 색상을 코드에서 동적으로 제어하려면 ColorFilter.tint()를 사용하세요.

주의: 이 문제는 ic_setting.xml, ic_schedule.xml, ic_news.xmlbg_skeleton_* 파일 등 여러 drawable에서 발견됩니다. 일괄 수정이 필요합니다.

🐛 해결 방법: `?attr/` 참조를 하드코딩 색상으로 교체
-            android:fillColor="?attr/colorControlNormal"
+            android:fillColor="#FF000000"

코드에서 아이콘 색상을 동적으로 제어하는 방법:

Image(
    painter = painterResource(Res.drawable.ic_home),
    contentDescription = "",
    colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurface)
)

추가 개선: 라인 6-12의 <group> + <clip-path> 래퍼는 클립이 전체 24×24 뷰포트를 정확히 따르므로 불필요합니다. 제거 가능합니다.

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

In `@composeApp/src/commonMain/composeResources/drawable/ic_home.xml` at line 10,
Replace the Android theme attribute reference
android:fillColor="?attr/colorControlNormal" in the commonMain vector drawable
(ic_home.xml) with a platform-neutral solution: either hard-code a hex fillColor
value (e.g., android:fillColor="#FF000000") or remove the theme reference and
control tinting from Compose using ColorFilter.tint when painting the Resource
(see usage with painterResource/ColorFilter). Apply the same fix to the other
common drawables listed (ic_setting.xml, ic_schedule.xml, ic_news.xml,
bg_skeleton_*). Also consider removing the unnecessary <group> + <clip-path>
wrapper around the 24×24 viewport (lines ~6-12) since it is redundant.

Comment on lines +100 to +108
Icon(
painter = painterResource(Res.drawable.logo_title),
contentDescription = stringResource(Res.string.logo_splash),
modifier =
Modifier
.scale(distanceFraction)
.size((pullOffsetLimit / 2).dp)
.offset(y = centerOffset / 3),
)
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

contentDescription이 잘못된 문자열 리소스를 참조하고 있습니다.

logo_title 아이콘의 contentDescription으로 Res.string.logo_splash를 사용하고 있어 스크린 리더가 잘못된 설명을 읽습니다. logo_title에 맞는 문자열 리소스를 사용하거나, 해당 아이콘이 순수 장식용이라면 contentDescription = null을 지정하세요.

🐛 제안 수정안 (장식용인 경우)
             Icon(
                 painter = painterResource(Res.drawable.logo_title),
-                contentDescription = stringResource(Res.string.logo_splash),
+                contentDescription = null,
📝 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
Icon(
painter = painterResource(Res.drawable.logo_title),
contentDescription = stringResource(Res.string.logo_splash),
modifier =
Modifier
.scale(distanceFraction)
.size((pullOffsetLimit / 2).dp)
.offset(y = centerOffset / 3),
)
Icon(
painter = painterResource(Res.drawable.logo_title),
contentDescription = null,
modifier =
Modifier
.scale(distanceFraction)
.size((pullOffsetLimit / 2).dp)
.offset(y = centerOffset / 3),
)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@composeApp/src/commonMain/kotlin/com/daedan/festabook/presentation/common/component/PullToRefreshContainer.kt`
around lines 100 - 108, The Icon composable using
painterResource(Res.drawable.logo_title) currently sets contentDescription =
stringResource(Res.string.logo_splash), which is the wrong string; update the
contentDescription to the correct string resource that describes logo_title
(e.g., stringResource(Res.string.logo_title)) or, if the icon is purely
decorative, set contentDescription = null; locate the Icon block in
PullToRefreshContainer.kt and change the stringResource reference or null
accordingly to fix accessibility.

Comment on lines +57 to +62
addStyle(
style =
SpanStyle(
color = FestabookColor.gray500,
textDecoration = TextDecoration.Underline,
),
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

URL 스팬 색상이 FestabookColor.gray500으로 하드코딩되어 있어 다크 모드/테마 대응이 어렵습니다.

URL 링크 색상이 고정값으로 지정되어 있어 다크 모드나 사용자 지정 테마에서 가독성 문제가 생길 수 있습니다. 또한 컴포저블에 전달된 color 파라미터가 URL 스팬에는 반영되지 않습니다. 링크 색상을 테마 색상(예: MaterialTheme.colorScheme.primary) 또는 별도 linkColor 파라미터로 교체하는 것을 고려하세요.

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

In
`@composeApp/src/commonMain/kotlin/com/daedan/festabook/presentation/common/component/URLText.kt`
around lines 57 - 62, The URL span color is hardcoded to FestabookColor.gray500
which ignores the composable's color parameter and breaks dark/theme support;
update the URL styling in the addStyle/SpanStyle usage inside URLText (or the
function that builds spans) to use a theme-aware color (e.g.
MaterialTheme.colorScheme.primary) or add a new linkColor parameter (propagate
it through URLText and use it when creating the SpanStyle) so the link color is
not fixed to FestabookColor.gray500 and respects passed-in color or theme.

Comment on lines +76 to +89
modifier =
modifier.pointerInput(Unit) {
detectTapGestures {
layoutResult?.let { result ->
val position = result.getOffsetForPosition(it)
linkedText
.getStringAnnotations("URL", position, position)
.firstOrNull()
?.let { annotation ->
uriHandler.openUri(annotation.item)
} ?: onClick()
}
}
},
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

pointerInput(Unit) 사용으로 linkedTextonClick이 스테일 클로저에 갇힙니다.

키가 Unit이면 코루틴 블록이 최초 컴포지션 시에만 생성되고 이후 재구성 시에는 교체되지 않습니다. 따라서:

  • linkedText — 재구성마다 새로 만들어지는 단순 지역 val이므로 클로저 내부에서는 최초 값만 참조합니다. text prop이 바뀌어도 새 URL 어노테이션을 사용하지 않아 잘못된 URL이 열릴 수 있습니다.
  • onClick — 마찬가지로 최초 참조가 고정됩니다.

(참고로 layoutResultby 위임자를 통해 항상 현재 State 값을 읽으므로 문제 없습니다.)

rememberUpdatedState를 사용해 최신 값을 항상 참조하도록 수정하는 것을 권장합니다.

🐛 제안 수정
     val uriHandler = LocalUriHandler.current
     var layoutResult by remember { mutableStateOf<TextLayoutResult?>(null) }
+    val currentLinkedText by rememberUpdatedState(linkedText)
+    val currentOnClick by rememberUpdatedState(onClick)
     val linkedText =
         buildAnnotatedString {
             modifier.pointerInput(Unit) {
                 detectTapGestures {
                     layoutResult?.let { result ->
                         val position = result.getOffsetForPosition(it)
-                        linkedText
+                        currentLinkedText
                             .getStringAnnotations("URL", position, position)
                             .firstOrNull()
                             ?.let { annotation ->
                                 uriHandler.openUri(annotation.item)
-                            } ?: onClick()
+                            } ?: currentOnClick()
                     }
                 }
             },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@composeApp/src/commonMain/kotlin/com/daedan/festabook/presentation/common/component/URLText.kt`
around lines 76 - 89, Replace the stale-closure usage inside the
pointerInput/detectTapGestures block by wrapping the changing values in
rememberUpdatedState so the coroutine always sees the latest values: create
currentLinkedText = rememberUpdatedState(linkedText) and currentOnClick =
rememberUpdatedState(onClick) and then inside the pointerInput/detectTapGestures
handler use currentLinkedText.value and currentOnClick.value (together with
existing layoutResult and uriHandler) instead of directly referencing linkedText
and onClick.

Comment on lines +112 to +113
private val WEB_REGEX =
"""(https?|ftp|file)://[a-zA-Z0-9+&@#/%?=~_|!:,.;]+(?<![.,:;])""".toRegex()
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 check if the file exists and read the relevant lines
cd composeApp && find . -name "URLText.kt" -type f

Repository: festabook/kotlin-multiplatform

Length of output: 159


🏁 Script executed:

cat -n composeApp/src/commonMain/kotlin/com/daedan/festabook/presentation/common/component/URLText.kt | sed -n '105,120p'

Repository: festabook/kotlin-multiplatform

Length of output: 342


🏁 Script executed:

# Find LocalUriHandler to see how it handles URIs
find composeApp -name "*.kt" -type f | xargs grep -l "LocalUriHandler" | head -5

Repository: festabook/kotlin-multiplatform

Length of output: 168


🏁 Script executed:

# Let's see the full URLText.kt file to understand how WEB_REGEX is used
cat -n composeApp/src/commonMain/kotlin/com/daedan/festabook/presentation/common/component/URLText.kt

Repository: festabook/kotlin-multiplatform

Length of output: 5069


🏁 Script executed:

# Search for any Android-specific URI handling in the project
find composeApp -name "*.kt" -type f | xargs grep -l "file://" 2>/dev/null

Repository: festabook/kotlin-multiplatform

Length of output: 58


🌐 Web query:

Android FileUriExposedException Intent ACTION_VIEW targetSdkVersion 24

💡 Result:

FileUriExposedException happens when your app (with targetSdkVersion >= 24, Android 7.0/N) puts a file:// URI (e.g., Uri.fromFile(file)) into an Intent like ACTION_VIEW and sends it to another app. Android blocks this for N+ targets; you’re expected to share content:// URIs with temporary grants instead. (developer.android.com)

Fix (recommended): use FileProvider + grant read permission

1) Manifest provider

<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="${applicationId}.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
  <meta-data
      android:name="android.support.FILE_PROVIDER_PATHS"
      android:resource="@xml/file_paths" />
</provider>

(That’s the standard setup pattern.) (developer.android.com)

2) res/xml/file_paths.xml (share only what you must)

<paths xmlns:android="http://schemas.android.com/apk/res/android">
  <cache-path name="shared" path="." />
  <!-- or files-path / external-files-path, etc -->
</paths>

Keep the shared path narrow (don’t share / or overly broad roots). (developer.android.com)

3) Build content:// URI and launch ACTION_VIEW

val uri = FileProvider.getUriForFile(
  context,
  "${BuildConfig.APPLICATION_ID}.fileprovider",
  file
)

val i = Intent(Intent.ACTION_VIEW).apply {
  setDataAndType(uri, mimeType)   // e.g. "application/pdf"
  addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}

startActivity(i)

FileProvider.getUriForFile() is the key, and the FLAG_GRANT_READ_URI_PERMISSION grant lets the other app read it. (developer.android.com)

Notes:

  • If you wrap with Intent.createChooser(), URI-grant flags are propagated correctly. (developer.android.com)
  • Avoid “fixes” like disabling the check via StrictMode—it just hides the underlying sharing problem. (developer.android.com)

Citations:


🌐 Web query:

Kotlin/Native iOS regex negative lookbehind support

💡 Result:

Kotlin/Native on iOS does support negative lookbehind ((?<!...)) in kotlin.text.Regex, because the Native implementation uses Kotlin’s own Pattern-based regex engine (Java/java.util.regex-style), not JavaScriptCore/Safari’s RegExp engine. [1]

Practical implication: (?<!foo)bar works, but the lookbehind pattern must be fixed-length / deterministically bounded (Java-style restriction), so unbounded quantifiers like .*, +, * inside lookbehind are not allowed. [2][3]

Sources:
[1] Kotlin stdlib (Native/Wasm) Regex implementation uses Pattern (kotlin.text.regex.*)
[2] Pattern syntax includes negative lookbehind (?<!X)
[3] Java-style lookbehind constraint: fixed-length / bounded in lookbehind


정규식의 두 가지 결함 — 하이픈 누락 및 file:// 스킴 포함.

① 문자 클래스에 하이픈(-) 누락
[a-zA-Z0-9+&@#/%?=~_|!:,.;]-가 없어, https://my-domain.com/some-path처럼 하이픈이 포함된 (즉 대부분의) URL이 첫 번째 하이픈에서 매칭이 끊깁니다.

file:// 스킴 포함으로 Android 크래시 위험
targetSdkVersion이 24 이상이면 Android 7.0+ 기기에서 file: URI를 Intent에 사용할 수 없습니다. LocalUriHandler.openUri()가 Android에서 Intent.ACTION_VIEW로 위임되므로, file://가 Intent에 첨부되면 FileUriExposedException이 발생하여 앱이 즉시 크래시될 수 있습니다.

🐛 제안 수정
 private val WEB_REGEX =
-    """(https?|ftp|file)://[a-zA-Z0-9+&@#/%?=~_|!:,.;]+(?<![.,:;])""".toRegex()
+    """(https?|ftp)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]+(?<![.,:;])""".toRegex()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@composeApp/src/commonMain/kotlin/com/daedan/festabook/presentation/common/component/URLText.kt`
around lines 112 - 113, The WEB_REGEX in URLText.kt is wrong: it omits '-' in
the character class and wrongly includes the 'file' scheme; update WEB_REGEX to
only allow safe schemes (remove "file" from "(https?|ftp|file)") and add '-'
into the allowed character class (either escape it or place it at the
end/beginning) so domains/paths with hyphens match correctly and file:// URIs
are not matched to avoid Android FileUriExposedException; locate the WEB_REGEX
constant and adjust the scheme group and character class accordingly.

Comment on lines +44 to 54
polygonHoleBoundary =
listOf(
TimeTagResponse(name = "오전", timeTagId = 1L),
FestivalGeographyResponse.PolygonHoleBoundary(
latitude = 37.5660,
longitude = 126.9775,
),
FestivalGeographyResponse.PolygonHoleBoundary(
latitude = 37.5670,
longitude = 126.9785,
),
),
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

polygonHoleBoundary가 최소 꼭짓점 수를 충족하지 않음

polygonHoleBoundary에 좌표가 2개만 정의되어 있습니다. 폴리곤(hole 포함)을 구성하려면 최소 3개의 꼭짓점이 필요하며, 2개는 기하학적으로 선분(degenerate polygon)에 해당합니다. 실제 지도 렌더링이나 경계 검증 로직을 테스트하는 경우 예상치 못한 동작이 발생할 수 있습니다.

🛠️ 제안: 꼭짓점 1개 추가
         polygonHoleBoundary =
             listOf(
                 FestivalGeographyResponse.PolygonHoleBoundary(
                     latitude = 37.5660,
                     longitude = 126.9775,
                 ),
                 FestivalGeographyResponse.PolygonHoleBoundary(
                     latitude = 37.5670,
                     longitude = 126.9785,
                 ),
+                FestivalGeographyResponse.PolygonHoleBoundary(
+                    latitude = 37.5665,
+                    longitude = 126.9790,
+                ),
             ),
📝 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
polygonHoleBoundary =
listOf(
TimeTagResponse(name = "오전", timeTagId = 1L),
FestivalGeographyResponse.PolygonHoleBoundary(
latitude = 37.5660,
longitude = 126.9775,
),
FestivalGeographyResponse.PolygonHoleBoundary(
latitude = 37.5670,
longitude = 126.9785,
),
),
polygonHoleBoundary =
listOf(
FestivalGeographyResponse.PolygonHoleBoundary(
latitude = 37.5660,
longitude = 126.9775,
),
FestivalGeographyResponse.PolygonHoleBoundary(
latitude = 37.5670,
longitude = 126.9785,
),
FestivalGeographyResponse.PolygonHoleBoundary(
latitude = 37.5665,
longitude = 126.9790,
),
),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@composeApp/src/commonTest/kotlin/com/daedan/festabook/data/repository/fixture/PlaceListRepositoryTestFixture.kt`
around lines 44 - 54, The test fixture PlaceListRepositoryTestFixture defines
polygonHoleBoundary with only two FestivalGeographyResponse.PolygonHoleBoundary
points which is invalid for a polygon; update polygonHoleBoundary in
PlaceListRepositoryTestFixture to include at least one more
FestivalGeographyResponse.PolygonHoleBoundary (a third coordinate with
latitude/longitude) so the hole is a valid polygon (minimum 3 vertices) for
geometry/boundary validation and rendering tests.

Copy link
Contributor

@parkjiminnnn parkjiminnnn left a comment

Choose a reason for hiding this comment

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

파일이 많아서 다음 작업 진행할 때 문제가 생긴다면 하나씩 해결해가면 좋을거 같아요!
고생하셨습니다 밀러~

@oungsi2000 oungsi2000 merged commit bbf4db9 into develop Mar 2, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feat 새로운 기능이나 기능 개선 추가

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants