@shaykec/app-agent 1.0.8 → 1.0.10

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 (213) hide show
  1. package/.claude/agents/android-customizer.md +9 -1
  2. package/.claude/agents/catalog-analyzer.md +57 -0
  3. package/.claude/agents/ios-customizer.md +9 -1
  4. package/.claude/agents/react-native-customizer.md +71 -0
  5. package/.claude/skills/android-customizer/SKILL.md +108 -23
  6. package/.claude/skills/bug-fixer/SKILL.md +59 -0
  7. package/.claude/skills/catalog-analyzer/SKILL.md +96 -0
  8. package/.claude/skills/customization-planner/SKILL.md +44 -5
  9. package/.claude/skills/design-selector/SKILL.md +3 -1
  10. package/.claude/skills/design-system/SKILL.md +1 -1
  11. package/.claude/skills/exploratory-tester/SKILL.md +82 -0
  12. package/.claude/skills/ios-customizer/SKILL.md +123 -23
  13. package/.claude/skills/module-integrator/SKILL.md +1 -1
  14. package/.claude/skills/react-native-customizer/SKILL.md +97 -11
  15. package/.claude/skills/test-planner/SKILL.md +72 -0
  16. package/.cursor/agents/README.md +3 -1
  17. package/.cursor/agents/android-customizer.md +15 -11
  18. package/.cursor/agents/catalog-analyzer.md +83 -0
  19. package/.cursor/agents/ios-customizer.md +15 -10
  20. package/.cursor/agents/react-native-customizer.md +170 -0
  21. package/.cursor/mcp.json +2 -10
  22. package/.cursor/rules/safety-guardrails.mdc +1 -1
  23. package/.cursor/rules/workflow.mdc +52 -18
  24. package/.cursor/skills/android-customizer/SKILL.md +46 -22
  25. package/.cursor/skills/bug-fixer/SKILL.md +189 -0
  26. package/.cursor/skills/catalog-analyzer/SKILL.md +222 -0
  27. package/.cursor/skills/customization-planner/SKILL.md +55 -8
  28. package/.cursor/skills/design-selector/SKILL.md +6 -5
  29. package/.cursor/skills/design-system/SKILL.md +8 -7
  30. package/.cursor/skills/exploratory-tester/SKILL.md +223 -0
  31. package/.cursor/skills/ios-customizer/SKILL.md +50 -15
  32. package/.cursor/skills/module-integrator/SKILL.md +2 -2
  33. package/.cursor/skills/output-validator/SKILL.md +1 -1
  34. package/.cursor/skills/react-native-customizer/SKILL.md +115 -25
  35. package/.cursor/skills/test-planner/SKILL.md +199 -0
  36. package/AGENTS.md +32 -11
  37. package/CLAUDE.md +78 -33
  38. package/README.md +77 -11
  39. package/designs/DESIGN_CATALOG.md +17 -15
  40. package/designs/DESIGN_PRINCIPLES.md +53 -0
  41. package/designs/brands/accessible-high-contrast.md +14 -0
  42. package/designs/brands/corporate-professional.md +14 -0
  43. package/designs/brands/dark-luxe.md +14 -0
  44. package/designs/brands/kids-playful.md +14 -0
  45. package/designs/brands/medical-clinical.md +14 -0
  46. package/designs/brands/modern-minimal.md +14 -0
  47. package/designs/brands/nature-organic.md +14 -0
  48. package/designs/brands/neo-brutalist.md +14 -0
  49. package/designs/brands/retro-vintage.md +14 -0
  50. package/designs/brands/soft-gradient.md +14 -0
  51. package/designs/brands/sport-athletic.md +14 -0
  52. package/designs/brands/tech-dynamic.md +14 -0
  53. package/designs/brands/vibrant-playful.md +14 -0
  54. package/dist/cli.d.ts +4 -2
  55. package/dist/cli.d.ts.map +1 -1
  56. package/dist/cli.js +91 -1
  57. package/dist/cli.js.map +1 -1
  58. package/dist/config.d.ts +2 -0
  59. package/dist/config.d.ts.map +1 -1
  60. package/dist/config.js +2 -0
  61. package/dist/config.js.map +1 -1
  62. package/dist/engines/claude-engine.d.ts.map +1 -1
  63. package/dist/engines/claude-engine.js +16 -4
  64. package/dist/engines/claude-engine.js.map +1 -1
  65. package/dist/engines/types.d.ts +1 -1
  66. package/dist/engines/types.d.ts.map +1 -1
  67. package/dist/engines/types.js +31 -2
  68. package/dist/engines/types.js.map +1 -1
  69. package/dist/github.d.ts +3 -0
  70. package/dist/github.d.ts.map +1 -1
  71. package/dist/github.js +47 -4
  72. package/dist/github.js.map +1 -1
  73. package/dist/index.js +217 -9
  74. package/dist/index.js.map +1 -1
  75. package/dist/prompt-builder.d.ts +11 -1
  76. package/dist/prompt-builder.d.ts.map +1 -1
  77. package/dist/prompt-builder.js +216 -1
  78. package/dist/prompt-builder.js.map +1 -1
  79. package/dist/validator.d.ts +7 -2
  80. package/dist/validator.d.ts.map +1 -1
  81. package/dist/validator.js +61 -41
  82. package/dist/validator.js.map +1 -1
  83. package/dist/workspace.js +2 -2
  84. package/dist/workspace.js.map +1 -1
  85. package/package.json +2 -2
  86. package/prompts/agent-prompt.md +35 -18
  87. package/prompts/deep-test-agent-prompt.md +122 -0
  88. package/prompts/fix-agent-prompt.md +90 -0
  89. package/prompts/quick-agent-prompt.md +32 -2
  90. package/prompts/scratch-agent-prompt.md +5 -8
  91. package/templates/android/BookTemplate/app/src/main/kotlin/com/appship/book/core/animation/AnimatedTransitionsModifiers.kt +188 -0
  92. package/templates/android/ChatTemplate/app/src/main/kotlin/com/appship/chat/core/animation/AnimatedTransitionsModifiers.kt +188 -0
  93. package/templates/android/ChatTemplate/app/src/main/kotlin/com/appship/chat/features/conversations/ConversationsScreen.kt +1 -1
  94. package/templates/android/DashTemplate/app/src/main/kotlin/com/appship/dash/core/animation/AnimatedTransitionsModifiers.kt +188 -0
  95. package/templates/android/DashTemplate/app/src/main/kotlin/com/appship/dash/features/navigation/MainScreen.kt +1 -0
  96. package/templates/android/FamilyTemplate/app/src/main/java/com/appship/family/core/animation/AnimatedTransitionsModifiers.kt +188 -0
  97. package/templates/android/FamilyTemplate/app/src/main/java/com/appship/family/features/navigation/MainNavigation.kt +5 -1
  98. package/templates/android/FinanceTemplate/app/src/main/kotlin/com/appship/finance/core/animation/AnimatedTransitionsModifiers.kt +188 -0
  99. package/templates/android/GameTemplate/app/src/main/kotlin/com/appship/game/core/animation/AnimatedTransitionsModifiers.kt +188 -0
  100. package/templates/android/GameTemplate/app/src/main/kotlin/com/appship/game/core/animation/MotionPreferencesScreen.kt +3 -3
  101. package/templates/android/GameTemplate/app/src/main/kotlin/com/appship/game/features/navigation/Navigation.kt +1 -1
  102. package/templates/android/GameTemplate/app/src/main/kotlin/com/appship/game/features/settings/SettingsScreen.kt +1 -1
  103. package/templates/android/HealthTemplate/app/src/main/kotlin/com/appship/health/core/animation/AnimatedTransitionsModifiers.kt +188 -0
  104. package/templates/android/LearnTemplate/app/src/main/kotlin/com/appship/learn/core/animation/AnimatedTransitionsModifiers.kt +188 -0
  105. package/templates/android/MapTemplate/app/src/main/kotlin/com/appship/map/core/animation/AnimatedTransitionsModifiers.kt +188 -0
  106. package/templates/android/MediaTemplate/app/src/main/kotlin/com/appship/media/core/animation/AnimatedTransitionsModifiers.kt +188 -0
  107. package/templates/android/MediaTemplate/app/src/main/kotlin/com/appship/media/features/settings/SettingsScreen.kt +3 -2
  108. package/templates/android/ReferenceTemplate/app/src/main/kotlin/com/appship/reference/core/animation/AnimatedTransitionsModifiers.kt +188 -0
  109. package/templates/android/ReferenceTemplate/app/src/main/kotlin/com/appship/reference/features/settings/SettingsScreen.kt +1 -1
  110. package/templates/android/ShopTemplate/app/src/main/kotlin/com/appship/shop/core/animation/AnimatedTransitionsModifiers.kt +188 -0
  111. package/templates/android/ShopTemplate/app/src/main/kotlin/com/appship/shop/features/cart/CartScreen.kt +3 -2
  112. package/templates/android/Skeleton/TESTING_MANIFEST.md +2 -1
  113. package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/MainActivity.kt +23 -2
  114. package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/core/animation/AnimatedTransitionsModifiers.kt +188 -0
  115. package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/core/theme/AppearanceManager.kt +42 -0
  116. package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/features/profile/ProfileScreen.kt +20 -8
  117. package/templates/android/Skeleton/tests/03_detail_screen.yaml +3 -2
  118. package/templates/android/Skeleton/tests/04_favorites.yaml +3 -2
  119. package/templates/android/Skeleton/tests/08_full_e2e.yaml +9 -2
  120. package/templates/android/Skeleton/tests/09_dark_mode.yaml +50 -0
  121. package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/core/animation/AnimatedTransitionsModifiers.kt +188 -0
  122. package/templates/android/TaskTemplate/app/src/main/kotlin/com/appship/task/core/animation/AnimatedTransitionsModifiers.kt +188 -0
  123. package/templates/android/TaskTemplate/app/src/main/kotlin/com/appship/task/features/settings/SettingsScreen.kt +3 -2
  124. package/templates/android/TrackTemplate/app/src/main/kotlin/com/appship/track/core/animation/AnimatedTransitionsModifiers.kt +188 -0
  125. package/templates/ios/BookTemplate/BookTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
  126. package/templates/ios/ChatTemplate/ChatTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
  127. package/templates/ios/DashTemplate/DashTemplate/App/AppConfig.swift +1 -0
  128. package/templates/ios/DashTemplate/DashTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
  129. package/templates/ios/DashTemplate/DashTemplate/Core/Strings.swift +13 -0
  130. package/templates/ios/DashTemplate/DashTemplate.xcodeproj/project.pbxproj +32 -20
  131. package/templates/ios/FamilyTemplate/FamilyTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
  132. package/templates/ios/FinanceTemplate/FinanceTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
  133. package/templates/ios/FinanceTemplate/FinanceTemplate/Core/Strings.swift +42 -0
  134. package/templates/ios/FinanceTemplate/FinanceTemplate.xcodeproj/project.pbxproj +36 -30
  135. package/templates/ios/GameTemplate/GameTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
  136. package/templates/ios/HealthTemplate/HealthTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
  137. package/templates/ios/LearnTemplate/LearnTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
  138. package/templates/ios/MapTemplate/MapTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
  139. package/templates/ios/MediaTemplate/MediaTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
  140. package/templates/ios/ReferenceTemplate/ReferenceTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
  141. package/templates/ios/ReferenceTemplate/ReferenceTemplate/Core/Strings.swift +12 -0
  142. package/templates/ios/ReferenceTemplate/ReferenceTemplate/Features/SkeletonLoading/SkeletonLoadingView.swift +2 -37
  143. package/templates/ios/ShopTemplate/ShopTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
  144. package/templates/ios/Skeleton/Skeleton/Core/Animation/AnimatedTransitionsView.swift +201 -0
  145. package/templates/ios/Skeleton/tests/08_full_e2e.yaml +4 -0
  146. package/templates/ios/Skeleton/tests/09_dark_mode.yaml +52 -0
  147. package/templates/ios/SocialTemplate/SocialTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
  148. package/templates/ios/TaskTemplate/TaskTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
  149. package/templates/ios/TrackTemplate/TrackTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
  150. package/templates/react-native/BookTemplate/src/animation/useAnimatedList.ts +219 -2
  151. package/templates/react-native/BookTemplate/src/animation/useMotionPreferences.ts +23 -9
  152. package/templates/react-native/BookTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
  153. package/templates/react-native/ChatTemplate/src/animation/useAnimatedList.ts +219 -2
  154. package/templates/react-native/ChatTemplate/src/animation/useMotionPreferences.ts +23 -9
  155. package/templates/react-native/ChatTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
  156. package/templates/react-native/DashTemplate/src/animation/useAnimatedList.ts +219 -2
  157. package/templates/react-native/DashTemplate/src/animation/useMotionPreferences.ts +23 -9
  158. package/templates/react-native/DashTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
  159. package/templates/react-native/FamilyTemplate/src/animation/useAnimatedList.ts +219 -2
  160. package/templates/react-native/FamilyTemplate/src/animation/useMotionPreferences.ts +23 -9
  161. package/templates/react-native/FamilyTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
  162. package/templates/react-native/FinanceTemplate/src/animation/useAnimatedList.ts +219 -2
  163. package/templates/react-native/FinanceTemplate/src/animation/useMotionPreferences.ts +23 -9
  164. package/templates/react-native/FinanceTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
  165. package/templates/react-native/GameTemplate/src/animation/useAnimatedList.ts +219 -2
  166. package/templates/react-native/GameTemplate/src/animation/useMotionPreferences.ts +23 -9
  167. package/templates/react-native/GameTemplate/src/screens/GameDetail/GameDetailScreen.tsx +2 -1
  168. package/templates/react-native/GameTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
  169. package/templates/react-native/HealthTemplate/src/animation/useAnimatedList.ts +219 -2
  170. package/templates/react-native/HealthTemplate/src/animation/useMotionPreferences.ts +23 -9
  171. package/templates/react-native/HealthTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
  172. package/templates/react-native/HealthTemplate/src/screens/WorkoutDetail/WorkoutDetailScreen.tsx +1 -1
  173. package/templates/react-native/LearnTemplate/src/animation/useAnimatedList.ts +219 -2
  174. package/templates/react-native/LearnTemplate/src/animation/useMotionPreferences.ts +23 -9
  175. package/templates/react-native/LearnTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
  176. package/templates/react-native/MapTemplate/src/animation/useAnimatedList.ts +219 -2
  177. package/templates/react-native/MapTemplate/src/animation/useMotionPreferences.ts +23 -9
  178. package/templates/react-native/MapTemplate/src/screens/Map/MapScreen.tsx +14 -0
  179. package/templates/react-native/MapTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
  180. package/templates/react-native/MediaTemplate/src/animation/useAnimatedList.ts +219 -2
  181. package/templates/react-native/MediaTemplate/src/animation/useMotionPreferences.ts +23 -9
  182. package/templates/react-native/MediaTemplate/src/screens/PlaylistDetail/PlaylistDetailScreen.tsx +1 -1
  183. package/templates/react-native/MediaTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
  184. package/templates/react-native/ReferenceTemplate/src/animation/useAnimatedList.ts +219 -2
  185. package/templates/react-native/ReferenceTemplate/src/animation/useMotionPreferences.ts +23 -9
  186. package/templates/react-native/ReferenceTemplate/src/screens/Settings/SettingsScreen.tsx +1 -1
  187. package/templates/react-native/ShopTemplate/src/animation/useAnimatedList.ts +219 -2
  188. package/templates/react-native/ShopTemplate/src/animation/useMotionPreferences.ts +23 -9
  189. package/templates/react-native/ShopTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
  190. package/templates/react-native/Skeleton/TESTING_MANIFEST.md +2 -1
  191. package/templates/react-native/Skeleton/src/animation/useAnimatedList.ts +219 -2
  192. package/templates/react-native/Skeleton/src/animation/useMotionPreferences.ts +23 -9
  193. package/templates/react-native/Skeleton/src/screens/Profile/ProfileScreen.tsx +1 -1
  194. package/templates/react-native/Skeleton/tests/07_profile.yaml +3 -2
  195. package/templates/react-native/Skeleton/tests/08_full_e2e.yaml +12 -1
  196. package/templates/react-native/Skeleton/tests/09_dark_mode.yaml +46 -0
  197. package/templates/react-native/SocialTemplate/src/animation/useAnimatedList.ts +219 -2
  198. package/templates/react-native/SocialTemplate/src/animation/useMotionPreferences.ts +23 -9
  199. package/templates/react-native/SocialTemplate/src/screens/Feed/FeedScreen.tsx +1 -0
  200. package/templates/react-native/SocialTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
  201. package/templates/react-native/TaskTemplate/src/animation/useAnimatedList.ts +219 -2
  202. package/templates/react-native/TaskTemplate/src/animation/useMotionPreferences.ts +23 -9
  203. package/templates/react-native/TaskTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
  204. package/templates/react-native/TrackTemplate/src/animation/useAnimatedList.ts +219 -2
  205. package/templates/react-native/TrackTemplate/src/animation/useMotionPreferences.ts +23 -9
  206. package/templates/react-native/TrackTemplate/src/screens/Settings/SettingsScreen.tsx +1 -1
  207. package/templates/shared/ios/AnimatedTransitions/AnimatedTransitionsView.swift +233 -93
  208. package/.claude/agents/template-selector.md +0 -39
  209. package/.claude/skills/module-selector/SKILL.md +0 -81
  210. package/.claude/skills/template-selector/SKILL.md +0 -44
  211. package/.cursor/agents/template-selector.md +0 -52
  212. package/.cursor/skills/module-selector/SKILL.md +0 -135
  213. package/.cursor/skills/template-selector/SKILL.md +0 -123
