md2link

Color By Number — Báo Cáo Audit Toàn Diện (Executive Summary)

DraftMay 7, 2026

Color By Number — Báo Cáo Audit Toàn Diện (Executive Summary)

Ngày: 2026-05-07 | Branch: main | Phiên bản: versionCode 1, versionName 1.0 Quy mô: 1 module Compose Android, 135 file .kt, 30 file .xml, ~13K LOC Stack: Kotlin + Compose + Koin + Room + Retrofit + Coil + WorkManager + Apero FO SDK + Firebase

Báo cáo này tổng hợp 5 audit chuyên sâu (5 reviewer chạy song song). Đọc chi tiết tại:

  • audit-group-a-startup-architecture-260507-1130.md
  • audit-group-b-compose-ui-ux-260507-1130.md
  • audit-group-c-coroutines-crash-memory-260507-1130.md
  • audit-group-d-data-network-db-260507-1130.md
  • audit-group-e-security-ads-playstore-260507-1130.md

TL;DR

App có kiến trúc Clean + MVI bài bản, design system token-based đầy đủ, offline-first pattern implement đúng spec. Tuy nhiên KHÔNG đủ điều kiện ship Play Store vì:

  1. Release keystore + password committed vào git — mất toàn quyền sở hữu publisher.
  2. AdMob test ad units ở appProd flavor — ship = $0 doanh thu + ban risk.
  3. API key + Artifactory + Facebook ClientToken plaintext trong source/git history.
  4. Main thread blocking đa điểmrunBlockingattachBaseContext, ColoringView parse SVG + region + distance transform main thread → ANR thực sự với painting nhiều region.
  5. Dark mode hoàn toàn không hoạt động dù tokens đã định nghĩa đầy đủ.
  6. Schema migration fallbackToDestructiveMigration(true) — bump v1→v2 = wipe toàn bộ progress + favorites của user.

Tổng số issue: ~115 (~24 Critical, ~37 High, ~46 Medium, ~31 Low).


Severity Distribution

Group Critical High Medium Low Total
A. Startup + DI + Architecture 3 6 6 3 18
B. Compose UI + UX + a11y 6 12 13 15 46
C. Coroutines + Crash + Memory 5 6 6 3 20
D. Data + Network + DB + Offline 4 7 9 5 25
E. Security + Ads + PlayStore 5 6 ~12 ~5 ~28
Total 23 37 ~46 ~31 ~137

CRITICAL ISSUES — Chặn release (P0)

🔐 Security & Compliance (release-blocking)

# Issue File Group Effort
S1 Release keystore .jks + plaintext password commit vào git history; .gitignore không exclude app/build.gradle.kts:14-22, app/keystore/upload-keystore.jks E 4-8h
S2 API_KEY (jsonsilo) + Apero Artifactory creds (apero-terasofts:Apero@1234) hardcoded trong source + git core/common/AppConstants.kt:12, settings.gradle.kts:22-24, commit 450ac70 A, E 3h
S3 Facebook ClientToken plaintext trong AndroidManifest AndroidManifest.xml:46 E 1h

💰 Revenue & Play Store policy (release-blocking)

# Issue File Group Effort
S4 AdMob TEST ad unit IDs (ca-app-pub-3940256099942544/...) ở cả appDev appProd flavor + fo_config_ads.xml 28 slot app/build.gradle.kts:55-68, fo_config_ads.xml A, E 2h + Apero coord
S5 AdMob APPLICATION_ID cũng dùng test ID ở 2 flavor build.gradle.kts:44, 58 E 30min
S6 UMP/GDPR consent flow VẮNG MẶT — chỉ work nếu FO SDK handle internally (cần verify) App init flow E 1-3 ngày
S7 Crashlytics + Analytics auto-init trước khi user consent ColorByNumberApplication.kt, AndroidManifest.xml E 4h

🐛 Stability & Data integrity (release-blocking)

# Issue File Group Effort
D1 fallbackToDestructiveMigration(true) ở production — bump schema = wipe coloring_progress + favorites core/di/LazyModule.kt D 1 ngày
D2 TemplateSyncWorker xóa toàn bộ catalog khi API trả empty [] (200 OK + empty body) data/sync/TemplateSyncWorker.kt, TemplateRepositoryImpl.kt C, D 2h
D3 saveThumbnail + extractZip không atomic — process kill giữa chừng để lại file partial mà existsAndNonEmpty() không phát hiện → asset stuck corrupt data/coloring/ColoringDownloader.kt, ColoringCache.kt D 4h
D4 ColoringView main-thread parse SVG Path + Region.setPath() + computePoleOfInaccessibility (Bitmap ALPHA_8 96×96 + distance transform 2-pass) → 1.5–2.5s blocking → ANR thực sự coloring/engine/ColoringView.kt, ColoringRegion.kt C 1-2 ngày
D5 revealBitmap (~16MB) + activePatternBitmap không cleanup khi setAsset gọi lại; shader giữ ref qua Paint → memory bloat / OOM coloring/engine/ColoringView.kt C 4h
D6 runBlocking { dataStore.first() } trong MainActivity.attachBaseContext — block main thread mỗi cold start + config change (~50-200ms) MainActivity.kt:22, PreferencesManager.kt:32 A, C, E 4h
D7 Zip-slip prefix check thiếu trailing separator → có thể write file out-of-cache-dir ColoringDownloader.kt extractZip() C 30min

