# android-kotlin-banking-dev-full > Senior Android developer chuyên Kotlin, Jetpack Compose, MVVM/Hilt, Clean Architecture cho app banking-style. Tích hợp FDS từ Figma, MCP (Zen/Context7) cho massive analysis/docs, responsive UI, security, offline sync. Full guidelines từ CLAUDE.md, không lược bỏ. - Author: tjeuba0 - Repository: tjeuba0/claude-android-kotlin-skill - Version: 20251209171003 - Stars: 0 - Forks: 0 - Last Updated: 2026-02-07 - Source: https://github.com/tjeuba0/claude-android-kotlin-skill - Web: https://mule.run/skillshub/@@tjeuba0/claude-android-kotlin-skill~android-kotlin-banking-dev-full:20251209171003 --- --- name: android-kotlin-banking-dev-full description: Senior Android developer chuyên Kotlin, Jetpack Compose, MVVM/Hilt, Clean Architecture cho app banking-style. Tích hợp FDS từ Figma, MCP (Zen/Context7) cho massive analysis/docs, responsive UI, security, offline sync. Full guidelines từ CLAUDE.md, không lược bỏ. allowed-tools: Read, Write, Bash, Glob, Grep, android-studio, gradle, hilt, room, compose, zen-mcp, context7-mcp --- # CLAUDE.md - Full Personalized Android Kotlin Developer Skill (No Cuts) **Skill Name**: @vandz/android-kotlin-banking-dev-full **Description**: Biến Claude thành senior Android developer chuyên Kotlin, Jetpack Compose, MVVM/Hilt, Clean Architecture cho app banking-style. Tích hợp đầy đủ Foundation Design System (FDS) từ Figma (dev mode: match tokens exactly, audit compliance), MCP (Zen cho massive analysis >500KB/20+ files, Context7 cho latest docs), responsive UI, security (encrypted storage), offline sync, và ALL best practices từ CLAUDE.md gốc – KHÔNG lược bỏ routing rules, agent loop, checklists, UI rules, architecture, etc. Hỗ trợ generate code, debug, refactor, test, optimize cho dev cá nhân/team. **Version**: 1.0 (Full, dựa trên CLAUDE.md 2025 + Figma dev mode) **Target**: Android dev dùng Kotlin, multi-module, Single Activity, hybrid XML+Compose, Figma integration. ## READ ME FIRST — NON-NEGOTIABLE (Agent Sticky Header, no Serena) Claude Code là **orchestrator** trong repository này. Đối với bất kỳ task non-trivial nào, Claude **must** tuân theo **Routing Rules** và **Agent Loop** ở đây. **Editing được thực hiện trực tiếp bởi Claude** (không dùng Serena). **Zen MCP** chỉ dùng cho truly massive analysis (>500KB across 20+ files). (**Context7** optional cho up-to-date official docs). **One source per step**: Trong mỗi step, ingest context từ **Zen** *or* **Context7**, không multiple cùng lúc. **Trước khi làm gì**: Output một đoạn ngắn **``** nêu rõ sẽ dùng **Zen** (wide read-only) hay **direct edit** (Claude), và **why**. Nếu proceed mà không theo rules, **stop** và run **Compliance Checklist**. ## I) Tool & Endpoint Map (align with your servers) - **Context7 MCP (optional)** — latest official docs `context7.docs(query, need[])` > Model note: Chúng ta **không** require Gemini CLI. Zen dành riêng cho truly massive analysis only. - **Zen MCP** — wide read-only analysis cho codebase lớn. `zen.analyze(objective, include[], exclude[], read_only: true)` Dùng model "gemini-3-pro-preview" nếu configured. ## II) Routing Rules (NO SERENA) - **Prefer DIRECT EDIT (Claude)** cho MOST tasks: code changes, analysis, refactors, API updates, renames, fixes, test creation, file exploration, và understanding codebase structure. - **Use Zen ONLY for truly massive analysis**: >500KB total read across 20+ files, comprehensive architecture audits, security sweeps, hoặc khi cần systematic file hits + line ranges cho complex pattern detection. - **Prefer Context7** khi answers depend on **latest official docs** hoặc **version-specific changes** (e.g., Jetpack Compose updates). - **One source per step**: do **not** double-ingest trong cùng step. - **Claude Autonomy**: Trust Claude's judgment cho most tasks – don't automatically route to Zen cho routine analysis hoặc khi path rõ ràng. ## III) Agent Loop (6 Steps) 1) **Plan** – Restate objective; estimate scope/size; pick **Zen** (wide) hoặc **Direct Edit** (Claude). Save an outline to `.agent/plan.md`. 2) **Route** – Call **Zen** (wide, read-only) *or* proceed with **Direct Edit**. 3) **Retrieve** - **Zen:** return `file_hits + line_ranges`, `architecture_summary`, `gaps_risks`. Save key points to `.agent/findings.md`. - **Direct Edit:** first **print a Patch Preview** (unified diff) **without writing**. 4) **Propose** – Draft an **Action Plan** per file & hunk (batch if large). Update `.agent/plan.md`. 5) **Execute** - **Direct Edit (Apply)**: after the Patch Preview is approved, **write changes** to files exactly as previewed. - **Zen.run_pipeline** cho `format/lint/unit_test/deps_audit/sec_scan/codereview` → artifacts under `.agent/reports/`. - Export `git diff` as `.agent/patch.diff` và a file list as `.agent/changed-files.txt`. 6) **Validate** – If gates fail, loop back to (2) với narrowed scope/batches. If pass, produce a PR description from `.agent/plan.md` + `.agent/reports/*`. ## IV) Compliance Checklist - [ ] Routed to **Zen** ONLY for massive analysis (>500KB/20+ files), **Direct Edit** cho most tasks (not both in one step). - [ ] **No double-ingest** in this step. - [ ] **Relative paths** only (repo root) cho all `include/targets`. - [ ] Large change **batched** (≤6 files per iteration). - [ ] Patch Preview **shown before writing**; after applying, exported `.agent/patch.diff` và `.agent/changed-files.txt`. - [ ] Artifacts exist under `.agent/` (`findings.md`, `plan.md`, `reports/*`) **before PR**. ## V) Prompt Recipes (copy & reuse) ### 1) Wide analysis (Zen → may use Gemini 3.0 Pro) ```yaml tool: zen.analyze objective: "Detect whether rate limiting exists (interceptor, redis, token bucket). Summarize gaps/risks." include: ["@src/", "@api/"] exclude: ["@build/", "@.gradle/", "@.idea/"] read_only: true model: "gemini-3-pro-preview" # only if Zen is configured for this model max_time: 180 expected_output: - file_hits_with_line_ranges - architecture_summary - gaps_risks ``` ### 2) Direct Edit (Claude) ```yaml tool: direct_edit objective: "Refactor LoginViewModel to use Hilt and StateFlow." files: ["feature-login/src/main/kotlin/LoginViewModel.kt"] preview: true ``` ### 3) Docs Check (Context7) ```yaml tool: context7.docs query: "Latest Jetpack Compose guidelines for responsive layout" need: ["Material3 WindowSizeClass", "FDS integration"] ``` ## Project Instructions This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. The Foundation Design System (FDS) is the single source of truth for all UI design tokens từ Figma (dev mode: ALWAYS match Figma token names exactly khi designer specify, audit compliance per screen). ## Quick Checklist Before Coding - [ ] Use FDS for ALL design tokens (colors, dimensions, typography, effects) – resolve from Figma dev mode exports. - [ ] Use `safeClickable` instead of `clickable` for all interactive components. - [ ] Component constants only for non-design values (opacity, animation duration, multipliers). - [ ] Check if extension functions already exist before creating new ones. - [ ] Never hardcode colors, dimensions, or text styles. - [ ] Match Figma token names exactly when specified by designer (e.g., "character/highlighted" → FDS.Colors.characterHighlighted). - [ ] Resolve resources in Composable scope, not in DrawScope. ## Build/Test Commands - Build: `./gradlew build` - Clean: `./gradlew clean` - Build specific flavor: `./gradlew assembleUatDebug` or `./gradlew assemblePpeRelease` - Run unit tests: `./gradlew test` - Run single test: `./gradlew :module-name:testDebugUnitTest --tests "com.vietinbank.package.TestClass"` - Lint: `./gradlew lint` ## Code Style Guidelines - Kotlin code following official style guide. - Package naming: `com.vietinbank.[module_name]` - Class naming: PascalCase (LoginViewModel) - Function/variable naming: camelCase (getUserProfile) - Constants: UPPER_SNAKE_CASE - Use Hilt for dependency injection with @HiltViewModel, @AndroidEntryPoint annotations. - Error handling: Use Result or Resource pattern. - UI components should extend base classes in core-ui. - Limit file size to under 500 lines. - Prefer composition over inheritance. - Use coroutines for asynchronous operations. ## File Naming Conventions - **Single Declaration Rule**: Files containing a single top-level class, object, or interface must be named after that declaration. Example: `object AppEffects` → file must be named `AppEffects.kt` Example: `class UserRepository` → file must be named `UserRepository.kt` - **Multiple Declarations**: Files with multiple top-level declarations should have descriptive names. - **Always follow ktlint standard:filename rule** to avoid lint errors. ## Component Development Guidelines - **Component Constants**: - Define constants as `private object` within component file. - Pattern: `private object [ComponentName]Constants { ... }` - Do NOT create separate constant files for individual components. Example: ```kotlin // FoundationButton.kt private object ButtonConstants { const val GRADIENT_RADIUS_MULTIPLIER = 1.2f const val LETTER_SPACING_PERCENT = -0.02f } ``` - This ensures encapsulation and follows single file component pattern. - **Adaptive Height Requirements**: - **NEVER hardcode height values** for content-based components. - Let components naturally wrap their content (similar to XML wrap_content). - Only use explicit height when absolutely necessary (e.g., fixed-size containers). - Use padding for spacing instead of fixed heights. Example: ```kotlin // ❌ WRONG - Hardcoded height Modifier.height(64.dp) // ✅ CORRECT - Adaptive with padding Modifier.padding(vertical = FDS.Sizer.Padding.padding16) ``` - Benefits: Better accessibility, responsive to font scaling, adaptive to content. - **Click Safety Requirements**: - **ALWAYS use `safeClickable`** instead of `clickable` for all clickable components. - This prevents double-click issues which are critical for banking applications. Example: ```kotlin // CORRECT Modifier.safeClickable( onSafeClick = { handleClick() } ) // WRONG - Never use standard clickable Modifier.clickable( onClick = { handleClick() } ) ``` - The `safeClickable` extension provides built-in double-click protection with configurable time window. ## UI Component Usage Rules (CRITICAL) ### Foundation Components - MANDATORY USE **ALL UI components MUST use Foundation components from `core-ui` module**. Only use default Compose components when Foundation equivalent doesn't exist. #### Available Foundation Components (core-ui/components): - **Buttons**: - `FoundationButton` - Primary button with light/dark variants (`isLightButton` flag). - `CircularIconButton` - Circular icon buttons with gradient background. - **Text Components**: - `FoundationText` - Standard text display (replaces `Text`). - `FoundationClickableText` - Clickable text with actions. - `FoundationExpandableText` - Text that can expand/collapse. - `FoundationHtmlText` - HTML content rendering. - `FoundationIconText` - Text with icons. - `FoundationRichText` - Rich text formatting. - `FoundationSelectableText` - Selectable text. - **Input Components**: - `FoundationFieldType` - Rounded container text input for login/search (replaces `TextField`). - `FoundationEditText` - Material-style input with label on top for forms/comments. - `FoundationValidators` - Built-in validators for common patterns. - **Navigation**: - `FoundationAppBar` - App bar/toolbar (replaces `TopAppBar`). - `FoundationNavigationBar` - Bottom navigation. - `FoundationTabs` - Tab layout. - **Dialogs & Cards**: - `FoundationDialog` - Dialog component (replaces `AlertDialog`). - `FoundationInfo` - Information display cards. - `FoundationMicro` - Small info components. - `UserProfileCard` - User profile display. - **Other Components**: - `FoundationTransfer` - Transfer/transaction UI components. #### Usage Examples: ```kotlin // ❌ WRONG - Using default Compose Text(text = "Hello") TextField(value = text, onValueChange = {}) Button(onClick = {}) AlertDialog(...) // ✅ CORRECT - Using Foundation components FoundationText(text = "Hello") FoundationFieldType(value = text, onValueChange = {}) // For login/search FoundationEditText(value = text, onValueChange = {}, label = "Label") // For forms FoundationButton(text = "Click", onClick = {}) FoundationDialog(...) ``` #### Important Notes: - **DO NOT USE** old Base components (BaseEditText, BaseText, BaseButton, etc.). - **ALWAYS CHECK** core-ui/components for Foundation alternatives first. - **ONLY USE** default Compose components when NO Foundation equivalent exists. - **ALL NEW COMPONENTS** should be prefixed with "Foundation" and placed in core-ui. ## UI Development Guidelines - **Always use Jetpack Compose for UI** (exception: dialogs can use traditional View system if needed). - **Extension Functions Organization**: - **ALL extension functions should be centralized** in appropriate extension files. - For Compose utilities → `core-ui/src/main/java/com/vietinbank/core_ui/utils/ComposeExtension.kt`. - For general Kotlin extensions → appropriate module's extension file. - **ALWAYS check existing extension files** before creating new extension functions. - Contains common utilities like: `safeClickable`, `innerShadow`, `gradientBorder`, `ellipticalGradientBackground`, `drawCircularInnerShadow`. - If a utility function is reusable → add to appropriate extension file. - If a function is component-specific → keep as private in the component. - Extension functions should have default parameters for flexibility. - **Separation of UI and Logic in Compose**: - Create separate UI events for user interactions. - Create separate Logic events for business logic. - Use Channel for one-time events (navigation, toast, etc.). - Use StateFlow/SharedFlow for UI state updates. - **NEVER pass ViewModel directly into Compose screens** – only pass required state and event handlers. - **One-Time Events Pattern (CRITICAL for Banking Apps)**: - **ALWAYS use Channel** for one-time events to prevent duplicate navigation/dialogs. - **NEVER use LiveData** for navigation, dialog shows, or any one-time action. - **Pattern Implementation**: ```kotlin // ViewModel private val _events = Channel(Channel.BUFFERED) val events = _events.receiveAsFlow() // Fragment lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.events.collect { event -> handleEvent(event) } } } ``` - **Why Critical**: Configuration changes (rotation) must NOT re-trigger: - Navigation to 2FA/OTP screens. - Transaction confirmations. - Error dialogs. - Password change prompts. - **Action Handling in Compose Screens**: - For screens with multiple triggers, **always use a sealed class** for actions. - Avoid declaring individual lambda functions in Compose screen constructors. - Exception: Very simple screens with only 1 trigger can use a single lambda. - Pass a single action handler: `onAction: (ScreenAction) -> Unit`. Example patterns: ```kotlin // Screen Actions (multiple triggers) sealed class ProfileScreenAction { object OnBackPressed : ProfileScreenAction() object OnLogoutClicked : ProfileScreenAction() data class OnAvatarChanged(val uri: Uri) : ProfileScreenAction() object OnEditProfileClicked : ProfileScreenAction() object OnSettingsClicked : ProfileScreenAction() } // Compose Screen @Composable fun ProfileScreen( state: ProfileState, onAction: (ProfileScreenAction) -> Unit ) { // UI implementation } // Simple screen exception (only 1 trigger) @Composable fun SimpleDialogScreen( message: String, onDismiss: () -> Unit // OK for single action ) { // UI implementation } ``` ## Project Architecture Guidelines - **Architecture**: Multi-module Clean Architecture with MVVM pattern, Single Activity. - **Core Modules**: - `core-common`: Shared utilities, constants, extensions, session management. - `core-data`: Repository implementations, encryption service, API clients. - `core-domain`: Business logic, use cases, repository interfaces, models. - `core-ui`: Foundation Design System (FDS), base UI components, themes. - `core-ott`: One-Time Token notification system and handlers. - `core-keyboard`: Custom keyboard implementations. - **Feature Modules**: - `feature-login`: Authentication, biometric login, session initialization. - `feature-home`: Dashboard, quick actions, account summary. - `feature-account`: Account details, balance inquiry, statements. - `feature-transaction-manage`: Transaction history, search, filters. - `feature-maker`: Transaction creation (Maker-Checker pattern). - `feature-checker`: Transaction approval (Maker-Checker pattern). - Additional features: eKYC, smartCA, soft token, etc. - **Foundation Design System (FDS)**: - Central design system in `core-ui`. - Provides Colors, Typography, Sizer (dimensions), Effects. - All UI components must use FDS tokens. - Import as: `import com.vietinbank.core_ui.theme.FoundationDesignSystem as FDS`. - **Module Guidelines**: - Use cases → `core-domain`. - Repository implementations → `core-data`. - API/Database related → `core-data`. - UI screens/ViewModels → appropriate `feature-*` module. - Shared UI components → `core-ui`. - Utilities/Extensions → `core-common`. ## Technical Decision Guidelines - **Maintain Technical Consistency**: - Once a technical decision is made with proper reasoning, stick to it. - Don't change implementation approaches based on questioning alone. - Always provide clear technical justification for design choices. - If user asks exploratory questions, explain the reasoning but don't change working code. ## Working with AI Assistants - **Gemini/Zen**: Dùng để phân tích context, hiểu vấn đề, và nhận góc nhìn bổ sung. - **Claude**: Là người quyết định cuối cùng và implement giải pháp. - **Nguyên tắc**: Không thay đổi code đang hoạt động tốt chỉ vì expert suggestion. - **Approach**: Incremental improvements, không refactor toàn bộ. ## Jetpack Compose Best Practices - **Understand Compose Context Boundaries**: - `@Composable` functions can only be called from other `@Composable` contexts. - `DrawScope` (inside `drawBehind`, `drawWithContent`, etc.) is NOT a Composable context. - Always resolve resources (colors, strings, dimensions) in Composable scope, NOT in drawing operations. - **Compose's Three Phases**: 1. **Composition**: @Composable functions run, build UI tree. 2. **Layout**: Measure and position elements. 3. **Drawing**: Draw pixels (DrawScope). - **Resource Resolution Pattern**: ```kotlin // CORRECT - Resolve in Composable scope val backgroundColor = FDS.Colors.white val textColor = FDS.Colors.primary Box( modifier = Modifier.drawBehind { // Use pre-resolved colors drawRect(color = backgroundColor) } ) { Text( text = "Hello", color = textColor // Direct use in Composable ) } // WRONG - Don't call @Composable in DrawScope Box( modifier = Modifier.drawBehind { drawRect(color = FDS.Colors.white) // ERROR! } ) ``` ## Resource Management for XML + Compose Hybrid Project - **CRITICAL**: This project supports BOTH XML layouts and Compose UI. - **Foundation Design System (FDS) is the single source of truth** từ Figma dev mode. - **Every resource must be accessible from both systems**. ### Color Management with Foundation Design System (FDS) 1. **FDS Colors Structure**: ```kotlin // Always use FDS.Colors for consistency import com.vietinbank.core_ui.theme.FoundationDesignSystem as FDS // Semantic Colors (preferred for general use) FDS.Colors.primary // Primary brand color FDS.Colors.secondary // Secondary brand color FDS.Colors.error // Error states FDS.Colors.success // Success states FDS.Colors.warning // Warning states // Functional Tokens (direct from Figma dev mode) FDS.Colors.characterHighlighted // character/highlighted (#0F4C7A) FDS.Colors.characterPrimary // character/primary (#232A2F) FDS.Colors.backgroundBgContainer // background/bg-container (#FFFFFF) FDS.Colors.stateActive // state/active (#2EB2F7) // Component-specific Colors FDS.Colors.buttonGradientPrimary // Button gradient start FDS.Colors.glassGradient1 // Glass morphism effects ``` 2. **Usage Patterns**: ```kotlin // In Composable - resolve in Composable scope @Composable fun MyComponent() { val primaryColor = FDS.Colors.primary // Resolve early val textColor = FDS.Colors.characterPrimary Box( modifier = Modifier.drawBehind { drawRect(color = primaryColor) // Use pre-resolved } ) { Text( text = "Hello", color = textColor // Direct use in Composable ) } } // When designer references Figma token // Designer: "Use character/highlighted-lighter here" Text( text = "Important", color = FDS.Colors.characterHighlightedLighter ) ``` 3. **Important Rules**: - **NEVER hardcode colors** – always use FDS.Colors. - **Resolve colors in Composable scope** before DrawScope. - **Match Figma tokens exactly** when specified (dev mode audit). - All colors defined in `colors.xml` for XML compatibility. ### Dimension Management with FDS 1. **FDS Sizer Structure**: ```kotlin // Use FDS.Sizer for all dimensions import com.vietinbank.core_ui.theme.FoundationDesignSystem as FDS // Padding values FDS.Sizer.Padding.padding4 // 4dp FDS.Sizer.Padding.padding8 // 8dp FDS.Sizer.Padding.padding16 // 16dp (common) FDS.Sizer.Padding.padding24 // 24dp // Gap values (spacing between elements) FDS.Sizer.Gap.gap8 // 8dp FDS.Sizer.Gap.gap16 // 16dp (common) FDS.Sizer.Gap.gap24 // 24dp // Icon sizes FDS.Sizer.Icon.icon24 // 24dp (default) FDS.Sizer.Icon.icon32 // 32dp (large) // Border radius FDS.Sizer.Radius.radius4 // 4dp (small) FDS.Sizer.Radius.radius8 // 8dp (medium) FDS.Sizer.Radius.radius32 // 32dp (default) FDS.Sizer.Radius.radiusFull // 999dp (circular) // Stroke widths FDS.Sizer.Stroke.stroke05 // 0.5dp (thin) FDS.Sizer.Stroke.stroke1 // 1dp (default) ``` 2. **Usage Patterns**: ```kotlin @Composable fun MyComponent() { Box( modifier = Modifier .padding(FDS.Sizer.Padding.padding16) .clip(RoundedCornerShape(FDS.Sizer.Radius.radius8)) ) { Row( horizontalArrangement = Arrangement.spacedBy(FDS.Sizer.Gap.gap8) ) { Icon( modifier = Modifier.size(FDS.Sizer.Icon.icon24), // ... ) } } } ``` 3. **Important Rules**: - **Use FDS.Sizer tokens** instead of hardcoded dp values. - **Gap for spacing between elements**, Padding for internal spacing. - All dimensions also available in `dimens.xml` for XML layouts. ### Theme Management with FDS 1. **Foundation Design System Components**: ```kotlin // Import once per file import com.vietinbank.core_ui.theme.FoundationDesignSystem as FDS // Access all design tokens FDS.Colors // All color tokens FDS.Typography // Text styles & Material3 typography FDS.Sizer // Dimensions (padding, gap, radius, etc.) FDS.Effects // Elevations and shadows ``` 2. **Typography System**: ```kotlin // Material3 Typography (for theme) FDS.Typography.material3Typography // Complete Material3 Typography // Functional Typography Tokens (from Figma dev mode) FDS.Typography.headingH1 // 32/40sp, SemiBold, -1% spacing FDS.Typography.headingH2 // 24/32sp, SemiBold, -1% spacing FDS.Typography.bodyB1 // 16/24sp, Medium, -1.5% spacing FDS.Typography.interactionButton // 16/24sp, SemiBold, -2% spacing // Usage Text( text = "Title", style = FDS.Typography.headingH2, color = FDS.Colors.characterPrimary ) ``` 3. **Effects (Elevations)**: ```kotlin // FDS.Effects provides consistent elevations FDS.Effects.elevationSm // 2dp - Cards FDS.Effects.elevationMd // 4dp - Dialogs FDS.Effects.elevationLg // 8dp - Modals FDS.Effects.elevationButton // 10dp - Buttons (Foundation spec) // Usage Card( elevation = CardDefaults.cardElevation( defaultElevation = FDS.Effects.elevationSm ) ) ``` 4. **Theme Setup**: ```kotlin @Composable fun AppTheme(content: @Composable () -> Unit) { MaterialTheme( colorScheme = lightColorScheme, // Maps to FDS colors typography = FDS.Typography.material3Typography, content = content ) } ``` 5. **Important Rules**: - **FDS is the single source of truth** for all design tokens (Figma dev mode sync). - **Never hardcode values** – always use FDS tokens. - **Match Figma exactly** – use functional tokens when specified. - All tokens accessible from both XML and Compose. ### Resource Management Best Practices 1. **Foundation Design System (FDS) as Single Source of Truth**: ```kotlin // ALWAYS use FDS tokens FDS.Colors.primary // NOT Color(0xFF006FBD) FDS.Sizer.Padding.padding16 // NOT 16.dp FDS.Effects.elevationSm // NOT 2.dp FDS.Typography.headingH2 // NOT custom TextStyle ``` 2. **Resource Resolution in Different Contexts**: ```kotlin // In Composable context - direct access @Composable fun MyComponent() { Text( text = "Hello", color = FDS.Colors.primary, // Direct use modifier = Modifier.padding(FDS.Sizer.Padding.padding16) ) } // In DrawScope - resolve first @Composable fun MyDrawComponent() { val primaryColor = FDS.Colors.primary // Resolve in Composable Canvas(modifier = Modifier.fillMaxSize()) { drawRect(color = primaryColor) // Use pre-resolved } } ``` 3. **XML and Compose Compatibility**: - All FDS resources backed by XML resources. - Ensures consistency across both UI systems. - XML: `@color/foundation_primary` - Compose: `FDS.Colors.primary` 4. **Best Practices Checklist**: - ✅ Use FDS tokens for all design values. - ✅ Resolve resources in Composable scope for DrawScope. - ✅ Match Figma token names exactly when specified (dev mode). - ✅ Remove hardcoded values during code review. - ✅ Maintain XML resources for backward compatibility. - ❌ Never hardcode colors, dimensions, or elevations. - ❌ Never create custom design tokens outside FDS. - **Asynchronous Operations**: - Use `suspend` functions for any I/O operations that might block. - Operations that could take > 16ms should be suspend (frame drop threshold). - Provide both suspend (core API) and non-suspend convenience methods when appropriate. Examples: ```kotlin // Core API - always safe with suspend suspend fun save(data: T) suspend fun get(): T? // Convenience API - fire-and-forget fun saveAsync(data: T) fun clearAsync() ``` - **Cache Implementation Guidelines**: - Always consider serialization cost (Gson can be slow with large objects). - Default to encrypted storage (`encrypted = true`), opt-out for non-sensitive data. - Use StateFlow for reactive data updates. - Handle errors gracefully without crashing. - Use internal CoroutineScope with SupervisorJob for async operations. - **Design Decisions Must Be Based On**: - Technical facts and measurements (e.g., serialization can take 50-500ms for large objects). - Established best practices (Kotlin coroutines guidelines, Android architecture). - Real-world performance implications (UI responsiveness, ANR prevention). - NOT on user's exploratory questions or "what if" scenarios. ## Common Development Patterns ### Component Constants Pattern - Define constants as `private object` within component file: ```kotlin // CircularIconButton.kt private object CircularIconButtonConstants { const val GRADIENT_RADIUS_MULTIPLIER = 1.5f const val GRADIENT_BLUE_END = 0.5f const val SHADOW_ALPHA = 0.1f } ``` - Benefits: Encapsulation, no hardcoded values, easy maintenance. - Do NOT create separate constant files for individual components. ### Backward Compatibility - When updating existing components, maintain backward compatibility: ```kotlin // Add new parameters with default values fun FoundationAppBar( title: String, onNavigationClick: () -> Unit, // New parameters with defaults isBackHome: Boolean = false, actions: List = emptyList(), ) // Use type alias if renaming typealias OldComponentName = NewComponentName ``` ### Component Development Workflow 1. **Analyze existing patterns** (e.g., study BaseAppBar before creating FoundationAppBar). 2. **Reuse common components** (e.g., use CircularIconButton in AppBar). 3. **Follow established patterns** while adding design system specifics. 4. **Maintain feature parity** when creating alternatives. ### Pressed State Implementation - Use `MutableInteractionSource` and `collectIsPressedAsState()`: ```kotlin val interactionSource = remember { MutableInteractionSource() } val isPressed by interactionSource.collectIsPressedAsState() // Apply different visuals based on state val gradient = if (isPressed) { // Pressed state gradient } else { // Default state gradient } ``` ### Resource Resolution in Different Contexts - **In Composable**: Direct access to FDS resources. - **In DrawScope**: Pre-resolve resources in Composable scope. - **Never** call resource functions inside DrawScope. Example pattern: ```kotlin @Composable fun MyComponent() { // Resolve in Composable scope val color = FDS.Colors.primary val radius = FDS.Sizer.Radius.radius8 Canvas { // Use pre-resolved values drawRoundRect(color = color, cornerRadius = CornerRadius(radius.toPx())) } } ``` ## Responsive Layout Guidelines ### Core Principles 1. **NEVER hardcode large spacing values** (e.g., 140dp). 2. **Use percentage-based spacing** with AppWindowSizeClass (NOT Material3 WindowSizeClass). 3. **Always coerceIn()** with min/max boundaries from FDS tokens. 4. **One insets strategy** - Either Scaffold OR manual, not both. 5. **imePadding() on scrollable container OR BoxWithConstraints**, not both. 6. **API Leakage Prevention** - Use AppWindowSizeClass abstraction, not Material3 directly. ### Standard Responsive Pattern ```kotlin // IMPORTANT: Use AppWindowSizeClass, NOT Material3 WindowSizeClass Scaffold(contentWindowInsets = WindowInsets.safeDrawing) { padding -> BoxWithConstraints( Modifier .fillMaxSize() .padding(padding) .imePadding() // IME here for maxHeight to shrink ) { val windowSizeClass = rememberAppWindowSizeClass() // Use app abstraction val targetSpacing = adaptiveSpacingFor(windowSizeClass) // Animate with guard for large changes (>56dp) val spacing = animateAdaptiveSpacing(targetSpacing) LazyColumn(Modifier.fillMaxSize()) { // NO imePadding here // Content with adaptive spacing } } } ``` ### Adaptive Spacing Calculation - **Base percentage from Figma dev mode**: Position / Screen Height (e.g., 144px / 852px = 0.17). - **Adjust by screen size**: - Compact (small phones): 15% of visible height. - Medium (normal phones): 17% of visible height. - Expanded (tablets): 20% of visible height. - **Always clamp**: `.coerceIn(48.dp, 180.dp)`. ### Tablet Centering Pattern ```kotlin // ✅ CORRECT - Box wrapper for centering item { Box(Modifier.fillMaxWidth()) { Card( Modifier .widthIn(max = 600.dp) .align(Alignment.Center) ) } } // ❌ WRONG - fillMaxWidth + wrapContentWidth doesn't work properly Card( Modifier .fillMaxWidth() .wrapContentWidth(Alignment.CenterHorizontally) .widthIn(max = 600.dp) ) ``` ### IME (Keyboard) Handling - **Option A (Recommended)**: `imePadding()` on BoxWithConstraints – `maxHeight` automatically adjusts when keyboard opens. Animation runs smoothly. - **Option B**: `imePadding()` on LazyColumn – Must manually calculate visible height with WindowInsets.ime. ### Testing Requirements - [ ] **Screen sizes**: Compact (5"), Medium (6.5"), Expanded (10"+). - [ ] **Font scale**: 1.0, 1.3, 1.5, 2.0 (accessibility maximum). - [ ] **Orientation**: Portrait & Landscape. - [ ] **IME**: Keyboard open/closed animation smooth. - [ ] **Languages**: Vietnamese (long text), English. - [ ] **Multi-window**: Split-screen on tablets and foldables. - [ ] **ChromeOS**: Desktop mode with max width 680-720dp. ### Common Mistakes to Avoid - ❌ Hardcoding dp values for major spacing. - ❌ Double insets (Scaffold + systemBarsPadding). - ❌ Double IME compensation. - ❌ Using weight in verticalScroll. - ❌ Missing Box wrapper for tablet centering. - ❌ Using @Stable on @Composable functions. - ❌ Using Material3 WindowSizeClass directly (use AppWindowSizeClass). - ❌ Using `api` dependency for Material3 window size class (causes API leakage). ### Edge Cases & Production Warnings #### AppWindowSizeClass Abstraction - **Why**: Prevents API leakage when Material3 is an `implementation` dependency. - **Pattern**: Create app-specific abstraction layer (`AppWindowSizeClass`). - **Location**: `core-ui/utils/AppWindowSizeClass.kt`. - **Usage**: Always use `rememberAppWindowSizeClass()`, never Material3 directly. #### Landscape Mode Protection - **Issue**: Phones in landscape can have height < 400dp. - **Solution**: Additional constraint in `adaptiveSpacingFor()` ```kotlin when { windowSizeClass.isLandscape && maxHeight < 400.dp -> { clampedSpacing.coerceAtMost(96.dp) // Hard limit for short screens } } ``` #### Animation Guards for Large Changes - **Issue**: Jarring animations during rotation or IME changes. - **Solution**: Snap instead of animate when delta > 56dp. ```kotlin val spacing = animateAdaptiveSpacing( targetSpacing = adaptiveSpacingFor(windowSizeClass), threshold = 56.dp // Snap if change is larger ) ``` #### Multi-Device Considerations 1. **Foldables**: Test with both screens (cover & main). 2. **Tablets**: Max content width 600-680dp for readability. 3. **ChromeOS**: Desktop mode needs max width constraints. 4. **Font Scale 2.0x**: Test with maximum accessibility setting. 5. **Split-screen**: Ensure layout adapts when window size changes dynamically. #### Production Readiness Checklist - [ ] AppWindowSizeClass abstraction implemented (no Material3 exposure). - [ ] Landscape protection for height < 400dp. - [ ] Animation guards for spacing changes > 56dp. - [ ] Max width constraints for tablets (600-680dp). - [ ] Font scale 2.0x tested without layout breaks. - [ ] Multi-window scenarios tested. - [ ] RTL layout support verified (if applicable). ## Common Mistakes to Avoid ### 1. Hardcoding Design Values ❌ **Wrong**: Hardcoding dimensions, colors, or text styles ```kotlin val tabHeight = 32.dp // NEVER hardcode dimensions val textColor = Color(0xFF0F4C7A) // NEVER hardcode colors Text( fontSize = 16.sp, // NEVER hardcode text sizes lineHeight = 24.sp, // NEVER hardcode line heights ) ``` ✅ **Correct**: Always use FDS tokens ```kotlin val tabHeight = FDS.Sizer.Tab.tabHeight val textColor = FDS.Colors.characterHighlighted Text( style = FDS.Typography.bodyB1, ) ``` ### 2. Creating Constants for Design Tokens ❌ **Wrong**: Adding design values to component constants ```kotlin private object TabConstants { val TAB_HEIGHT = 32.dp // Should be in FDS val CONTAINER_HEIGHT = 48.dp // Should be in FDS const val OPACITY = 0.2f // OK - not a design token } ``` ✅ **Correct**: Add to FDS if not exists, constants only for non-design values ```kotlin private object TabConstants { const val GLASS_OPACITY = 0.2f // OK - opacity percentage const val ANIMATION_DURATION = 300 // OK - milliseconds } // Use FDS for dimensions val height = FDS.Sizer.Tab.tabHeight ``` ### 3. Using Standard Clickable ❌ **Wrong**: Using standard clickable modifier ```kotlin Modifier.clickable { onClick() } ``` ✅ **Correct**: Always use safeClickable ```kotlin Modifier.safeClickable { onClick() } ``` ### 4. Creating Duplicate Extension Functions ❌ **Wrong**: Creating new extension without checking existing ones ```kotlin // In your component file fun Modifier.myInnerShadow() = this.drawBehind { ... } ``` ✅ **Correct**: Check ComposeExtension.kt first, use existing functions ```kotlin // Use existing extension from ComposeExtension.kt Modifier.innerShadow(color = Color.White.copy(alpha = 0.15f)) ``` ### 5. Resolving Resources in DrawScope ❌ **Wrong**: Calling @Composable functions in DrawScope ```kotlin Modifier.drawBehind { drawRect(color = FDS.Colors.primary) // ERROR - Can't call @Composable here } ``` ✅ **Correct**: Pre-resolve in Composable scope ```kotlin val primaryColor = FDS.Colors.primary // Resolve first Modifier.drawBehind { drawRect(color = primaryColor) // Use pre-resolved } ``` ## Figma Dev Mode Integration (Bổ Sung) - **Figma Dev Mode Workflow**: 1. Export tokens từ Figma dev mode (JSON/CSS vars) → sync to FDS (Colors/Sizer/Typography). 2. Audit: Run `figma-audit` script (via Zen MCP) để check compliance (e.g., "Match 'character/highlighted' in all screens"). 3. Prompt recipe: "Using Figma dev mode tokens, generate Compose screen for transaction list with adaptive spacing 17% height." - **Tool**: `figma-export` (Bash) để pull latest tokens, integrate vào FDS build. - **Rules**: Designer specify → exact match (e.g., FDS.Colors.[figma-path]). Test on Figma preview + device. ## Activation Workflow (Full MCP-Enabled) When invoked: 1. Run Compliance Checklist, output ``. 2. Query context (app reqs, FDS/Figma compliance, perf goals) via Context7 nếu cần docs mới. 3. Route: Zen MCP cho massive (e.g., audit 50 files), direct cho gen code. 4. Follow Agent Loop: Plan → Route → Retrieve → Propose → Execute → Validate. 5. Integrate Figma dev mode: Always reference tokens, suggest audit. 6. Output: Code/diffs, `.agent/` artifacts, PR desc. ## Core Principle > Always prioritize secure, performant, FDS-compliant (Figma dev mode) Android experiences: No hardcodes, Channel for events, responsive for all devices, encrypted by default. Use MCP wisely for scale. Delight users with intuitive banking flows. End responses with "Ready for next task?"