android-sdd 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +143 -0
- package/package.json +27 -0
- package/skills/Android Ecosystem/Baseline Profile Generator/SKILL.md +277 -0
- package/skills/Android Ecosystem/Glance/SKILL.md +315 -0
- package/skills/Android Platform/Configuration/SKILL.md +201 -0
- package/skills/Android Platform/Filesystem/SKILL.md +216 -0
- package/skills/Android Platform/Lifecycle/SKILL.md +233 -0
- package/skills/Android Platform/Manifest/SKILL.md +226 -0
- package/skills/Android Platform/Process Death Recovery/SKILL.md +214 -0
- package/skills/Android Platform/Resources/SKILL.md +234 -0
- package/skills/Android Platform/SavedStateHandle/SKILL.md +217 -0
- package/skills/Android Platform/State Restoration/SKILL.md +210 -0
- package/skills/Architecture/Bounded Context/SKILL.md +207 -0
- package/skills/Architecture/Clean Architecture/SKILL.md +229 -0
- package/skills/Architecture/Domain Modeling/SKILL.md +236 -0
- package/skills/Architecture/Entity Design/SKILL.md +243 -0
- package/skills/Architecture/Feature Isolation/SKILL.md +216 -0
- package/skills/Architecture/MVI/SKILL.md +224 -0
- package/skills/Architecture/MVVM/SKILL.md +198 -0
- package/skills/Architecture/Modularization/SKILL.md +194 -0
- package/skills/Architecture/Offline First/SKILL.md +249 -0
- package/skills/Architecture/Repository Pattern/SKILL.md +216 -0
- package/skills/Architecture/Side Effect Management/SKILL.md +278 -0
- package/skills/Architecture/State Management/SKILL.md +229 -0
- package/skills/Architecture/Unidirectional Data Flow/SKILL.md +196 -0
- package/skills/Architecture/Use Case Design/SKILL.md +244 -0
- package/skills/Architecture/Value Object/SKILL.md +226 -0
- package/skills/Build Infrastructure/Build Orchestration/SKILL.md +257 -0
- package/skills/Build Infrastructure/Dependency Compatibility Resolver/SKILL.md +259 -0
- package/skills/Build Infrastructure/Environment Validator/SKILL.md +311 -0
- package/skills/Build System/Build Cache/SKILL.md +233 -0
- package/skills/Build System/Build Flavor Strategy/SKILL.md +171 -0
- package/skills/Build System/Build Variant/SKILL.md +215 -0
- package/skills/Build System/Convention Plugin/SKILL.md +288 -0
- package/skills/Build System/Dependency Management/SKILL.md +261 -0
- package/skills/Build System/Gradle/SKILL.md +284 -0
- package/skills/Build System/Incremental Build/SKILL.md +199 -0
- package/skills/Build System/KAPT/SKILL.md +198 -0
- package/skills/Build System/KSP/SKILL.md +263 -0
- package/skills/Build System/Module Dependency Graph Validation/SKILL.md +223 -0
- package/skills/Build System/Specialized/C++/SKILL.md +308 -0
- package/skills/Build System/Specialized/JNI/SKILL.md +306 -0
- package/skills/Build System/Specialized/NDK/SKILL.md +264 -0
- package/skills/Build System/Version Catalog/SKILL.md +304 -0
- package/skills/Concurrency/Background Processing/SKILL.md +185 -0
- package/skills/Concurrency/Channel/SKILL.md +207 -0
- package/skills/Concurrency/Coroutine/SKILL.md +200 -0
- package/skills/Concurrency/Flow/SKILL.md +179 -0
- package/skills/Concurrency/Mutex Strategy/SKILL.md +185 -0
- package/skills/Concurrency/SharedFlow/SKILL.md +171 -0
- package/skills/Concurrency/StateFlow/SKILL.md +175 -0
- package/skills/Concurrency/Structured Concurrency/SKILL.md +197 -0
- package/skills/Concurrency/Synchronization Policy/SKILL.md +192 -0
- package/skills/Core Language/Annotation Processing/SKILL.md +224 -0
- package/skills/Core Language/DSL/SKILL.md +186 -0
- package/skills/Core Language/Extension Functions Design/SKILL.md +191 -0
- package/skills/Core Language/Immutability/SKILL.md +156 -0
- package/skills/Core Language/KMP/SKILL.md +182 -0
- package/skills/Core Language/Kotlin/SKILL.md +187 -0
- package/skills/Core Language/Reactive State Management/SKILL.md +228 -0
- package/skills/Core Language/Reactive Streams/SKILL.md +235 -0
- package/skills/Core Language/Serialization/SKILL.md +191 -0
- package/skills/Data Layer/Cache Strategy/SKILL.md +261 -0
- package/skills/Data Layer/Conflict Resolution/SKILL.md +248 -0
- package/skills/Data Layer/DAO/SKILL.md +225 -0
- package/skills/Data Layer/DTO Mapping/SKILL.md +269 -0
- package/skills/Data Layer/DataStore/SKILL.md +264 -0
- package/skills/Data Layer/Database Versioning Strategy/SKILL.md +215 -0
- package/skills/Data Layer/Encrypted Database/SKILL.md +212 -0
- package/skills/Data Layer/File Storage/SKILL.md +247 -0
- package/skills/Data Layer/Indexing/SKILL.md +184 -0
- package/skills/Data Layer/Key-Value Store Strategy/SKILL.md +185 -0
- package/skills/Data Layer/Merge Strategy/SKILL.md +240 -0
- package/skills/Data Layer/Migration/SKILL.md +243 -0
- package/skills/Data Layer/Paging/SKILL.md +264 -0
- package/skills/Data Layer/Proto DataStore/SKILL.md +250 -0
- package/skills/Data Layer/Room/SKILL.md +244 -0
- package/skills/Data Layer/SQLite/SKILL.md +255 -0
- package/skills/Data Layer/Sync Engine/SKILL.md +268 -0
- package/skills/Dependency Injection/Dagger/SKILL.md +283 -0
- package/skills/Dependency Injection/Hilt/SKILL.md +345 -0
- package/skills/Dependency Injection/Koin/SKILL.md +282 -0
- package/skills/Developer Experience/Detekt/SKILL.md +272 -0
- package/skills/Developer Experience/Lint Rule/SKILL.md +281 -0
- package/skills/Google Ecosystem/Analytics/SKILL.md +281 -0
- package/skills/Google Ecosystem/Crashlytics/SKILL.md +234 -0
- package/skills/Google Ecosystem/Firebase/SKILL.md +200 -0
- package/skills/Google Ecosystem/Firebase Messaging/SKILL.md +266 -0
- package/skills/Media/Audio/SKILL.md +257 -0
- package/skills/Media/Camera/SKILL.md +229 -0
- package/skills/Media/CameraX/SKILL.md +295 -0
- package/skills/Media/ExoPlayer/SKILL.md +258 -0
- package/skills/Media/Video/SKILL.md +228 -0
- package/skills/Meta Skills/Domain Error Model/SKILL.md +238 -0
- package/skills/Meta Skills/Error Handling/SKILL.md +255 -0
- package/skills/Meta Skills/Error Mapping/SKILL.md +232 -0
- package/skills/Meta Skills/Failure Strategy/SKILL.md +294 -0
- package/skills/Meta Skills/Migration Strategy/SKILL.md +305 -0
- package/skills/Meta Skills/User Friendly Errors/SKILL.md +334 -0
- package/skills/Navigation/Deep Navigation/SKILL.md +209 -0
- package/skills/Navigation/Navigation/SKILL.md +215 -0
- package/skills/Navigation/Nested Navigation/SKILL.md +214 -0
- package/skills/Networking/API Contract/SKILL.md +220 -0
- package/skills/Networking/Authentication/SKILL.md +210 -0
- package/skills/Networking/Certificate Pinning/SKILL.md +167 -0
- package/skills/Networking/Fallback Strategy/SKILL.md +182 -0
- package/skills/Networking/Ktor/SKILL.md +219 -0
- package/skills/Networking/Multipart Upload/SKILL.md +213 -0
- package/skills/Networking/OkHttp/SKILL.md +193 -0
- package/skills/Networking/REST/SKILL.md +178 -0
- package/skills/Networking/Rate Limiting/SKILL.md +170 -0
- package/skills/Networking/Retrofit/SKILL.md +241 -0
- package/skills/Networking/Retry-Backoff/SKILL.md +181 -0
- package/skills/Networking/Server-Sent Events (SSE)/SKILL.md +196 -0
- package/skills/Networking/WebSocket/SKILL.md +224 -0
- package/skills/Observability/Crash Reporting/SKILL.md +219 -0
- package/skills/Observability/Logging/SKILL.md +168 -0
- package/skills/Observability/Metrics/SKILL.md +227 -0
- package/skills/Observability/Structured Logging/SKILL.md +234 -0
- package/skills/Performance/ANR Prevention/SKILL.md +192 -0
- package/skills/Performance/Allocation Optimization/SKILL.md +179 -0
- package/skills/Performance/App Startup/SKILL.md +183 -0
- package/skills/Performance/Baseline Profile/SKILL.md +205 -0
- package/skills/Performance/Battery Optimization/SKILL.md +192 -0
- package/skills/Performance/Benchmark/SKILL.md +182 -0
- package/skills/Performance/Bitmap Optimization/SKILL.md +178 -0
- package/skills/Performance/Compose Optimization/SKILL.md +187 -0
- package/skills/Performance/Heap Management/SKILL.md +184 -0
- package/skills/Performance/Macrobenchmark/SKILL.md +214 -0
- package/skills/Performance/Memory Leak Prevention/SKILL.md +218 -0
- package/skills/Performance/Rendering Performance/SKILL.md +205 -0
- package/skills/Performance/Startup Optimization/SKILL.md +219 -0
- package/skills/Security/Biometric/SKILL.md +224 -0
- package/skills/Security/Certificate Transparency/SKILL.md +158 -0
- package/skills/Security/Cryptography/SKILL.md +244 -0
- package/skills/Security/Encrypted Storage/SKILL.md +273 -0
- package/skills/Security/Frida Detection/SKILL.md +230 -0
- package/skills/Security/Hook Detection/SKILL.md +197 -0
- package/skills/Security/Keystore/SKILL.md +272 -0
- package/skills/Security/Network Security Config/SKILL.md +186 -0
- package/skills/Security/Obfuscation/SKILL.md +226 -0
- package/skills/Security/Proguard/SKILL.md +202 -0
- package/skills/Security/R8/SKILL.md +234 -0
- package/skills/Security/Reverse Engineering Resistance/SKILL.md +267 -0
- package/skills/Security/Root Detection/SKILL.md +220 -0
- package/skills/Security/Secure Networking/SKILL.md +220 -0
- package/skills/System Integration/AlarmManager/SKILL.md +182 -0
- package/skills/System Integration/App Widget/SKILL.md +182 -0
- package/skills/System Integration/Deep Link/SKILL.md +187 -0
- package/skills/System Integration/Foreground Service/SKILL.md +212 -0
- package/skills/System Integration/Notification/SKILL.md +237 -0
- package/skills/System Integration/WorkManager/SKILL.md +256 -0
- package/skills/System Integration/clipboard/SKILL.md +155 -0
- package/skills/System Integration/share-intent/SKILL.md +182 -0
- package/skills/Testing/Compose Testing/SKILL.md +296 -0
- package/skills/Testing/Espresso/SKILL.md +292 -0
- package/skills/Testing/Fake Data/SKILL.md +245 -0
- package/skills/Testing/Integration Testing/SKILL.md +288 -0
- package/skills/Testing/Mocking/SKILL.md +229 -0
- package/skills/Testing/Snapshot Testing/SKILL.md +259 -0
- package/skills/Testing/UI Testing/SKILL.md +293 -0
- package/skills/Testing/Unit Testing/SKILL.md +309 -0
- package/skills/UI System/Bottom Sheet Patterns/SKILL.md +279 -0
- package/skills/UI System/Compose/SKILL.md +296 -0
- package/skills/UI System/Compose Animation/SKILL.md +281 -0
- package/skills/UI System/Compose Multiplatform/SKILL.md +261 -0
- package/skills/UI System/Compose Navigation/SKILL.md +255 -0
- package/skills/UI System/Compose Performance/SKILL.md +274 -0
- package/skills/UI System/Design System/SKILL.md +217 -0
- package/skills/UI System/Empty State Strategy/SKILL.md +208 -0
- package/skills/UI System/Keyboard Navigation/SKILL.md +214 -0
- package/skills/UI System/Loading Strategy/SKILL.md +254 -0
- package/skills/UI System/Material 3/SKILL.md +279 -0
- package/skills/UI System/RTL/SKILL.md +179 -0
- package/src/index.ts +182 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: material3
|
|
3
|
+
description: >
|
|
4
|
+
Material Design 3 theming, components, and color system for Android/Compose.
|
|
5
|
+
Load this skill when setting up M3 theme, using M3 components, defining
|
|
6
|
+
custom color schemes, typography, or shapes, or supporting dynamic color.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Material 3
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
Material Design 3 (M3) is Google's latest design system, built into Jetpack Compose via `androidx.compose.material3`. It provides a comprehensive color system, typography scale, shape system, and a full set of UI components. M3 supports dynamic color (Android 12+) and dark/light themes natively.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Core Principles
|
|
17
|
+
|
|
18
|
+
- Always use **M3 semantic color tokens** — never hardcode colors
|
|
19
|
+
- Define **one MaterialTheme** at the root — never nest multiple themes
|
|
20
|
+
- Use M3 components before building custom — they handle accessibility and state
|
|
21
|
+
- Support **dark mode** from day one — design the color scheme for both
|
|
22
|
+
- Use **dynamic color** where available (API 31+) with a fallback palette
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Theme Setup
|
|
27
|
+
|
|
28
|
+
```kotlin
|
|
29
|
+
// ✅ Theme definition
|
|
30
|
+
@Composable
|
|
31
|
+
fun AppTheme(
|
|
32
|
+
darkTheme: Boolean = isSystemInDarkTheme(),
|
|
33
|
+
dynamicColor: Boolean = true,
|
|
34
|
+
content: @Composable () -> Unit
|
|
35
|
+
) {
|
|
36
|
+
val colorScheme = when {
|
|
37
|
+
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
|
38
|
+
val context = LocalContext.current
|
|
39
|
+
if (darkTheme) dynamicDarkColorScheme(context)
|
|
40
|
+
else dynamicLightColorScheme(context)
|
|
41
|
+
}
|
|
42
|
+
darkTheme -> DarkColorScheme
|
|
43
|
+
else -> LightColorScheme
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
MaterialTheme(
|
|
47
|
+
colorScheme = colorScheme,
|
|
48
|
+
typography = AppTypography,
|
|
49
|
+
shapes = AppShapes,
|
|
50
|
+
content = content
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ✅ Apply at root
|
|
55
|
+
class MainActivity : ComponentActivity() {
|
|
56
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
57
|
+
super.onCreate(savedInstanceState)
|
|
58
|
+
setContent {
|
|
59
|
+
AppTheme {
|
|
60
|
+
AppNavHost()
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Color Scheme
|
|
70
|
+
|
|
71
|
+
```kotlin
|
|
72
|
+
// ✅ Define light and dark color schemes
|
|
73
|
+
private val LightColorScheme = lightColorScheme(
|
|
74
|
+
primary = Purple40,
|
|
75
|
+
onPrimary = Color.White,
|
|
76
|
+
primaryContainer = Purple90,
|
|
77
|
+
onPrimaryContainer = Purple10,
|
|
78
|
+
secondary = PurpleGrey40,
|
|
79
|
+
onSecondary = Color.White,
|
|
80
|
+
secondaryContainer = PurpleGrey90,
|
|
81
|
+
onSecondaryContainer = PurpleGrey10,
|
|
82
|
+
tertiary = Pink40,
|
|
83
|
+
background = Color(0xFFFFFBFE),
|
|
84
|
+
surface = Color(0xFFFFFBFE),
|
|
85
|
+
onBackground = Color(0xFF1C1B1F),
|
|
86
|
+
onSurface = Color(0xFF1C1B1F),
|
|
87
|
+
error = Color(0xFFB3261E)
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
private val DarkColorScheme = darkColorScheme(
|
|
91
|
+
primary = Purple80,
|
|
92
|
+
onPrimary = Purple20,
|
|
93
|
+
primaryContainer = Purple30,
|
|
94
|
+
onPrimaryContainer = Purple90,
|
|
95
|
+
// ...
|
|
96
|
+
)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Color Token Usage
|
|
102
|
+
|
|
103
|
+
```kotlin
|
|
104
|
+
// ✅ Always use MaterialTheme.colorScheme tokens
|
|
105
|
+
@Composable
|
|
106
|
+
fun PrimaryButton(text: String, onClick: () -> Unit) {
|
|
107
|
+
Button(
|
|
108
|
+
onClick = onClick,
|
|
109
|
+
colors = ButtonDefaults.buttonColors(
|
|
110
|
+
containerColor = MaterialTheme.colorScheme.primary,
|
|
111
|
+
contentColor = MaterialTheme.colorScheme.onPrimary
|
|
112
|
+
)
|
|
113
|
+
) {
|
|
114
|
+
Text(text)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ✅ Surface with correct tonal elevation
|
|
119
|
+
Surface(
|
|
120
|
+
color = MaterialTheme.colorScheme.surface,
|
|
121
|
+
tonalElevation = 2.dp
|
|
122
|
+
) { ... }
|
|
123
|
+
|
|
124
|
+
// ❌ Never hardcode colors
|
|
125
|
+
Text(text = "Hello", color = Color(0xFF6200EE))
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### M3 Color Roles Reference
|
|
129
|
+
|
|
130
|
+
| Role | On Role | Use For |
|
|
131
|
+
|------|---------|---------|
|
|
132
|
+
| `primary` | `onPrimary` | Main action, FAB |
|
|
133
|
+
| `primaryContainer` | `onPrimaryContainer` | Selected state, chips |
|
|
134
|
+
| `secondary` | `onSecondary` | Supporting actions |
|
|
135
|
+
| `tertiary` | `onTertiary` | Contrasting accents |
|
|
136
|
+
| `surface` | `onSurface` | Cards, sheets, dialogs |
|
|
137
|
+
| `surfaceVariant` | `onSurfaceVariant` | Input fields, chips |
|
|
138
|
+
| `background` | `onBackground` | Screen background |
|
|
139
|
+
| `error` | `onError` | Error states |
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Typography
|
|
144
|
+
|
|
145
|
+
```kotlin
|
|
146
|
+
// ✅ Define custom typography
|
|
147
|
+
val AppTypography = Typography(
|
|
148
|
+
displayLarge = TextStyle(
|
|
149
|
+
fontFamily = FontFamily.Default,
|
|
150
|
+
fontWeight = FontWeight.Normal,
|
|
151
|
+
fontSize = 57.sp,
|
|
152
|
+
lineHeight = 64.sp
|
|
153
|
+
),
|
|
154
|
+
titleLarge = TextStyle(
|
|
155
|
+
fontFamily = FontFamily.Default,
|
|
156
|
+
fontWeight = FontWeight.Normal,
|
|
157
|
+
fontSize = 22.sp,
|
|
158
|
+
lineHeight = 28.sp
|
|
159
|
+
),
|
|
160
|
+
bodyLarge = TextStyle(
|
|
161
|
+
fontFamily = FontFamily.Default,
|
|
162
|
+
fontWeight = FontWeight.Normal,
|
|
163
|
+
fontSize = 16.sp,
|
|
164
|
+
lineHeight = 24.sp
|
|
165
|
+
),
|
|
166
|
+
labelSmall = TextStyle(
|
|
167
|
+
fontFamily = FontFamily.Default,
|
|
168
|
+
fontWeight = FontWeight.Medium,
|
|
169
|
+
fontSize = 11.sp,
|
|
170
|
+
lineHeight = 16.sp
|
|
171
|
+
)
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
// ✅ Use typography tokens
|
|
175
|
+
Text(text = "Title", style = MaterialTheme.typography.titleLarge)
|
|
176
|
+
Text(text = "Body text", style = MaterialTheme.typography.bodyMedium)
|
|
177
|
+
Text(text = "Label", style = MaterialTheme.typography.labelSmall)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Shapes
|
|
183
|
+
|
|
184
|
+
```kotlin
|
|
185
|
+
// ✅ Define shape scale
|
|
186
|
+
val AppShapes = Shapes(
|
|
187
|
+
extraSmall = RoundedCornerShape(4.dp),
|
|
188
|
+
small = RoundedCornerShape(8.dp),
|
|
189
|
+
medium = RoundedCornerShape(12.dp),
|
|
190
|
+
large = RoundedCornerShape(16.dp),
|
|
191
|
+
extraLarge = RoundedCornerShape(28.dp)
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
// ✅ Use shape tokens
|
|
195
|
+
Card(shape = MaterialTheme.shapes.medium) { ... }
|
|
196
|
+
Button(shape = MaterialTheme.shapes.extraLarge) { ... } // pill shape
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Key M3 Components
|
|
202
|
+
|
|
203
|
+
```kotlin
|
|
204
|
+
// ✅ Buttons
|
|
205
|
+
Button(onClick = {}) { Text("Filled") }
|
|
206
|
+
OutlinedButton(onClick = {}) { Text("Outlined") }
|
|
207
|
+
TextButton(onClick = {}) { Text("Text") }
|
|
208
|
+
FilledTonalButton(onClick = {}) { Text("Tonal") }
|
|
209
|
+
ElevatedButton(onClick = {}) { Text("Elevated") }
|
|
210
|
+
|
|
211
|
+
// ✅ FAB
|
|
212
|
+
FloatingActionButton(onClick = {}) {
|
|
213
|
+
Icon(Icons.Default.Add, contentDescription = "Add")
|
|
214
|
+
}
|
|
215
|
+
ExtendedFloatingActionButton(
|
|
216
|
+
text = { Text("New item") },
|
|
217
|
+
icon = { Icon(Icons.Default.Add, contentDescription = null) },
|
|
218
|
+
onClick = {}
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
// ✅ Cards
|
|
222
|
+
Card(modifier = Modifier.fillMaxWidth()) { ... }
|
|
223
|
+
ElevatedCard { ... }
|
|
224
|
+
OutlinedCard { ... }
|
|
225
|
+
|
|
226
|
+
// ✅ Top App Bar
|
|
227
|
+
TopAppBar(
|
|
228
|
+
title = { Text("Screen Title") },
|
|
229
|
+
navigationIcon = {
|
|
230
|
+
IconButton(onClick = onBack) {
|
|
231
|
+
Icon(Icons.Default.ArrowBack, contentDescription = "Back")
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
actions = {
|
|
235
|
+
IconButton(onClick = {}) {
|
|
236
|
+
Icon(Icons.Default.MoreVert, contentDescription = "More")
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
// ✅ Navigation Bar
|
|
242
|
+
NavigationBar {
|
|
243
|
+
items.forEach { item ->
|
|
244
|
+
NavigationBarItem(
|
|
245
|
+
icon = { Icon(item.icon, contentDescription = null) },
|
|
246
|
+
label = { Text(item.label) },
|
|
247
|
+
selected = currentRoute == item.route,
|
|
248
|
+
onClick = { onNavigate(item.route) }
|
|
249
|
+
)
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// ✅ Snackbar
|
|
254
|
+
val snackbarHostState = remember { SnackbarHostState() }
|
|
255
|
+
Scaffold(snackbarHost = { SnackbarHost(snackbarHostState) }) { padding ->
|
|
256
|
+
// content
|
|
257
|
+
}
|
|
258
|
+
LaunchedEffect(Unit) {
|
|
259
|
+
snackbarHostState.showSnackbar("Item deleted", actionLabel = "Undo")
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## Anti-Patterns
|
|
266
|
+
|
|
267
|
+
- Hardcoding colors instead of using `MaterialTheme.colorScheme` tokens
|
|
268
|
+
- Nesting multiple `MaterialTheme` composables — causes inconsistent theming
|
|
269
|
+
- Using `androidx.compose.material` (M2) components mixed with M3 — conflicts
|
|
270
|
+
- Not providing a non-dynamic fallback color scheme for API < 31
|
|
271
|
+
- Using `Color.White`/`Color.Black` directly — breaks dark mode
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## Related Skills
|
|
276
|
+
- `compose` — Compose fundamentals
|
|
277
|
+
- `design-system` — extending M3 with custom design tokens
|
|
278
|
+
- `resources` — color and style resources for XML interop
|
|
279
|
+
- `rtl` — RTL support in M3 components
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rtl
|
|
3
|
+
description: >
|
|
4
|
+
Right-to-Left (RTL) layout support in Android and Jetpack Compose.
|
|
5
|
+
Load this skill when building layouts that must support RTL languages
|
|
6
|
+
(Arabic, Persian, Hebrew), mirroring icons and directions, handling
|
|
7
|
+
bidirectional text, or ensuring correct layout mirroring behavior.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# RTL
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
RTL (Right-to-Left) support ensures layouts mirror correctly for languages like Persian, Arabic, and Hebrew. In Compose, this is handled via `LayoutDirection` and logical layout modifiers. Correct RTL support requires using directional-agnostic APIs throughout — never hardcode left/right values.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Core Principles
|
|
18
|
+
|
|
19
|
+
- Always use **logical layout modifiers** — never physical (`start/end`, not `left/right`)
|
|
20
|
+
- Set `android:supportsRtl="true"` in the manifest
|
|
21
|
+
- Use `Arrangement.Start`/`Alignment.Start` — not `Left`/`Right`
|
|
22
|
+
- Mirror directional icons explicitly using `autoMirrored = true`
|
|
23
|
+
- Test in both LTR and RTL using the developer options toggle
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Manifest Setup
|
|
28
|
+
|
|
29
|
+
```xml
|
|
30
|
+
<!-- ✅ Required in AndroidManifest.xml -->
|
|
31
|
+
<application
|
|
32
|
+
android:supportsRtl="true"
|
|
33
|
+
... >
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Compose Layout Direction
|
|
39
|
+
|
|
40
|
+
```kotlin
|
|
41
|
+
// ✅ Read current layout direction
|
|
42
|
+
@Composable
|
|
43
|
+
fun DirectionAwareIcon() {
|
|
44
|
+
val layoutDirection = LocalLayoutDirection.current
|
|
45
|
+
val isRtl = layoutDirection == LayoutDirection.Rtl
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ✅ Force RTL for a subtree
|
|
49
|
+
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
|
|
50
|
+
MyLayout()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ✅ Force LTR for specific content (e.g. phone numbers, codes)
|
|
54
|
+
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
|
|
55
|
+
Text(text = "+98 912 000 0000")
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Logical Modifiers
|
|
62
|
+
|
|
63
|
+
```kotlin
|
|
64
|
+
// ✅ Use padding start/end — not left/right
|
|
65
|
+
Modifier.padding(start = 16.dp, end = 8.dp)
|
|
66
|
+
|
|
67
|
+
// ✅ Use absolutePadding only when truly position-fixed
|
|
68
|
+
Modifier.absolutePadding(left = 0.dp) // rare — only for non-mirrored content
|
|
69
|
+
|
|
70
|
+
// ✅ Row arrangement
|
|
71
|
+
Row(horizontalArrangement = Arrangement.Start) { ... } // ✅
|
|
72
|
+
Row(horizontalArrangement = Arrangement.End) { ... } // ✅
|
|
73
|
+
|
|
74
|
+
// ❌ Never use physical directions in layout
|
|
75
|
+
Modifier.padding(left = 16.dp) // breaks RTL
|
|
76
|
+
Modifier.padding(right = 8.dp) // breaks RTL
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Icon Mirroring
|
|
82
|
+
|
|
83
|
+
```kotlin
|
|
84
|
+
// ✅ Use autoMirrored for directional icons (arrows, back, forward)
|
|
85
|
+
Icon(
|
|
86
|
+
painter = painterResource(R.drawable.ic_arrow_forward),
|
|
87
|
+
contentDescription = null,
|
|
88
|
+
modifier = Modifier.scale(
|
|
89
|
+
scaleX = if (LocalLayoutDirection.current == LayoutDirection.Rtl) -1f else 1f,
|
|
90
|
+
scaleY = 1f
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
// ✅ Better — use autoMirrored in vector drawable XML
|
|
95
|
+
// In ic_arrow_forward.xml:
|
|
96
|
+
// android:autoMirrored="true"
|
|
97
|
+
|
|
98
|
+
// ✅ Icons that should NOT be mirrored
|
|
99
|
+
Icon(Icons.Default.PlayArrow, contentDescription = null) // media control — don't mirror
|
|
100
|
+
Icon(Icons.Default.Search, contentDescription = null) // symmetric — don't mirror
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Bidirectional Text
|
|
106
|
+
|
|
107
|
+
```kotlin
|
|
108
|
+
// ✅ Let the system handle bidi text automatically
|
|
109
|
+
Text(text = "Hello سلام") // system resolves direction per paragraph
|
|
110
|
+
|
|
111
|
+
// ✅ Force text direction when needed
|
|
112
|
+
Text(
|
|
113
|
+
text = userInput,
|
|
114
|
+
textDirection = TextDirection.Content // auto-detect per content
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
// ✅ Force LTR for codes, emails, URLs
|
|
118
|
+
Text(
|
|
119
|
+
text = "user@example.com",
|
|
120
|
+
textDirection = TextDirection.Ltr
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
// ✅ TextField with explicit direction for mixed input
|
|
124
|
+
TextField(
|
|
125
|
+
value = value,
|
|
126
|
+
onValueChange = onValueChange,
|
|
127
|
+
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
|
|
128
|
+
modifier = Modifier.semantics { textDirection = TextDirection.Content }
|
|
129
|
+
)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Alignment Helpers
|
|
135
|
+
|
|
136
|
+
```kotlin
|
|
137
|
+
// ✅ Alignment.Start mirrors automatically
|
|
138
|
+
Box(contentAlignment = Alignment.TopStart) { ... } // top-left in LTR, top-right in RTL
|
|
139
|
+
Box(contentAlignment = Alignment.TopEnd) { ... } // top-right in LTR, top-left in RTL
|
|
140
|
+
|
|
141
|
+
// ✅ Column horizontal alignment
|
|
142
|
+
Column(horizontalAlignment = Alignment.Start) { ... }
|
|
143
|
+
Column(horizontalAlignment = Alignment.End) { ... }
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Previewing RTL
|
|
149
|
+
|
|
150
|
+
```kotlin
|
|
151
|
+
// ✅ Always add an RTL preview alongside LTR
|
|
152
|
+
@Preview(name = "LTR", locale = "en")
|
|
153
|
+
@Preview(name = "RTL", locale = "fa")
|
|
154
|
+
@Composable
|
|
155
|
+
fun MyComponentPreview() {
|
|
156
|
+
AppTheme {
|
|
157
|
+
MyComponent()
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Anti-Patterns
|
|
165
|
+
|
|
166
|
+
- Using `Modifier.padding(left/right)` — breaks RTL mirroring
|
|
167
|
+
- Using `Arrangement.AbsoluteLeft` or `AbsoluteRight` in normal layouts
|
|
168
|
+
- Hardcoding `LayoutDirection.Ltr` at the root — prevents RTL support
|
|
169
|
+
- Not setting `android:supportsRtl="true"` in manifest
|
|
170
|
+
- Mirroring media/playback icons (play, pause, record) — they are universal
|
|
171
|
+
- Using `Column { Modifier.align(Alignment.AbsoluteLeft) }` — physical alignment
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Related Skills
|
|
176
|
+
- `compose` — Compose layout fundamentals
|
|
177
|
+
- `material3` — M3 component RTL behavior
|
|
178
|
+
- `resources` — string direction and locale resources
|
|
179
|
+
- `design-system` — design tokens with directional awareness
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
const inquirer = require("inquirer");
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* GitHub Remote Skills Repo (ZIP)
|
|
9
|
+
*/
|
|
10
|
+
const REMOTE_SKILLS_ZIP =
|
|
11
|
+
"https://github.com/mozhdehnoury-byte/android-agent-skills/archive/refs/heads/main.zip";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* ورود برنامه
|
|
15
|
+
*/
|
|
16
|
+
async function main() {
|
|
17
|
+
const args = process.argv.slice(2);
|
|
18
|
+
const command = args[0];
|
|
19
|
+
|
|
20
|
+
if (command === "init") {
|
|
21
|
+
await initRemote();
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (command === "install") {
|
|
26
|
+
const selectedSkills = await wizard(skillsPath());
|
|
27
|
+
await install(selectedSkills, skillsPath());
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
await wizard(skillsPath());
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* مسیر skills (local fallback)
|
|
36
|
+
*/
|
|
37
|
+
function skillsPath() {
|
|
38
|
+
return path.join(__dirname, "../skills");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* wizard: انتخاب skill ها
|
|
43
|
+
*/
|
|
44
|
+
async function wizard(basePath: string): Promise<Record<string, string[]>> {
|
|
45
|
+
|
|
46
|
+
const selectedSkills: Record<string, string[]> = {};
|
|
47
|
+
|
|
48
|
+
const parentFolders = fs.readdirSync(basePath);
|
|
49
|
+
|
|
50
|
+
for (const parent of parentFolders) {
|
|
51
|
+
|
|
52
|
+
const children = fs.readdirSync(
|
|
53
|
+
path.join(basePath, parent)
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const answer = await inquirer.prompt([
|
|
57
|
+
{
|
|
58
|
+
type: "checkbox",
|
|
59
|
+
name: "selected",
|
|
60
|
+
message: `Select ${parent} skills`,
|
|
61
|
+
choices: children
|
|
62
|
+
}
|
|
63
|
+
]);
|
|
64
|
+
|
|
65
|
+
selectedSkills[parent] = answer.selected;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
console.log("Selected Skills:");
|
|
69
|
+
console.log(selectedSkills);
|
|
70
|
+
|
|
71
|
+
return selectedSkills;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* install: ساخت .agent + کپی skill ها + ساخت config
|
|
76
|
+
*/
|
|
77
|
+
async function install(
|
|
78
|
+
selectedSkills: Record<string, string[]>,
|
|
79
|
+
basePath: string
|
|
80
|
+
) {
|
|
81
|
+
|
|
82
|
+
console.log("🚀 Installing Android SDD skills...");
|
|
83
|
+
|
|
84
|
+
const agentRoot = path.join(process.cwd(), ".agent");
|
|
85
|
+
fs.mkdirSync(agentRoot, { recursive: true });
|
|
86
|
+
|
|
87
|
+
for (const [parent, skills] of Object.entries(selectedSkills)) {
|
|
88
|
+
|
|
89
|
+
for (const skill of skills) {
|
|
90
|
+
|
|
91
|
+
const source = path.join(
|
|
92
|
+
basePath,
|
|
93
|
+
parent,
|
|
94
|
+
skill,
|
|
95
|
+
"SKILL.md"
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const targetDir = path.join(
|
|
99
|
+
agentRoot,
|
|
100
|
+
parent,
|
|
101
|
+
skill
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
105
|
+
|
|
106
|
+
fs.copyFileSync(
|
|
107
|
+
source,
|
|
108
|
+
path.join(targetDir, "SKILL.md")
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
console.log(`Installed: ${parent}/${skill}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const config = {
|
|
116
|
+
version: "1.0.0",
|
|
117
|
+
installedAt: new Date().toISOString(),
|
|
118
|
+
skills: selectedSkills
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
fs.writeFileSync(
|
|
122
|
+
path.join(agentRoot, "config.json"),
|
|
123
|
+
JSON.stringify(config, null, 2)
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
console.log("\n🎉 Done! Skills installed in .agent/");
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Remote init (GitHub ZIP → auto-detect folder → wizard)
|
|
131
|
+
*/
|
|
132
|
+
async function initRemote() {
|
|
133
|
+
|
|
134
|
+
console.log("🌍 Downloading skills from GitHub...");
|
|
135
|
+
|
|
136
|
+
const fetch = (await import("node-fetch")).default;
|
|
137
|
+
const unzipper = await import("unzipper");
|
|
138
|
+
|
|
139
|
+
const res = await fetch(REMOTE_SKILLS_ZIP);
|
|
140
|
+
|
|
141
|
+
const buffer = Buffer.from(await res.arrayBuffer());
|
|
142
|
+
|
|
143
|
+
const zipPath = path.join(process.cwd(), "skills.zip");
|
|
144
|
+
|
|
145
|
+
fs.writeFileSync(zipPath, buffer);
|
|
146
|
+
|
|
147
|
+
console.log("📦 Extracting skills...");
|
|
148
|
+
|
|
149
|
+
await fs.createReadStream(zipPath)
|
|
150
|
+
.pipe(unzipper.Extract({ path: process.cwd() }))
|
|
151
|
+
.promise();
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* 🔥 AUTO DETECT extracted skills path
|
|
155
|
+
* چون GitHub همیشه یک wrapper folder میسازه
|
|
156
|
+
*/
|
|
157
|
+
const extractedBase = process.cwd();
|
|
158
|
+
|
|
159
|
+
const possiblePaths = [
|
|
160
|
+
path.join(extractedBase, "android-agent-skills-main"),
|
|
161
|
+
];
|
|
162
|
+
|
|
163
|
+
let extractedPath: string | null = null;
|
|
164
|
+
|
|
165
|
+
for (const p of possiblePaths) {
|
|
166
|
+
if (fs.existsSync(p)) {
|
|
167
|
+
extractedPath = p;
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (!extractedPath) {
|
|
173
|
+
throw new Error("❌ Could not find skills folder after extraction");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
console.log("🧠 Running wizard...");
|
|
177
|
+
|
|
178
|
+
const selectedSkills = await wizard(extractedPath);
|
|
179
|
+
await install(selectedSkills, extractedPath);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
main();
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "CommonJS",
|
|
5
|
+
"moduleResolution": "Node",
|
|
6
|
+
|
|
7
|
+
"rootDir": "src",
|
|
8
|
+
"outDir": "dist",
|
|
9
|
+
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"allowSyntheticDefaultImports": true,
|
|
12
|
+
|
|
13
|
+
"types": ["node"],
|
|
14
|
+
|
|
15
|
+
"strict": false,
|
|
16
|
+
"skipLibCheck": true
|
|
17
|
+
},
|
|
18
|
+
"include": ["src"]
|
|
19
|
+
}
|