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.
Files changed (176) hide show
  1. package/dist/index.js +143 -0
  2. package/package.json +27 -0
  3. package/skills/Android Ecosystem/Baseline Profile Generator/SKILL.md +277 -0
  4. package/skills/Android Ecosystem/Glance/SKILL.md +315 -0
  5. package/skills/Android Platform/Configuration/SKILL.md +201 -0
  6. package/skills/Android Platform/Filesystem/SKILL.md +216 -0
  7. package/skills/Android Platform/Lifecycle/SKILL.md +233 -0
  8. package/skills/Android Platform/Manifest/SKILL.md +226 -0
  9. package/skills/Android Platform/Process Death Recovery/SKILL.md +214 -0
  10. package/skills/Android Platform/Resources/SKILL.md +234 -0
  11. package/skills/Android Platform/SavedStateHandle/SKILL.md +217 -0
  12. package/skills/Android Platform/State Restoration/SKILL.md +210 -0
  13. package/skills/Architecture/Bounded Context/SKILL.md +207 -0
  14. package/skills/Architecture/Clean Architecture/SKILL.md +229 -0
  15. package/skills/Architecture/Domain Modeling/SKILL.md +236 -0
  16. package/skills/Architecture/Entity Design/SKILL.md +243 -0
  17. package/skills/Architecture/Feature Isolation/SKILL.md +216 -0
  18. package/skills/Architecture/MVI/SKILL.md +224 -0
  19. package/skills/Architecture/MVVM/SKILL.md +198 -0
  20. package/skills/Architecture/Modularization/SKILL.md +194 -0
  21. package/skills/Architecture/Offline First/SKILL.md +249 -0
  22. package/skills/Architecture/Repository Pattern/SKILL.md +216 -0
  23. package/skills/Architecture/Side Effect Management/SKILL.md +278 -0
  24. package/skills/Architecture/State Management/SKILL.md +229 -0
  25. package/skills/Architecture/Unidirectional Data Flow/SKILL.md +196 -0
  26. package/skills/Architecture/Use Case Design/SKILL.md +244 -0
  27. package/skills/Architecture/Value Object/SKILL.md +226 -0
  28. package/skills/Build Infrastructure/Build Orchestration/SKILL.md +257 -0
  29. package/skills/Build Infrastructure/Dependency Compatibility Resolver/SKILL.md +259 -0
  30. package/skills/Build Infrastructure/Environment Validator/SKILL.md +311 -0
  31. package/skills/Build System/Build Cache/SKILL.md +233 -0
  32. package/skills/Build System/Build Flavor Strategy/SKILL.md +171 -0
  33. package/skills/Build System/Build Variant/SKILL.md +215 -0
  34. package/skills/Build System/Convention Plugin/SKILL.md +288 -0
  35. package/skills/Build System/Dependency Management/SKILL.md +261 -0
  36. package/skills/Build System/Gradle/SKILL.md +284 -0
  37. package/skills/Build System/Incremental Build/SKILL.md +199 -0
  38. package/skills/Build System/KAPT/SKILL.md +198 -0
  39. package/skills/Build System/KSP/SKILL.md +263 -0
  40. package/skills/Build System/Module Dependency Graph Validation/SKILL.md +223 -0
  41. package/skills/Build System/Specialized/C++/SKILL.md +308 -0
  42. package/skills/Build System/Specialized/JNI/SKILL.md +306 -0
  43. package/skills/Build System/Specialized/NDK/SKILL.md +264 -0
  44. package/skills/Build System/Version Catalog/SKILL.md +304 -0
  45. package/skills/Concurrency/Background Processing/SKILL.md +185 -0
  46. package/skills/Concurrency/Channel/SKILL.md +207 -0
  47. package/skills/Concurrency/Coroutine/SKILL.md +200 -0
  48. package/skills/Concurrency/Flow/SKILL.md +179 -0
  49. package/skills/Concurrency/Mutex Strategy/SKILL.md +185 -0
  50. package/skills/Concurrency/SharedFlow/SKILL.md +171 -0
  51. package/skills/Concurrency/StateFlow/SKILL.md +175 -0
  52. package/skills/Concurrency/Structured Concurrency/SKILL.md +197 -0
  53. package/skills/Concurrency/Synchronization Policy/SKILL.md +192 -0
  54. package/skills/Core Language/Annotation Processing/SKILL.md +224 -0
  55. package/skills/Core Language/DSL/SKILL.md +186 -0
  56. package/skills/Core Language/Extension Functions Design/SKILL.md +191 -0
  57. package/skills/Core Language/Immutability/SKILL.md +156 -0
  58. package/skills/Core Language/KMP/SKILL.md +182 -0
  59. package/skills/Core Language/Kotlin/SKILL.md +187 -0
  60. package/skills/Core Language/Reactive State Management/SKILL.md +228 -0
  61. package/skills/Core Language/Reactive Streams/SKILL.md +235 -0
  62. package/skills/Core Language/Serialization/SKILL.md +191 -0
  63. package/skills/Data Layer/Cache Strategy/SKILL.md +261 -0
  64. package/skills/Data Layer/Conflict Resolution/SKILL.md +248 -0
  65. package/skills/Data Layer/DAO/SKILL.md +225 -0
  66. package/skills/Data Layer/DTO Mapping/SKILL.md +269 -0
  67. package/skills/Data Layer/DataStore/SKILL.md +264 -0
  68. package/skills/Data Layer/Database Versioning Strategy/SKILL.md +215 -0
  69. package/skills/Data Layer/Encrypted Database/SKILL.md +212 -0
  70. package/skills/Data Layer/File Storage/SKILL.md +247 -0
  71. package/skills/Data Layer/Indexing/SKILL.md +184 -0
  72. package/skills/Data Layer/Key-Value Store Strategy/SKILL.md +185 -0
  73. package/skills/Data Layer/Merge Strategy/SKILL.md +240 -0
  74. package/skills/Data Layer/Migration/SKILL.md +243 -0
  75. package/skills/Data Layer/Paging/SKILL.md +264 -0
  76. package/skills/Data Layer/Proto DataStore/SKILL.md +250 -0
  77. package/skills/Data Layer/Room/SKILL.md +244 -0
  78. package/skills/Data Layer/SQLite/SKILL.md +255 -0
  79. package/skills/Data Layer/Sync Engine/SKILL.md +268 -0
  80. package/skills/Dependency Injection/Dagger/SKILL.md +283 -0
  81. package/skills/Dependency Injection/Hilt/SKILL.md +345 -0
  82. package/skills/Dependency Injection/Koin/SKILL.md +282 -0
  83. package/skills/Developer Experience/Detekt/SKILL.md +272 -0
  84. package/skills/Developer Experience/Lint Rule/SKILL.md +281 -0
  85. package/skills/Google Ecosystem/Analytics/SKILL.md +281 -0
  86. package/skills/Google Ecosystem/Crashlytics/SKILL.md +234 -0
  87. package/skills/Google Ecosystem/Firebase/SKILL.md +200 -0
  88. package/skills/Google Ecosystem/Firebase Messaging/SKILL.md +266 -0
  89. package/skills/Media/Audio/SKILL.md +257 -0
  90. package/skills/Media/Camera/SKILL.md +229 -0
  91. package/skills/Media/CameraX/SKILL.md +295 -0
  92. package/skills/Media/ExoPlayer/SKILL.md +258 -0
  93. package/skills/Media/Video/SKILL.md +228 -0
  94. package/skills/Meta Skills/Domain Error Model/SKILL.md +238 -0
  95. package/skills/Meta Skills/Error Handling/SKILL.md +255 -0
  96. package/skills/Meta Skills/Error Mapping/SKILL.md +232 -0
  97. package/skills/Meta Skills/Failure Strategy/SKILL.md +294 -0
  98. package/skills/Meta Skills/Migration Strategy/SKILL.md +305 -0
  99. package/skills/Meta Skills/User Friendly Errors/SKILL.md +334 -0
  100. package/skills/Navigation/Deep Navigation/SKILL.md +209 -0
  101. package/skills/Navigation/Navigation/SKILL.md +215 -0
  102. package/skills/Navigation/Nested Navigation/SKILL.md +214 -0
  103. package/skills/Networking/API Contract/SKILL.md +220 -0
  104. package/skills/Networking/Authentication/SKILL.md +210 -0
  105. package/skills/Networking/Certificate Pinning/SKILL.md +167 -0
  106. package/skills/Networking/Fallback Strategy/SKILL.md +182 -0
  107. package/skills/Networking/Ktor/SKILL.md +219 -0
  108. package/skills/Networking/Multipart Upload/SKILL.md +213 -0
  109. package/skills/Networking/OkHttp/SKILL.md +193 -0
  110. package/skills/Networking/REST/SKILL.md +178 -0
  111. package/skills/Networking/Rate Limiting/SKILL.md +170 -0
  112. package/skills/Networking/Retrofit/SKILL.md +241 -0
  113. package/skills/Networking/Retry-Backoff/SKILL.md +181 -0
  114. package/skills/Networking/Server-Sent Events (SSE)/SKILL.md +196 -0
  115. package/skills/Networking/WebSocket/SKILL.md +224 -0
  116. package/skills/Observability/Crash Reporting/SKILL.md +219 -0
  117. package/skills/Observability/Logging/SKILL.md +168 -0
  118. package/skills/Observability/Metrics/SKILL.md +227 -0
  119. package/skills/Observability/Structured Logging/SKILL.md +234 -0
  120. package/skills/Performance/ANR Prevention/SKILL.md +192 -0
  121. package/skills/Performance/Allocation Optimization/SKILL.md +179 -0
  122. package/skills/Performance/App Startup/SKILL.md +183 -0
  123. package/skills/Performance/Baseline Profile/SKILL.md +205 -0
  124. package/skills/Performance/Battery Optimization/SKILL.md +192 -0
  125. package/skills/Performance/Benchmark/SKILL.md +182 -0
  126. package/skills/Performance/Bitmap Optimization/SKILL.md +178 -0
  127. package/skills/Performance/Compose Optimization/SKILL.md +187 -0
  128. package/skills/Performance/Heap Management/SKILL.md +184 -0
  129. package/skills/Performance/Macrobenchmark/SKILL.md +214 -0
  130. package/skills/Performance/Memory Leak Prevention/SKILL.md +218 -0
  131. package/skills/Performance/Rendering Performance/SKILL.md +205 -0
  132. package/skills/Performance/Startup Optimization/SKILL.md +219 -0
  133. package/skills/Security/Biometric/SKILL.md +224 -0
  134. package/skills/Security/Certificate Transparency/SKILL.md +158 -0
  135. package/skills/Security/Cryptography/SKILL.md +244 -0
  136. package/skills/Security/Encrypted Storage/SKILL.md +273 -0
  137. package/skills/Security/Frida Detection/SKILL.md +230 -0
  138. package/skills/Security/Hook Detection/SKILL.md +197 -0
  139. package/skills/Security/Keystore/SKILL.md +272 -0
  140. package/skills/Security/Network Security Config/SKILL.md +186 -0
  141. package/skills/Security/Obfuscation/SKILL.md +226 -0
  142. package/skills/Security/Proguard/SKILL.md +202 -0
  143. package/skills/Security/R8/SKILL.md +234 -0
  144. package/skills/Security/Reverse Engineering Resistance/SKILL.md +267 -0
  145. package/skills/Security/Root Detection/SKILL.md +220 -0
  146. package/skills/Security/Secure Networking/SKILL.md +220 -0
  147. package/skills/System Integration/AlarmManager/SKILL.md +182 -0
  148. package/skills/System Integration/App Widget/SKILL.md +182 -0
  149. package/skills/System Integration/Deep Link/SKILL.md +187 -0
  150. package/skills/System Integration/Foreground Service/SKILL.md +212 -0
  151. package/skills/System Integration/Notification/SKILL.md +237 -0
  152. package/skills/System Integration/WorkManager/SKILL.md +256 -0
  153. package/skills/System Integration/clipboard/SKILL.md +155 -0
  154. package/skills/System Integration/share-intent/SKILL.md +182 -0
  155. package/skills/Testing/Compose Testing/SKILL.md +296 -0
  156. package/skills/Testing/Espresso/SKILL.md +292 -0
  157. package/skills/Testing/Fake Data/SKILL.md +245 -0
  158. package/skills/Testing/Integration Testing/SKILL.md +288 -0
  159. package/skills/Testing/Mocking/SKILL.md +229 -0
  160. package/skills/Testing/Snapshot Testing/SKILL.md +259 -0
  161. package/skills/Testing/UI Testing/SKILL.md +293 -0
  162. package/skills/Testing/Unit Testing/SKILL.md +309 -0
  163. package/skills/UI System/Bottom Sheet Patterns/SKILL.md +279 -0
  164. package/skills/UI System/Compose/SKILL.md +296 -0
  165. package/skills/UI System/Compose Animation/SKILL.md +281 -0
  166. package/skills/UI System/Compose Multiplatform/SKILL.md +261 -0
  167. package/skills/UI System/Compose Navigation/SKILL.md +255 -0
  168. package/skills/UI System/Compose Performance/SKILL.md +274 -0
  169. package/skills/UI System/Design System/SKILL.md +217 -0
  170. package/skills/UI System/Empty State Strategy/SKILL.md +208 -0
  171. package/skills/UI System/Keyboard Navigation/SKILL.md +214 -0
  172. package/skills/UI System/Loading Strategy/SKILL.md +254 -0
  173. package/skills/UI System/Material 3/SKILL.md +279 -0
  174. package/skills/UI System/RTL/SKILL.md +179 -0
  175. package/src/index.ts +182 -0
  176. package/tsconfig.json +19 -0