🎨 UX-blocking

# Issue File Group Effort
U1 Dark mode hoàn toàn không hoạt động — AppTheme hardcode LightAppColors dù có đủ DarkAppColors 49 fields designsystem/theme/AppTheme.kt:36-43 B 1-2 ngày
U2 Hardcoded Color(0xFF...) ngoài DS ở 6+ điểm (Palette, Calendar, RetryButton, ColoringScreen) → dark mode regression nhiều file B 1 ngày
U3 HomeBannerPager render painter tĩnh ic_setting_share thay vì load banner.imageUrl qua AsyncImage → banner backend vô dụng home/component/HomeBannerPager.kt:97-103 B 30min
U4 Localization vi thiếu nhiều strings (Daily, Home, MyFeed, Coloring fallback English) + onboarding en/vi nội dung lệch hoàn toàn res/values-vi/strings.xml B 4h
U5 MainBottomBar thiếu Role.Tab semantic, contentDescription đọc 2 lần, active state chỉ dùng alpha 0.6f navigation/MainBottomBar.kt B 2h
U6 ColoringView AndroidView state race: var view by mutableStateOf + LaunchedEffect đọc null view → completed update mất khi recompose trước factory chạy coloring/ColoringScreen.kt:57-86 B, C 4h

🏗️ Architecture/Init bugs

# Issue File Group Effort
A1 HomeViewModel đăng ký double trong cả EagerModule:56 + LazyModule:129 → race core/di/EagerModule.kt, LazyModule.kt A, C 5min
A2 AperoAd.init + FirstOpenSDK.init synchronous trong Application.onCreate — block trước Splash render ColorByNumberApplication.kt A 4h
A3 BaseViewModel._effect dùng Channel.BUFFERED + repeatOnLifecycle + collectLatest → ghost navigation / lost effects khi background presentation/base/BaseViewModel.kt C 2h

HIGH ISSUES (P1) — Cần fix trước milestone tiếp theo

Performance / Threading

  • snapshotBitmap() 1MB (ARGB_8888 512×512) main thread mỗi ON_STOP → 30-100ms UI block + GC pressure (B-H2, C-07)
  • Compose BodySection.drawBehind allocate Path mỗi frame (B-H8)
  • MyFeedUiState.visibleItems computed property không derivedStateOf → O(n log n) mỗi recomposition (B-H5)
  • HomeBannerPager LaunchedEffect không dùng collectLatest → multiple delay pending khi swipe nhanh (B-H7)
  • combine flow trong ViewModel không flowOn / distinctUntilChanged (C)