@@ -1,14 +1,24 @@
1
1
  package com.appship.reference.core.animation
2
2
 
3
+ import android.view.HapticFeedbackConstants
3
4
  import androidx.compose.animation.*
4
5
  import androidx.compose.animation.core.*
6
+ import androidx.compose.foundation.clickable
7
+ import androidx.compose.foundation.gestures.detectTapGestures
8
+ import androidx.compose.foundation.interaction.MutableInteractionSource
9
+ import androidx.compose.foundation.interaction.collectIsPressedAsState
5
10
  import androidx.compose.foundation.layout.offset
6
11
  import androidx.compose.runtime.*
7
12
  import androidx.compose.ui.Modifier
8
13
  import androidx.compose.ui.composed
9
14
  import androidx.compose.ui.draw.alpha
10
15
  import androidx.compose.ui.draw.scale
16
+ import androidx.compose.ui.draw.shadow
17
+ import androidx.compose.ui.graphics.Color
18
+ import androidx.compose.ui.input.pointer.pointerInput
19
+ import androidx.compose.ui.platform.LocalView
11
20
  import androidx.compose.ui.unit.IntOffset
21
+ import androidx.compose.ui.unit.dp
12
22
 
13
23
  /**
14
24
  * Staggered appear animation for list items.
@@ -118,3 +128,181 @@ fun Modifier.bounceOnChange(
118
128
 
119
129
  this.scale(bounceAnim)
120
130
  }
131
+
132
+ /**
133
+ * Scale-down press feedback for tappable elements.
134
+ * Scales to 0.95 on press with spring return animation.
135
+ */
136
+ fun Modifier.scaleOnPress(): Modifier = composed {
137
+ val interactionSource = remember { MutableInteractionSource() }
138
+ val isPressed by interactionSource.collectIsPressedAsState()
139
+
140
+ val scale by animateFloatAsState(
141
+ targetValue = if (isPressed) 0.95f else 1f,
142
+ animationSpec = spring(
143
+ dampingRatio = Spring.DampingRatioMediumBouncy,
144
+ stiffness = Spring.StiffnessMedium
145
+ ),
146
+ label = "pressScale"
147
+ )
148
+
149
+ this
150
+ .scale(scale)
151
+ .clickable(
152
+ interactionSource = interactionSource,
153
+ indication = null
154
+ ) { }
155
+ }
156
+
157
+ /**
158
+ * Triggers haptic feedback on tap.
159
+ */
160
+ fun Modifier.hapticFeedback(): Modifier = composed {
161
+ val view = LocalView.current
162
+ this.pointerInput(Unit) {
163
+ detectTapGestures {
164
+ view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
165
+ }
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Slide-from-side + fade-in animation on appear.
171
+ */
172
+ fun Modifier.slideAndFade(
173
+ delayMs: Int = 0,
174
+ durationMs: Int = 400
175
+ ): Modifier = composed {
176
+ var isVisible by remember { mutableStateOf(false) }
177
+
178
+ val alpha by animateFloatAsState(
179
+ targetValue = if (isVisible) 1f else 0f,
180
+ animationSpec = tween(
181
+ durationMillis = durationMs,
182
+ delayMillis = delayMs,
183
+ easing = FastOutSlowInEasing
184
+ ),
185
+ label = "slideAndFadeAlpha"
186
+ )
187
+
188
+ val offsetX by animateIntAsState(
189
+ targetValue = if (isVisible) 0 else 40,
190
+ animationSpec = tween(
191
+ durationMillis = durationMs,
192
+ delayMillis = delayMs,
193
+ easing = FastOutSlowInEasing
194
+ ),
195
+ label = "slideAndFadeOffsetX"
196
+ )
197
+
198
+ LaunchedEffect(Unit) {
199
+ isVisible = true
200
+ }
201
+
202
+ this
203
+ .alpha(alpha)
204
+ .offset { IntOffset(offsetX, 0) }
205
+ }
206
+
207
+ /**
208
+ * Subtle colored shadow for cards and elevated surfaces.
209
+ * Uses Material 3 elevation with optional color tinting.
210
+ */
211
+ fun Modifier.cardShadow(
212
+ color: Color = Color.Black,
213
+ elevation: Float = 4f
214
+ ): Modifier = composed {
215
+ this.shadow(
216
+ elevation = elevation.dp,
217
+ ambientColor = color.copy(alpha = 0.08f),
218
+ spotColor = color.copy(alpha = 0.12f),
219
+ shape = androidx.compose.foundation.shape.RoundedCornerShape(12.dp)
220
+ )
221
+ }
222
+
223
+ /**
224
+ * Deeper shadow for floating elements (FABs, modals).
225
+ */
226
+ fun Modifier.elevatedShadow(
227
+ color: Color = Color.Black
228
+ ): Modifier = composed {
229
+ this.shadow(
230
+ elevation = 8.dp,
231
+ ambientColor = color.copy(alpha = 0.12f),
232
+ spotColor = color.copy(alpha = 0.16f),
233
+ shape = androidx.compose.foundation.shape.RoundedCornerShape(16.dp)
234
+ )
235
+ }
236
+
237
+ /**
238
+ * Shimmer loading overlay for skeleton placeholder views.
239
+ * Sweeps a translucent gradient across the content repeatedly.
240
+ */
241
+ fun Modifier.shimmer(
242
+ durationMs: Int = 1500
243
+ ): Modifier = composed {
244
+ val transition = rememberInfiniteTransition(label = "shimmer")
245
+ val translateAnim by transition.animateFloat(
246
+ initialValue = -1f,
247
+ targetValue = 2f,
248
+ animationSpec = infiniteRepeatable(
249
+ animation = tween(durationMillis = durationMs, easing = LinearEasing),
250
+ repeatMode = RepeatMode.Restart
251
+ ),
252
+ label = "shimmerTranslate"
253
+ )
254
+
255
+ this.alpha(0.3f + 0.7f * ((translateAnim + 1f) / 3f).coerceIn(0f, 1f))
256
+ }
257
+
258
+ /**
259
+ * Repeating scale pulse animation for live indicators and notifications.
260
+ */
261
+ fun Modifier.pulse(
262
+ intensity: Float = 0.05f,
263
+ durationMs: Int = 1000
264
+ ): Modifier = composed {
265
+ val transition = rememberInfiniteTransition(label = "pulse")
266
+ val scale by transition.animateFloat(
267
+ initialValue = 1f,
268
+ targetValue = 1f + intensity,
269
+ animationSpec = infiniteRepeatable(
270
+ animation = tween(durationMillis = durationMs, easing = FastOutSlowInEasing),
271
+ repeatMode = RepeatMode.Reverse
272
+ ),
273
+ label = "pulseScale"
274
+ )
275
+
276
+ this.scale(scale)
277
+ }
278
+
279
+ /**
280
+ * Heart/like bounce effect when toggled active.
281
+ * Scales up to 1.3x then springs back. Perfect for favorite buttons.
282
+ */
283
+ fun Modifier.heartBounce(
284
+ isActive: Boolean
285
+ ): Modifier = composed {
286
+ var previousActive by remember { mutableStateOf(isActive) }
287
+ var animatedScale by remember { mutableFloatStateOf(1f) }
288
+
289
+ val bounceAnim by animateFloatAsState(
290
+ targetValue = animatedScale,
291
+ animationSpec = spring(
292
+ dampingRatio = Spring.DampingRatioMediumBouncy,
293
+ stiffness = Spring.StiffnessMedium
294
+ ),
295
+ label = "heartBounce"
296
+ )
297
+
298
+ LaunchedEffect(isActive) {
299
+ if (isActive && !previousActive) {
300
+ animatedScale = 1.3f
301
+ kotlinx.coroutines.delay(200)
302
+ animatedScale = 1f
303
+ }
304
+ previousActive = isActive
305
+ }
306
+
307
+ this.scale(bounceAnim)
308
+ }
@@ -1,4 +1,4 @@
1
- package com.appship.reference.features
1
+ package com.appship.reference.features.settings
2
2
 
3
3
  import androidx.compose.foundation.clickable
4
4
  import androidx.compose.foundation.layout.*
@@ -1,14 +1,24 @@
1
1
  package com.appship.shop.core.animation
2
2
 
3
+ import android.view.HapticFeedbackConstants
3
4
  import androidx.compose.animation.*
4
5
  import androidx.compose.animation.core.*
6
+ import androidx.compose.foundation.clickable
7
+ import androidx.compose.foundation.gestures.detectTapGestures
8
+ import androidx.compose.foundation.interaction.MutableInteractionSource
9
+ import androidx.compose.foundation.interaction.collectIsPressedAsState
5
10
  import androidx.compose.foundation.layout.offset
6
11
  import androidx.compose.runtime.*
7
12
  import androidx.compose.ui.Modifier
8
13
  import androidx.compose.ui.composed
9
14
  import androidx.compose.ui.draw.alpha
10
15
  import androidx.compose.ui.draw.scale
16
+ import androidx.compose.ui.draw.shadow
17
+ import androidx.compose.ui.graphics.Color
18
+ import androidx.compose.ui.input.pointer.pointerInput
19
+ import androidx.compose.ui.platform.LocalView
11
20
  import androidx.compose.ui.unit.IntOffset
21
+ import androidx.compose.ui.unit.dp
12
22
 
13
23
  /**
14
24
  * Staggered appear animation for list items.
@@ -118,3 +128,181 @@ fun Modifier.bounceOnChange(
118
128
 
119
129
  this.scale(bounceAnim)
120
130
  }
131
+
132
+ /**
133
+ * Scale-down press feedback for tappable elements.
134
+ * Scales to 0.95 on press with spring return animation.
135
+ */
136
+ fun Modifier.scaleOnPress(): Modifier = composed {
137
+ val interactionSource = remember { MutableInteractionSource() }
138
+ val isPressed by interactionSource.collectIsPressedAsState()
139
+
140
+ val scale by animateFloatAsState(
141
+ targetValue = if (isPressed) 0.95f else 1f,
142
+ animationSpec = spring(
143
+ dampingRatio = Spring.DampingRatioMediumBouncy,
144
+ stiffness = Spring.StiffnessMedium
145
+ ),
146
+ label = "pressScale"
147
+ )
148
+
149
+ this
150
+ .scale(scale)
151
+ .clickable(
152
+ interactionSource = interactionSource,
153
+ indication = null
154
+ ) { }
155
+ }
156
+
157
+ /**
158
+ * Triggers haptic feedback on tap.
159
+ */
160
+ fun Modifier.hapticFeedback(): Modifier = composed {
161
+ val view = LocalView.current
162
+ this.pointerInput(Unit) {
163
+ detectTapGestures {
164
+ view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
165
+ }
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Slide-from-side + fade-in animation on appear.
171
+ */
172
+ fun Modifier.slideAndFade(
173
+ delayMs: Int = 0,
174
+ durationMs: Int = 400
175
+ ): Modifier = composed {
176
+ var isVisible by remember { mutableStateOf(false) }
177
+
178
+ val alpha by animateFloatAsState(
179
+ targetValue = if (isVisible) 1f else 0f,
180
+ animationSpec = tween(
181
+ durationMillis = durationMs,
182
+ delayMillis = delayMs,
183
+ easing = FastOutSlowInEasing
184
+ ),
185
+ label = "slideAndFadeAlpha"
186
+ )
187
+
188
+ val offsetX by animateIntAsState(
189
+ targetValue = if (isVisible) 0 else 40,
190
+ animationSpec = tween(
191
+ durationMillis = durationMs,
192
+ delayMillis = delayMs,
193
+ easing = FastOutSlowInEasing
194
+ ),
195
+ label = "slideAndFadeOffsetX"
196
+ )
197
+
198
+ LaunchedEffect(Unit) {
199
+ isVisible = true
200
+ }
201
+
202
+ this
203
+ .alpha(alpha)
204
+ .offset { IntOffset(offsetX, 0) }
205
+ }
206
+
207
+ /**
208
+ * Subtle colored shadow for cards and elevated surfaces.
209
+ * Uses Material 3 elevation with optional color tinting.
210
+ */
211
+ fun Modifier.cardShadow(
212
+ color: Color = Color.Black,
213
+ elevation: Float = 4f
214
+ ): Modifier = composed {
215
+ this.shadow(
216
+ elevation = elevation.dp,
217
+ ambientColor = color.copy(alpha = 0.08f),
218
+ spotColor = color.copy(alpha = 0.12f),
219
+ shape = androidx.compose.foundation.shape.RoundedCornerShape(12.dp)
220
+ )
221
+ }
222
+
223
+ /**
224
+ * Deeper shadow for floating elements (FABs, modals).
225
+ */
226
+ fun Modifier.elevatedShadow(
227
+ color: Color = Color.Black
228
+ ): Modifier = composed {
229
+ this.shadow(
230
+ elevation = 8.dp,
231
+ ambientColor = color.copy(alpha = 0.12f),
232
+ spotColor = color.copy(alpha = 0.16f),
233
+ shape = androidx.compose.foundation.shape.RoundedCornerShape(16.dp)
234
+ )
235
+ }
236
+
237
+ /**
238
+ * Shimmer loading overlay for skeleton placeholder views.
239
+ * Sweeps a translucent gradient across the content repeatedly.
240
+ */
241
+ fun Modifier.shimmer(
242
+ durationMs: Int = 1500
243
+ ): Modifier = composed {
244
+ val transition = rememberInfiniteTransition(label = "shimmer")
245
+ val translateAnim by transition.animateFloat(
246
+ initialValue = -1f,
247
+ targetValue = 2f,
248
+ animationSpec = infiniteRepeatable(
249
+ animation = tween(durationMillis = durationMs, easing = LinearEasing),
250
+ repeatMode = RepeatMode.Restart
251
+ ),
252
+ label = "shimmerTranslate"
253
+ )
254
+
255
+ this.alpha(0.3f + 0.7f * ((translateAnim + 1f) / 3f).coerceIn(0f, 1f))
256
+ }
257
+
258
+ /**
259
+ * Repeating scale pulse animation for live indicators and notifications.
260
+ */
261
+ fun Modifier.pulse(
262
+ intensity: Float = 0.05f,
263
+ durationMs: Int = 1000
264
+ ): Modifier = composed {
265
+ val transition = rememberInfiniteTransition(label = "pulse")
266
+ val scale by transition.animateFloat(
267
+ initialValue = 1f,
268
+ targetValue = 1f + intensity,
269
+ animationSpec = infiniteRepeatable(
270
+ animation = tween(durationMillis = durationMs, easing = FastOutSlowInEasing),
271
+ repeatMode = RepeatMode.Reverse
272
+ ),
273
+ label = "pulseScale"
274
+ )
275
+
276
+ this.scale(scale)
277
+ }
278
+
279
+ /**
280
+ * Heart/like bounce effect when toggled active.
281
+ * Scales up to 1.3x then springs back. Perfect for favorite buttons.
282
+ */
283
+ fun Modifier.heartBounce(
284
+ isActive: Boolean
285
+ ): Modifier = composed {
286
+ var previousActive by remember { mutableStateOf(isActive) }
287
+ var animatedScale by remember { mutableFloatStateOf(1f) }
288
+
289
+ val bounceAnim by animateFloatAsState(
290
+ targetValue = animatedScale,
291
+ animationSpec = spring(
292
+ dampingRatio = Spring.DampingRatioMediumBouncy,
293
+ stiffness = Spring.StiffnessMedium
294
+ ),
295
+ label = "heartBounce"
296
+ )
297
+
298
+ LaunchedEffect(isActive) {
299
+ if (isActive && !previousActive) {
300
+ animatedScale = 1.3f
301
+ kotlinx.coroutines.delay(200)
302
+ animatedScale = 1f
303
+ }
304
+ previousActive = isActive
305
+ }
306
+
307
+ this.scale(bounceAnim)
308
+ }
@@ -294,9 +294,10 @@ fun OrderSummaryCard(
294
294
  discountCode: String?,
295
295
  shipping: Double,
296
296
  tax: Double,
297
- total: Double
297
+ total: Double,
298
+ modifier: Modifier = Modifier
298
299
  ) {
299
- Card {
300
+ Card(modifier = modifier) {
300
301
  Column(modifier = Modifier.padding(16.dp)) {
301
302
  Text(
302
303
  "Order Summary",
@@ -48,7 +48,8 @@ This document catalogs all test tags in the Skeleton template for automated test
48
48
  - `profile_name` — User name
49
49
  - `profile_email` — User email
50
50
  - `profile_notifications` — Notifications setting
51
- - `profile_appearance` — Appearance setting
51
+ - `profile_appearance` — Appearance / Dark Mode row
52
+ - `profile_dark_mode_toggle` — Dark mode toggle switch
52
53
  - `profile_motion_preferences` — Motion Preferences / Animations link
53
54
  - `profile_version` — App version
54
55
  - `profile_privacy` — Privacy policy
@@ -4,18 +4,39 @@ import android.os.Bundle
4
4
  import androidx.activity.ComponentActivity
5
5
  import androidx.activity.compose.setContent
6
6
  import androidx.activity.enableEdgeToEdge
7
+ import androidx.compose.foundation.layout.Box
8
+ import androidx.compose.foundation.layout.fillMaxSize
9
+ import androidx.compose.runtime.CompositionLocalProvider
10
+ import androidx.compose.runtime.remember
11
+ import androidx.compose.ui.ExperimentalComposeUiApi
12
+ import androidx.compose.ui.Modifier
13
+ import androidx.compose.ui.semantics.semantics
14
+ import androidx.compose.ui.semantics.testTagsAsResourceId
15
+ import com.appship.skeleton.core.theme.AppearanceManager
16
+ import com.appship.skeleton.core.theme.LocalAppearanceManager
7
17
  import com.appship.skeleton.core.theme.SkeletonTheme
18
+ import com.appship.skeleton.core.theme.resolveIsDarkTheme
8
19
  import com.appship.skeleton.features.navigation.MainScreen
9
20
  import dagger.hilt.android.AndroidEntryPoint
10
21
 
11
22
  @AndroidEntryPoint
12
23
  class MainActivity : ComponentActivity() {
24
+ @OptIn(ExperimentalComposeUiApi::class)
13
25
  override fun onCreate(savedInstanceState: Bundle?) {
14
26
  super.onCreate(savedInstanceState)
15
27
  enableEdgeToEdge()
16
28
  setContent {
17
- SkeletonTheme {
18
- MainScreen()
29
+ val appearanceManager = remember { AppearanceManager(this) }
30
+ CompositionLocalProvider(LocalAppearanceManager provides appearanceManager) {
31
+ SkeletonTheme(darkTheme = resolveIsDarkTheme(appearanceManager)) {
32
+ Box(
33
+ modifier = Modifier
34
+ .fillMaxSize()
35
+ .semantics { testTagsAsResourceId = true }
36
+ ) {
37
+ MainScreen()
38
+ }
39
+ }
19
40
  }
20
41
  }
21
42
  }
@@ -1,14 +1,24 @@
1
1
  package com.appship.skeleton.core.animation
2
2
 
3
+ import android.view.HapticFeedbackConstants
3
4
  import androidx.compose.animation.*
4
5
  import androidx.compose.animation.core.*
6
+ import androidx.compose.foundation.clickable
7
+ import androidx.compose.foundation.gestures.detectTapGestures
8
+ import androidx.compose.foundation.interaction.MutableInteractionSource
9
+ import androidx.compose.foundation.interaction.collectIsPressedAsState
5
10
  import androidx.compose.foundation.layout.offset
6
11
  import androidx.compose.runtime.*
7
12
  import androidx.compose.ui.Modifier
8
13
  import androidx.compose.ui.composed
9
14
  import androidx.compose.ui.draw.alpha
10
15
  import androidx.compose.ui.draw.scale
16
+ import androidx.compose.ui.draw.shadow
17
+ import androidx.compose.ui.graphics.Color
18
+ import androidx.compose.ui.input.pointer.pointerInput
19
+ import androidx.compose.ui.platform.LocalView
11
20
  import androidx.compose.ui.unit.IntOffset
21
+ import androidx.compose.ui.unit.dp
12
22
 
13
23
  /**
14
24
  * Staggered appear animation for list items.
@@ -118,3 +128,181 @@ fun Modifier.bounceOnChange(
118
128
 
119
129
  this.scale(bounceAnim)
120
130
  }
131
+
132
+ /**
133
+ * Scale-down press feedback for tappable elements.
134
+ * Scales to 0.95 on press with spring return animation.
135
+ */
136
+ fun Modifier.scaleOnPress(): Modifier = composed {
137
+ val interactionSource = remember { MutableInteractionSource() }
138
+ val isPressed by interactionSource.collectIsPressedAsState()
139
+
140
+ val scale by animateFloatAsState(
141
+ targetValue = if (isPressed) 0.95f else 1f,
142
+ animationSpec = spring(
143
+ dampingRatio = Spring.DampingRatioMediumBouncy,
144
+ stiffness = Spring.StiffnessMedium
145
+ ),
146
+ label = "pressScale"
147
+ )
148
+
149
+ this
150
+ .scale(scale)
151
+ .clickable(
152
+ interactionSource = interactionSource,
153
+ indication = null
154
+ ) { }
155
+ }
156
+
157
+ /**
158
+ * Triggers haptic feedback on tap.
159
+ */
160
+ fun Modifier.hapticFeedback(): Modifier = composed {
161
+ val view = LocalView.current
162
+ this.pointerInput(Unit) {
163
+ detectTapGestures {
164
+ view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
165
+ }
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Slide-from-side + fade-in animation on appear.
171
+ */
172
+ fun Modifier.slideAndFade(
173
+ delayMs: Int = 0,
174
+ durationMs: Int = 400
175
+ ): Modifier = composed {
176
+ var isVisible by remember { mutableStateOf(false) }
177
+
178
+ val alpha by animateFloatAsState(
179
+ targetValue = if (isVisible) 1f else 0f,
180
+ animationSpec = tween(
181
+ durationMillis = durationMs,
182
+ delayMillis = delayMs,
183
+ easing = FastOutSlowInEasing
184
+ ),
185
+ label = "slideAndFadeAlpha"
186
+ )
187
+
188
+ val offsetX by animateIntAsState(
189
+ targetValue = if (isVisible) 0 else 40,
190
+ animationSpec = tween(
191
+ durationMillis = durationMs,
192
+ delayMillis = delayMs,
193
+ easing = FastOutSlowInEasing
194
+ ),
195
+ label = "slideAndFadeOffsetX"
196
+ )
197
+
198
+ LaunchedEffect(Unit) {
199
+ isVisible = true
200
+ }
201
+
202
+ this
203
+ .alpha(alpha)
204
+ .offset { IntOffset(offsetX, 0) }
205
+ }
206
+
207
+ /**
208
+ * Subtle colored shadow for cards and elevated surfaces.
209
+ * Uses Material 3 elevation with optional color tinting.
210
+ */
211
+ fun Modifier.cardShadow(
212
+ color: Color = Color.Black,
213
+ elevation: Float = 4f
214
+ ): Modifier = composed {
215
+ this.shadow(
216
+ elevation = elevation.dp,
217
+ ambientColor = color.copy(alpha = 0.08f),
218
+ spotColor = color.copy(alpha = 0.12f),
219
+ shape = androidx.compose.foundation.shape.RoundedCornerShape(12.dp)
220
+ )
221
+ }
222
+
223
+ /**
224
+ * Deeper shadow for floating elements (FABs, modals).
225
+ */
226
+ fun Modifier.elevatedShadow(
227
+ color: Color = Color.Black
228
+ ): Modifier = composed {
229
+ this.shadow(
230
+ elevation = 8.dp,
231
+ ambientColor = color.copy(alpha = 0.12f),
232
+ spotColor = color.copy(alpha = 0.16f),
233
+ shape = androidx.compose.foundation.shape.RoundedCornerShape(16.dp)
234
+ )
235
+ }
236
+
237
+ /**
238
+ * Shimmer loading overlay for skeleton placeholder views.
239
+ * Sweeps a translucent gradient across the content repeatedly.
240
+ */
241
+ fun Modifier.shimmer(
242
+ durationMs: Int = 1500
243
+ ): Modifier = composed {
244
+ val transition = rememberInfiniteTransition(label = "shimmer")
245
+ val translateAnim by transition.animateFloat(
246
+ initialValue = -1f,
247
+ targetValue = 2f,
248
+ animationSpec = infiniteRepeatable(
249
+ animation = tween(durationMillis = durationMs, easing = LinearEasing),
250
+ repeatMode = RepeatMode.Restart
251
+ ),
252
+ label = "shimmerTranslate"
253
+ )
254
+
255
+ this.alpha(0.3f + 0.7f * ((translateAnim + 1f) / 3f).coerceIn(0f, 1f))
256
+ }
257
+
258
+ /**
259
+ * Repeating scale pulse animation for live indicators and notifications.
260
+ */
261
+ fun Modifier.pulse(
262
+ intensity: Float = 0.05f,
263
+ durationMs: Int = 1000
264
+ ): Modifier = composed {
265
+ val transition = rememberInfiniteTransition(label = "pulse")
266
+ val scale by transition.animateFloat(
267
+ initialValue = 1f,
268
+ targetValue = 1f + intensity,
269
+ animationSpec = infiniteRepeatable(
270
+ animation = tween(durationMillis = durationMs, easing = FastOutSlowInEasing),
271
+ repeatMode = RepeatMode.Reverse
272
+ ),
273
+ label = "pulseScale"
274
+ )
275
+
276
+ this.scale(scale)
277
+ }
278
+
279
+ /**
280
+ * Heart/like bounce effect when toggled active.
281
+ * Scales up to 1.3x then springs back. Perfect for favorite buttons.
282
+ */
283
+ fun Modifier.heartBounce(
284
+ isActive: Boolean
285
+ ): Modifier = composed {
286
+ var previousActive by remember { mutableStateOf(isActive) }
287
+ var animatedScale by remember { mutableFloatStateOf(1f) }
288
+
289
+ val bounceAnim by animateFloatAsState(
290
+ targetValue = animatedScale,
291
+ animationSpec = spring(
292
+ dampingRatio = Spring.DampingRatioMediumBouncy,
293
+ stiffness = Spring.StiffnessMedium
294
+ ),
295
+ label = "heartBounce"
296
+ )
297
+
298
+ LaunchedEffect(isActive) {
299
+ if (isActive && !previousActive) {
300
+ animatedScale = 1.3f
301
+ kotlinx.coroutines.delay(200)
302
+ animatedScale = 1f
303
+ }
304
+ previousActive = isActive
305
+ }
306
+
307
+ this.scale(bounceAnim)
308
+ }