@@ -0,0 +1,214 @@
1
+ ---
2
+ name: keyboard-navigation
3
+ description: >
4
+ Keyboard and IME (soft keyboard) navigation in Android/Compose.
5
+ Load this skill when managing focus traversal between fields, handling
6
+ IME actions (Next, Done, Search), avoiding content hidden behind the
7
+ keyboard, managing keyboard visibility, or supporting hardware keyboards.
8
+ ---
9
+
10
+ # Keyboard Navigation
11
+
12
+ ## Overview
13
+ Keyboard navigation covers two concerns: soft keyboard (IME) management for touch devices, and hardware keyboard support for accessibility and productivity. Both require explicit focus management, correct IME action wiring, and ensuring content remains accessible when the keyboard is visible.
14
+
15
+ ---
16
+
17
+ ## Core Principles
18
+
19
+ - Always set `imeAction` to match the field's role (`Next` for mid-form, `Done`/`Search` for last)
20
+ - Never let the keyboard obscure active input fields — use `imePadding()` or `imeNestedScroll()`
21
+ - Use `FocusRequester` for programmatic focus — never simulate clicks
22
+ - Form fields must chain focus in logical order — left-to-right, top-to-bottom (LTR) or right-to-left (RTL)
23
+ - Request focus on first field only after the screen is fully composed
24
+
25
+ ---
26
+
27
+ ## IME Actions
28
+
29
+ ```kotlin
30
+ // ✅ Chain fields with Next — last field uses Done/Search/Send
31
+ @Composable
32
+ fun LoginForm(onSubmit: () -> Unit) {
33
+ val focusManager = LocalFocusManager.current
34
+ var email by remember { mutableStateOf("") }
35
+ var password by remember { mutableStateOf("") }
36
+
37
+ Column(verticalArrangement = Arrangement.spacedBy(AppSpacing.md)) {
38
+ OutlinedTextField(
39
+ value = email,
40
+ onValueChange = { email = it },
41
+ label = { Text("Email") },
42
+ keyboardOptions = KeyboardOptions(
43
+ keyboardType = KeyboardType.Email,
44
+ imeAction = ImeAction.Next // ✅ moves focus to next field
45
+ ),
46
+ keyboardActions = KeyboardActions(
47
+ onNext = { focusManager.moveFocus(FocusDirection.Down) }
48
+ ),
49
+ singleLine = true
50
+ )
51
+
52
+ OutlinedTextField(
53
+ value = password,
54
+ onValueChange = { password = it },
55
+ label = { Text("Password") },
56
+ visualTransformation = PasswordVisualTransformation(),
57
+ keyboardOptions = KeyboardOptions(
58
+ keyboardType = KeyboardType.Password,
59
+ imeAction = ImeAction.Done // ✅ last field — submit
60
+ ),
61
+ keyboardActions = KeyboardActions(
62
+ onDone = {
63
+ focusManager.clearFocus()
64
+ onSubmit()
65
+ }
66
+ ),
67
+ singleLine = true
68
+ )
69
+ }
70
+ }
71
+ ```
72
+
73
+ ---
74
+
75
+ ## FocusRequester
76
+
77
+ ```kotlin
78
+ // ✅ Programmatic focus after screen appears
79
+ @Composable
80
+ fun SearchScreen() {
81
+ val focusRequester = remember { FocusRequester() }
82
+
83
+ OutlinedTextField(
84
+ modifier = Modifier.focusRequester(focusRequester),
85
+ value = query,
86
+ onValueChange = { query = it },
87
+ keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
88
+ keyboardActions = KeyboardActions(onSearch = { onSearch(query) })
89
+ )
90
+
91
+ LaunchedEffect(Unit) {
92
+ focusRequester.requestFocus() // ✅ focus after composition
93
+ }
94
+ }
95
+
96
+ // ✅ Chaining focus manually across fields
97
+ @Composable
98
+ fun MultiFieldForm() {
99
+ val (firstFocus, secondFocus, thirdFocus) = remember { FocusRequester.createRefs() }
100
+
101
+ OutlinedTextField(
102
+ modifier = Modifier.focusRequester(firstFocus),
103
+ keyboardActions = KeyboardActions(onNext = { secondFocus.requestFocus() }),
104
+ keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
105
+ ...
106
+ )
107
+ OutlinedTextField(
108
+ modifier = Modifier.focusRequester(secondFocus),
109
+ keyboardActions = KeyboardActions(onNext = { thirdFocus.requestFocus() }),
110
+ keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
111
+ ...
112
+ )
113
+ OutlinedTextField(
114
+ modifier = Modifier.focusRequester(thirdFocus),
115
+ keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
116
+ ...
117
+ )
118
+ }
119
+ ```
120
+
121
+ ---
122
+
123
+ ## Avoiding Content Hidden Behind Keyboard
124
+
125
+ ```kotlin
126
+ // ✅ imePadding on scrollable containers
127
+ @Composable
128
+ fun FormScreen() {
129
+ Column(
130
+ modifier = Modifier
131
+ .fillMaxSize()
132
+ .verticalScroll(rememberScrollState())
133
+ .imePadding() // ✅ pushes content above keyboard
134
+ .padding(AppSpacing.md)
135
+ ) {
136
+ // form fields
137
+ }
138
+ }
139
+
140
+ // ✅ WindowInsets approach in Scaffold
141
+ Scaffold(
142
+ modifier = Modifier.imePadding()
143
+ ) { padding ->
144
+ Column(modifier = Modifier.padding(padding)) { ... }
145
+ }
146
+
147
+ // ✅ For LazyColumn — imeNestedScroll + imePadding
148
+ LazyColumn(
149
+ modifier = Modifier
150
+ .fillMaxSize()
151
+ .imePadding()
152
+ .imeNestedScroll()
153
+ ) { ... }
154
+ ```
155
+
156
+ ---
157
+
158
+ ## Keyboard Visibility Control
159
+
160
+ ```kotlin
161
+ // ✅ Show keyboard programmatically (Compose)
162
+ val keyboardController = LocalSoftwareKeyboardController.current
163
+
164
+ Button(onClick = { keyboardController?.show() }) { Text("Open Keyboard") }
165
+ Button(onClick = { keyboardController?.hide() }) { Text("Close Keyboard") }
166
+
167
+ // ✅ Hide keyboard on outside tap
168
+ @Composable
169
+ fun DismissKeyboardOnTap(content: @Composable () -> Unit) {
170
+ val focusManager = LocalFocusManager.current
171
+ Box(
172
+ modifier = Modifier
173
+ .fillMaxSize()
174
+ .pointerInput(Unit) {
175
+ detectTapGestures(onTap = { focusManager.clearFocus() })
176
+ }
177
+ ) {
178
+ content()
179
+ }
180
+ }
181
+ ```
182
+
183
+ ---
184
+
185
+ ## Keyboard Type Reference
186
+
187
+ | KeyboardType | Use For |
188
+ |---|---|
189
+ | `Text` | General text input |
190
+ | `Email` | Email addresses |
191
+ | `Number` | Numeric only |
192
+ | `Phone` | Phone numbers |
193
+ | `Password` | Password (masked) |
194
+ | `NumberPassword` | PIN / numeric password |
195
+ | `Uri` | URLs |
196
+ | `Decimal` | Decimal numbers |
197
+
198
+ ---
199
+
200
+ ## Anti-Patterns
201
+
202
+ - Not setting `imeAction` — keyboard shows a random action button
203
+ - Using `ImeAction.Done` on non-last fields — breaks tab/next navigation
204
+ - Not adding `imePadding()` to scrollable forms — keyboard hides the active field
205
+ - Calling `requestFocus()` outside `LaunchedEffect` — crashes before composition completes
206
+ - Using `WindowManager.hideSoftInputFromWindow()` directly — deprecated, use `SoftwareKeyboardController`
207
+ - Setting `keyboardType = KeyboardType.Number` for phone numbers — use `Phone` type instead
208
+
209
+ ---
210
+
211
+ ## Related Skills
212
+ - `compose` — Compose fundamentals and Modifier system
213
+ - `bottom-sheet-pattern` — keyboard interaction with bottom sheets
214
+ - `accessibility` — focus order and hardware keyboard support
@@ -0,0 +1,254 @@
1
+ ---
2
+ name: loading-strategy
3
+ description: >
4
+ Strategies for showing loading states in Android/Compose applications.
5
+ Load this skill when implementing skeleton screens, shimmer effects,
6
+ pull-to-refresh, progressive loading, pagination loading indicators,
7
+ or deciding between spinner vs skeleton vs placeholder approaches.
8
+ ---
9
+
10
+ # Loading Strategy
11
+
12
+ ## Overview
13
+ Loading strategy defines how the UI communicates data-fetching progress to the user. The choice between spinner, skeleton, shimmer, and placeholder affects perceived performance significantly. A consistent, well-defined loading strategy prevents layout shifts and provides a smoother user experience.
14
+
15
+ ---
16
+
17
+ ## Core Principles
18
+
19
+ - Use **skeleton/shimmer** for initial content load — never a full-screen spinner
20
+ - Use **inline spinner** for actions (button submit, pull-to-refresh)
21
+ - Never block the entire screen with a loading overlay unless the action is destructive and non-cancellable
22
+ - Loading state must be part of the UI state model — never a separate boolean flag
23
+ - Skeleton dimensions must match the real content dimensions to prevent layout shift
24
+
25
+ ---
26
+
27
+ ## UI State Model
28
+
29
+ ```kotlin
30
+ // ✅ Loading is part of a sealed state — not a separate flag
31
+ sealed interface UiState<out T> {
32
+ data object Loading : UiState<Nothing>
33
+ data class Success<T>(val data: T) : UiState<T>
34
+ data class Error(val message: String) : UiState<Nothing>
35
+ }
36
+
37
+ // ✅ In ViewModel
38
+ class UserListViewModel : ViewModel() {
39
+ private val _state = MutableStateFlow<UiState<List<User>>>(UiState.Loading)
40
+ val state: StateFlow<UiState<List<User>>> = _state.asStateFlow()
41
+
42
+ fun loadUsers() {
43
+ viewModelScope.launch {
44
+ _state.value = UiState.Loading
45
+ _state.value = repository.getUsers().fold(
46
+ onSuccess = { UiState.Success(it) },
47
+ onFailure = { UiState.Error(it.message ?: "Unknown error") }
48
+ )
49
+ }
50
+ }
51
+ }
52
+ ```
53
+
54
+ ---
55
+
56
+ ## Skeleton Screen
57
+
58
+ ```kotlin
59
+ // ✅ Skeleton matches real item dimensions
60
+ @Composable
61
+ fun UserItemSkeleton(modifier: Modifier = Modifier) {
62
+ Row(
63
+ modifier = modifier
64
+ .fillMaxWidth()
65
+ .padding(horizontal = AppSpacing.md, vertical = AppSpacing.sm),
66
+ horizontalArrangement = Arrangement.spacedBy(AppSpacing.md)
67
+ ) {
68
+ // Avatar placeholder
69
+ Box(
70
+ modifier = Modifier
71
+ .size(40.dp)
72
+ .clip(CircleShape)
73
+ .shimmer()
74
+ )
75
+ Column(verticalArrangement = Arrangement.spacedBy(AppSpacing.xs)) {
76
+ // Name placeholder
77
+ Box(
78
+ modifier = Modifier
79
+ .fillMaxWidth(0.5f)
80
+ .height(16.dp)
81
+ .clip(MaterialTheme.shapes.small)
82
+ .shimmer()
83
+ )
84
+ // Subtitle placeholder
85
+ Box(
86
+ modifier = Modifier
87
+ .fillMaxWidth(0.35f)
88
+ .height(12.dp)
89
+ .clip(MaterialTheme.shapes.small)
90
+ .shimmer()
91
+ )
92
+ }
93
+ }
94
+ }
95
+
96
+ // ✅ Shimmer modifier using animated brush
97
+ @Composable
98
+ fun Modifier.shimmer(): Modifier {
99
+ val transition = rememberInfiniteTransition(label = "shimmer")
100
+ val translateAnim by transition.animateFloat(
101
+ initialValue = 0f,
102
+ targetValue = 1000f,
103
+ animationSpec = infiniteRepeatable(
104
+ animation = tween(durationMillis = 1200, easing = LinearEasing),
105
+ repeatMode = RepeatMode.Restart
106
+ ),
107
+ label = "shimmer_translate"
108
+ )
109
+ val brush = Brush.linearGradient(
110
+ colors = listOf(
111
+ MaterialTheme.colorScheme.surfaceVariant,
112
+ MaterialTheme.colorScheme.surface,
113
+ MaterialTheme.colorScheme.surfaceVariant
114
+ ),
115
+ start = Offset(translateAnim - 200f, 0f),
116
+ end = Offset(translateAnim, 0f)
117
+ )
118
+ return this.background(brush)
119
+ }
120
+ ```
121
+
122
+ ---
123
+
124
+ ## List Loading
125
+
126
+ ```kotlin
127
+ // ✅ Show skeleton list during initial load
128
+ @Composable
129
+ fun UserListScreen(viewModel: UserListViewModel = hiltViewModel()) {
130
+ val state by viewModel.state.collectAsStateWithLifecycle()
131
+
132
+ when (state) {
133
+ is UiState.Loading -> {
134
+ LazyColumn {
135
+ items(6) { UserItemSkeleton() } // fixed count matches expected content
136
+ }
137
+ }
138
+ is UiState.Success -> {
139
+ LazyColumn {
140
+ items((state as UiState.Success).data) { user ->
141
+ UserItem(user)
142
+ }
143
+ }
144
+ }
145
+ is UiState.Error -> {
146
+ ErrorState(message = (state as UiState.Error).message)
147
+ }
148
+ }
149
+ }
150
+ ```
151
+
152
+ ---
153
+
154
+ ## Pull-to-Refresh
155
+
156
+ ```kotlin
157
+ // ✅ Pull-to-refresh with PullToRefreshBox (M3)
158
+ @Composable
159
+ fun UserListScreen(viewModel: UserListViewModel = hiltViewModel()) {
160
+ val state by viewModel.state.collectAsStateWithLifecycle()
161
+ val isRefreshing = state is UiState.Loading && viewModel.isRefreshing
162
+
163
+ PullToRefreshBox(
164
+ isRefreshing = isRefreshing,
165
+ onRefresh = viewModel::refresh
166
+ ) {
167
+ LazyColumn { ... }
168
+ }
169
+ }
170
+ ```
171
+
172
+ ---
173
+
174
+ ## Button Loading State
175
+
176
+ ```kotlin
177
+ // ✅ Inline loading indicator in action button
178
+ @Composable
179
+ fun SubmitButton(
180
+ isLoading: Boolean,
181
+ onClick: () -> Unit
182
+ ) {
183
+ Button(
184
+ onClick = onClick,
185
+ enabled = !isLoading
186
+ ) {
187
+ AnimatedContent(targetState = isLoading, label = "submit_btn") { loading ->
188
+ if (loading) {
189
+ CircularProgressIndicator(
190
+ modifier = Modifier.size(18.dp),
191
+ strokeWidth = 2.dp,
192
+ color = MaterialTheme.colorScheme.onPrimary
193
+ )
194
+ } else {
195
+ Text("Submit")
196
+ }
197
+ }
198
+ }
199
+ }
200
+ ```
201
+
202
+ ---
203
+
204
+ ## Pagination Loading
205
+
206
+ ```kotlin
207
+ // ✅ Footer loading indicator for paginated lists
208
+ @Composable
209
+ fun PaginatedList(
210
+ items: List<Item>,
211
+ isLoadingMore: Boolean,
212
+ onLoadMore: () -> Unit
213
+ ) {
214
+ LazyColumn {
215
+ items(items) { ItemRow(it) }
216
+
217
+ if (isLoadingMore) {
218
+ item {
219
+ Box(
220
+ modifier = Modifier
221
+ .fillMaxWidth()
222
+ .padding(AppSpacing.md),
223
+ contentAlignment = Alignment.Center
224
+ ) {
225
+ CircularProgressIndicator(modifier = Modifier.size(24.dp))
226
+ }
227
+ }
228
+ }
229
+ }
230
+
231
+ // Trigger load more when near end
232
+ LaunchedEffect(items.size) {
233
+ if (items.isNotEmpty()) onLoadMore()
234
+ }
235
+ }
236
+ ```
237
+
238
+ ---
239
+
240
+ ## Anti-Patterns
241
+
242
+ - Full-screen `CircularProgressIndicator` for content loading — use skeleton instead
243
+ - Using a separate `isLoading: Boolean` flag alongside data state — use sealed state
244
+ - Skeleton with wrong dimensions — causes layout shift when content appears
245
+ - Not disabling interactive elements during loading — allows double submissions
246
+ - Showing spinner on every recomposition — tie to actual async state only
247
+
248
+ ---
249
+
250
+ ## Related Skills
251
+ - `empty-state-strategy` — what to show when loading succeeds but data is empty
252
+ - `state-management` — UI state modeling
253
+ - `compose` — animation and layout fundamentals
254
+ - `paging` — pagination with Paging 3 library