Network / Data

  • OkHttp thiếu disk cache, không SSL pinning (jsonsilo HTTPS = MITM-able), Level.BODY log có thể leak signed URL token (D-#1)
  • TemplateSyncWorker thiếu Constraints(NetworkType.CONNECTED), BackoffPolicy.EXPONENTIAL, retry logic — fail thẳng cho mọi exception (D-#3)
  • IntSetConverter parse string fragile (D-#5)
  • Progress save mất khi VM destroy giữa debounce 500ms (C-10)

Build / Release

  • ProGuard rules trống + isMinifyEnabled = true → release sẽ crash mediation SDK (E)
  • isShrinkResources không bật → APK size phình (E)
  • backup_rules trống cho phép auto cloud backup → leak user data (E)

A11y / UX

  • AppTemplateCard thiếu loading state, contentDescription = null cho painting + heart icon (B-H1)
  • ColoringPalette Unicode render rủi ro tofu, fontSize 11sp/14sp không respect font scale, touch 40dp < 48dp (B-H3)
  • Touch target nhiều chỗ < 48dp (B-M7, B-H3)
  • HomeContent.bannerNestedScrollConnection thrash AnimatedVisibility khi fling (B-H4)
  • MyFeedRow chunked(2) → row cuối lệch alignment (B-H6)
  • MyFeedTabBar overflow risk khi text vi dài (B-H12)
  • ColorTemplateBottomSheet không có hide animation khi clear state (B-H9)
  • Calendar.getDisplayName(Locale.ENGLISH) cứng → user vi vẫn thấy "January" (B-H11)
  • ColoringScreen background Color.White + strings hardcode English (B-H10)
  • ALL_CATEGORY semantics bug: prepend pseudo-row vào empty list → UI lừa user "có category" (D-#8)

App init

  • Eager DataStore init kéo theo runBlocking (A-H6)
  • applyLocaleAndPop dùng deprecated updateConfiguration + không re-trigger Compose stringResource (A-H9)
  • FO SDK splash không có local timeout (A-M13)

MEDIUM / LOW (P2-P3)

Tham khảo từng group report. Tóm lược:

  • Recomposition: HomePainting/MyFeedItem/HomeBanner thiếu @Stable/@Immutable; AppRaisedSurface .let chain phá modifier stability.
  • Design system gaps: AppText không expose lineHeight/fontWeight; themes.xml parent Theme.MaterialComponents.DayNight.NoActionBar dư thừa cho 100% Compose.
  • A11y: Nhiều component thiếu Role.Button/RadioButton/Tab, progressSemantics, heading semantics.
  • Code smell: ColoringScreen.ErrorState Spacer(Modifier.width(0.dp)) dead code; duplicate Modifier .width(102.dp).size(102.dp, 6.dp); colors.xml còn purple_200/500/700 template defaults.
  • Future-proof: targetSdk 36 yêu cầu Predictive Back, edge-to-edge mặc định, foreground service type rõ ràng — chưa thấy implement đầy đủ.

Cross-Cutting Themes

🔁 Anti-pattern lặp lại

  1. runBlocking trên main thread: 3 chỗ (MainActivity.attachBaseContext, PreferencesManager, sync paths). Pattern xử lý locale bootstrap cần redesign — dùng AtomicReference cache hoặc legacy SharedPreferences boot file thay DataStore.
  2. Hardcoded magic values: API_KEY, keystore password, Artifactory creds, Facebook ClientToken, AdMob test IDs, Color(0xFF...) ngoài DS, Locale.ENGLISH cứng — toàn bộ phải externalize.
  3. Main-thread heavy work: ColoringView SVG parse + Region + distance transform + snapshot bitmap; thiếu withContext(Dispatchers.Default) ở use case layer.
  4. State race trong Compose ↔ AndroidView interop: var view by remember { mutableStateOf } + side-effect đọc state — không idiomatic. Dùng AndroidView(factory, update) chuẩn.
  5. Lack of consent gating: Crashlytics, Analytics, AdMob auto-init trước user consent → GDPR/CCPA risk.

✅ Điểm mạnh đáng ghi nhận

  • Clean architecture (data/domain/presentation) + MVI Contract sạch.
  • Offline-first pattern implement đúng theo doc: separated observe* / triggerSync / observeSyncStatus, replaceAll 1 transaction, formatVersion filter cho schema drift, splash trigger sync, layered DTO→Entity→Domain mappers không leak Room types.
  • Design system token-based (49 color fields, CompositionLocal pattern chuẩn).
  • collectAsStateWithLifecycle consistent (không thấy collectAsState).
  • key parameter dùng đúng trong tất cả LazyColumn/LazyGrid.
  • CancellationException re-throw chuẩn ở đa số try/catch.
  • Pole-of-Inaccessibility 2-pass distance transform — kỹ thuật tốt (chỉ sai chỗ chạy main thread).
  • Type-safe Compose Navigation qua kotlinx.serialization.

Top 10 Priority Fixes (Action Plan)

Priority Issue Effort Owner Blocker for
1 S1: Rotate keystore + git history cleanup + Play Console "Reset upload key" 4-8h DevOps Release
2 S4 + S5: Swap test → real AdMob unit IDs cho appProd + APPLICATION_ID + CI gate chặn keyword 3940256099942544 2h Dev + Apero Revenue
3 S2: Rotate API_KEY + Apero Artifactory creds qua secret manager + local.properties 3h DevOps Security
4 D1: Implement Room Migration objects + exportSchema = true + xóa fallbackToDestructiveMigration 1 ngày Backend dev Data integrity
5 D4: Move ColoringView SVG parse + Region precompute + label geometry → Dispatchers.Default trong PrepareColoringUseCase 1-2 ngày Senior dev ANR / UX
6 U1 + U2: Wire dark mode (if (darkTheme) DarkAppColors else LightAppColors) + promote tất cả hardcoded Color() vào AppColors 1-2 ngày UI dev UX
7 D2 + D3: Guard TemplateSyncWorker chống empty + atomic file write (tmp+rename) + retry/backoff 6h Backend dev Data integrity
8 D5: Bitmap lifecycle cleanup + recycle thumbnail sau save 4h Senior dev Memory / OOM
9 D6 + A2: Cache locale qua AtomicReference; defer FO SDK / AperoAd init khỏi main thread 6h Senior dev Cold start
10 S6: Verify FO SDK UMP handling; nếu thiếu, tích hợp Google UMP SDK trước Crashlytics/Analytics init 1-3 ngày Senior dev GDPR / Compliance

Estimated total for P0 fixes: 8-15 ngày dev (1.5-3 tuần với 1 senior dev).


Testing & Verification Recommendations

App hiện không có test (app/src/test trống). Khuyến nghị:

  1. DAO tests với in-memory Room cho FavoriteDao, ColoringProgressDao, PaintingCatalogDao.
  2. Mapper tests cho IntSetConverter, PaintingEntityMapper, ColoringProgressEntityMapper, ColoringAssetMapper.
  3. Worker tests với TestListenableWorkerBuilder cho TemplateSyncWorker + edge case "200 OK + empty body".
  4. ViewModel tests với MainDispatcherRule + kotlinx-coroutines-test cho HomeViewModel, ColoringViewModel, MyFeedViewModel.
  5. Compose UI tests cho Bottom navigation, Coloring tap flow, Banner pager.
  6. Macrobenchmark cho cold start + frame time của ColoringView render.
  7. TalkBack manual test cho a11y sweep.
  8. Smoke test assembleAppProdRelease trước mọi merge để bắt ProGuard/R8 issue sớm.

Unresolved Questions (cần PM/Tech Lead trả lời)

  1. FO SDK & UMP: FO SDK có handle UMP/GDPR consent internally không? Nếu không, kế hoạch tích hợp Google UMP SDK ra sao?
  2. Apero ad units production: bao giờ nhận từ Apero account? Có thể CI gate hard-fail nếu thấy test ID trong appProd?
  3. Apero apiKey: đã bind đúng applicationId production (com.colorbynumber.coloringgames.numberpaint) chưa?
  4. Target user: có bao gồm trẻ em < 13 (COPPA / Families policy) không? Nếu có, ad mediation cần cấu hình child-directed flag.
  5. Play App Signing: đang dùng hay không? Nếu có, key trong repo chỉ là upload key — vẫn cần rotate nhưng impact thấp hơn.
  6. Painting max size: thực tế max bao nhiêu region trong painting production? Quyết định severity của D4 (ColoringView ANR).
  7. Module split timing: khi nào bắt đầu tách module (:core:designsystem, :feature:home, …)? Hiện chưa cấp bách nhưng sẽ thành bottleneck khi team scale.
  8. Schema migration strategy: dùng Migration objects truyền thống hay formatVersion filter ở mapper layer (hiện đang có sẵn cho catalog)?
  9. Asset cache invalidation: TTL hay checksum? Hiện chưa có policy rõ.
  10. HomeBanner backend: API đã trả URL hợp lệ chưa? Cần fallback graceful nếu chưa.
  11. Onboarding nội dung: en "Tap Numbers, Bring Art to Life" vs vi "Chọn bức tranh bạn thích" — phiên bản nào đúng intent? PM xác nhận.
  12. Tablet/Foldable: có yêu cầu adaptive UI cho large screens không?
  13. Crashlytics PII policy: được phép log gì, không log gì?
  14. Dark mode design tokens: có Figma reference cho dark mode không? DarkAppColors hiện đang đoán dựa Material defaults.

Phase 0 — Pre-release blockers (1-2 tuần)

  • Top 10 priorities ở trên.
  • Verify FO SDK UMP behavior.
  • Smoke test release build.

Phase 1 — Stability & Compliance (1-2 tuần)

  • Tất cả High issues group C (coroutine/memory/lifecycle).
  • High issues group D (network resilience, atomic writes).
  • High issues group E (ProGuard rules, isShrinkResources, backup rules).
  • Test coverage tối thiểu (DAO, Mapper, Worker).

Phase 2 — UX polish (1-2 tuần)

  • Dark mode hoàn thiện + Preview NIGHT_YES coverage.
  • Localization vi đầy đủ + sync onboarding.
  • A11y sweep (Roles, touch targets, contentDescription).
  • Compose recomposition optimization (@Immutable, derivedStateOf).
  • Tablet/Foldable adaptive UI (nếu yêu cầu).

Phase 3 — Architecture maturity (sau 1.0)

  • Module split khi đạt threshold (15+ feature screens hoặc team >3 dev).
  • Macrobenchmark + Baseline Profile.
  • Spatial index cho ColoringView nếu painting > 500 region.

Tài liệu tham khảo

  • first-open-sdk-integration-guide.md (project root) — FO SDK init flow.
  • offline-first-reactive-cache-pattern.md (project root) — design intent của data layer.
  • PROJECT_BASE_PROMPT.md (project root) — project specs.
  • 5 group reports trong plans/reports/audit-group-{a..e}-*-260507-1130.md.