@shaykec/app-agent 1.0.9 → 1.0.11
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/.claude/agents/catalog-analyzer.md +57 -0
- package/.claude/skills/android-customizer/SKILL.md +23 -10
- package/.claude/skills/bug-fixer/SKILL.md +59 -0
- package/.claude/skills/catalog-analyzer/SKILL.md +96 -0
- package/.claude/skills/customization-planner/SKILL.md +44 -5
- package/.claude/skills/design-selector/SKILL.md +3 -1
- package/.claude/skills/design-system/SKILL.md +1 -1
- package/.claude/skills/exploratory-tester/SKILL.md +82 -0
- package/.claude/skills/ios-customizer/SKILL.md +29 -8
- package/.claude/skills/module-integrator/SKILL.md +1 -1
- package/.claude/skills/react-native-customizer/SKILL.md +22 -10
- package/.claude/skills/test-planner/SKILL.md +72 -0
- package/.cursor/agents/README.md +3 -1
- package/.cursor/agents/catalog-analyzer.md +83 -0
- package/.cursor/rules/safety-guardrails.mdc +1 -1
- package/.cursor/rules/workflow.mdc +52 -18
- package/.cursor/skills/android-customizer/SKILL.md +43 -19
- package/.cursor/skills/bug-fixer/SKILL.md +189 -0
- package/.cursor/skills/catalog-analyzer/SKILL.md +222 -0
- package/.cursor/skills/customization-planner/SKILL.md +55 -8
- package/.cursor/skills/design-selector/SKILL.md +6 -5
- package/.cursor/skills/design-system/SKILL.md +8 -7
- package/.cursor/skills/exploratory-tester/SKILL.md +223 -0
- package/.cursor/skills/ios-customizer/SKILL.md +47 -12
- package/.cursor/skills/module-integrator/SKILL.md +2 -2
- package/.cursor/skills/output-validator/SKILL.md +1 -1
- package/.cursor/skills/react-native-customizer/SKILL.md +46 -16
- package/.cursor/skills/test-planner/SKILL.md +199 -0
- package/.cursor/skills/web-analyzer/SKILL.md +310 -0
- package/.cursor/skills/web-crawler/SKILL.md +252 -0
- package/AGENTS.md +32 -11
- package/CLAUDE.md +78 -33
- package/README.md +77 -11
- package/designs/DESIGN_CATALOG.md +17 -15
- package/designs/DESIGN_PRINCIPLES.md +53 -0
- package/designs/brands/accessible-high-contrast.md +14 -0
- package/designs/brands/corporate-professional.md +14 -0
- package/designs/brands/dark-luxe.md +14 -0
- package/designs/brands/kids-playful.md +14 -0
- package/designs/brands/medical-clinical.md +14 -0
- package/designs/brands/modern-minimal.md +14 -0
- package/designs/brands/nature-organic.md +14 -0
- package/designs/brands/neo-brutalist.md +14 -0
- package/designs/brands/retro-vintage.md +14 -0
- package/designs/brands/soft-gradient.md +14 -0
- package/designs/brands/sport-athletic.md +14 -0
- package/designs/brands/tech-dynamic.md +14 -0
- package/designs/brands/vibrant-playful.md +14 -0
- package/dist/cli.d.ts +4 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +123 -11
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +8 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +6 -1
- package/dist/config.js.map +1 -1
- package/dist/engines/claude-engine.d.ts.map +1 -1
- package/dist/engines/claude-engine.js +16 -4
- package/dist/engines/claude-engine.js.map +1 -1
- package/dist/engines/types.d.ts +1 -1
- package/dist/engines/types.d.ts.map +1 -1
- package/dist/engines/types.js +31 -2
- package/dist/engines/types.js.map +1 -1
- package/dist/github.d.ts +3 -0
- package/dist/github.d.ts.map +1 -1
- package/dist/github.js +47 -4
- package/dist/github.js.map +1 -1
- package/dist/index.js +294 -16
- package/dist/index.js.map +1 -1
- package/dist/prompt-builder.d.ts +17 -1
- package/dist/prompt-builder.d.ts.map +1 -1
- package/dist/prompt-builder.js +272 -1
- package/dist/prompt-builder.js.map +1 -1
- package/dist/validator.d.ts +7 -2
- package/dist/validator.d.ts.map +1 -1
- package/dist/validator.js +61 -41
- package/dist/validator.js.map +1 -1
- package/dist/workspace.js +2 -2
- package/dist/workspace.js.map +1 -1
- package/package.json +2 -4
- package/prompts/agent-prompt.md +35 -18
- package/prompts/deep-test-agent-prompt.md +122 -0
- package/prompts/fix-agent-prompt.md +90 -0
- package/prompts/quick-agent-prompt.md +32 -2
- package/prompts/scratch-agent-prompt.md +5 -8
- package/prompts/web-clone-agent-prompt.md +179 -0
- package/templates/android/BookTemplate/app/src/main/kotlin/com/appship/book/core/animation/AnimatedTransitionsModifiers.kt +188 -0
- package/templates/android/ChatTemplate/app/src/main/kotlin/com/appship/chat/core/animation/AnimatedTransitionsModifiers.kt +188 -0
- package/templates/android/ChatTemplate/app/src/main/kotlin/com/appship/chat/features/conversations/ConversationsScreen.kt +1 -1
- package/templates/android/DashTemplate/app/src/main/kotlin/com/appship/dash/core/animation/AnimatedTransitionsModifiers.kt +188 -0
- package/templates/android/DashTemplate/app/src/main/kotlin/com/appship/dash/features/navigation/MainScreen.kt +1 -0
- package/templates/android/FamilyTemplate/app/src/main/java/com/appship/family/core/animation/AnimatedTransitionsModifiers.kt +188 -0
- package/templates/android/FamilyTemplate/app/src/main/java/com/appship/family/features/navigation/MainNavigation.kt +5 -1
- package/templates/android/FinanceTemplate/app/src/main/kotlin/com/appship/finance/core/animation/AnimatedTransitionsModifiers.kt +188 -0
- package/templates/android/GameTemplate/app/src/main/kotlin/com/appship/game/core/animation/AnimatedTransitionsModifiers.kt +188 -0
- package/templates/android/GameTemplate/app/src/main/kotlin/com/appship/game/core/animation/MotionPreferencesScreen.kt +3 -3
- package/templates/android/GameTemplate/app/src/main/kotlin/com/appship/game/features/navigation/Navigation.kt +1 -1
- package/templates/android/GameTemplate/app/src/main/kotlin/com/appship/game/features/settings/SettingsScreen.kt +1 -1
- package/templates/android/HealthTemplate/app/src/main/kotlin/com/appship/health/core/animation/AnimatedTransitionsModifiers.kt +188 -0
- package/templates/android/LearnTemplate/app/src/main/kotlin/com/appship/learn/core/animation/AnimatedTransitionsModifiers.kt +188 -0
- package/templates/android/MapTemplate/app/src/main/kotlin/com/appship/map/core/animation/AnimatedTransitionsModifiers.kt +188 -0
- package/templates/android/MediaTemplate/app/src/main/kotlin/com/appship/media/core/animation/AnimatedTransitionsModifiers.kt +188 -0
- package/templates/android/MediaTemplate/app/src/main/kotlin/com/appship/media/features/settings/SettingsScreen.kt +3 -2
- package/templates/android/ReferenceTemplate/app/src/main/kotlin/com/appship/reference/core/animation/AnimatedTransitionsModifiers.kt +188 -0
- package/templates/android/ReferenceTemplate/app/src/main/kotlin/com/appship/reference/features/settings/SettingsScreen.kt +1 -1
- package/templates/android/ShopTemplate/app/src/main/kotlin/com/appship/shop/core/animation/AnimatedTransitionsModifiers.kt +188 -0
- package/templates/android/ShopTemplate/app/src/main/kotlin/com/appship/shop/features/cart/CartScreen.kt +3 -2
- package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/core/animation/AnimatedTransitionsModifiers.kt +188 -0
- package/templates/android/Skeleton/tests/03_detail_screen.yaml +1 -1
- package/templates/android/Skeleton/tests/04_favorites.yaml +1 -1
- package/templates/android/Skeleton/tests/08_full_e2e.yaml +7 -1
- package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/core/animation/AnimatedTransitionsModifiers.kt +188 -0
- package/templates/android/TaskTemplate/app/src/main/kotlin/com/appship/task/core/animation/AnimatedTransitionsModifiers.kt +188 -0
- package/templates/android/TaskTemplate/app/src/main/kotlin/com/appship/task/features/settings/SettingsScreen.kt +3 -2
- package/templates/android/TrackTemplate/app/src/main/kotlin/com/appship/track/core/animation/AnimatedTransitionsModifiers.kt +188 -0
- package/templates/ios/BookTemplate/BookTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
- package/templates/ios/ChatTemplate/ChatTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
- package/templates/ios/DashTemplate/DashTemplate/App/AppConfig.swift +1 -0
- package/templates/ios/DashTemplate/DashTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
- package/templates/ios/DashTemplate/DashTemplate/Core/Strings.swift +13 -0
- package/templates/ios/DashTemplate/DashTemplate.xcodeproj/project.pbxproj +32 -20
- package/templates/ios/FamilyTemplate/FamilyTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
- package/templates/ios/FinanceTemplate/FinanceTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
- package/templates/ios/FinanceTemplate/FinanceTemplate/Core/Strings.swift +42 -0
- package/templates/ios/FinanceTemplate/FinanceTemplate.xcodeproj/project.pbxproj +36 -30
- package/templates/ios/GameTemplate/GameTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
- package/templates/ios/HealthTemplate/HealthTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
- package/templates/ios/LearnTemplate/LearnTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
- package/templates/ios/MapTemplate/MapTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
- package/templates/ios/MediaTemplate/MediaTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
- package/templates/ios/ReferenceTemplate/ReferenceTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
- package/templates/ios/ReferenceTemplate/ReferenceTemplate/Core/Strings.swift +12 -0
- package/templates/ios/ReferenceTemplate/ReferenceTemplate/Features/SkeletonLoading/SkeletonLoadingView.swift +2 -37
- package/templates/ios/ShopTemplate/ShopTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
- package/templates/ios/Skeleton/Skeleton/Core/Animation/AnimatedTransitionsView.swift +201 -0
- package/templates/ios/Skeleton/tests/08_full_e2e.yaml +4 -0
- package/templates/ios/SocialTemplate/SocialTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
- package/templates/ios/TaskTemplate/TaskTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
- package/templates/ios/TrackTemplate/TrackTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
- package/templates/react-native/BookTemplate/src/animation/useAnimatedList.ts +219 -2
- package/templates/react-native/BookTemplate/src/animation/useMotionPreferences.ts +23 -9
- package/templates/react-native/BookTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
- package/templates/react-native/ChatTemplate/src/animation/useAnimatedList.ts +219 -2
- package/templates/react-native/ChatTemplate/src/animation/useMotionPreferences.ts +23 -9
- package/templates/react-native/ChatTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
- package/templates/react-native/DashTemplate/src/animation/useAnimatedList.ts +219 -2
- package/templates/react-native/DashTemplate/src/animation/useMotionPreferences.ts +23 -9
- package/templates/react-native/DashTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
- package/templates/react-native/FamilyTemplate/src/animation/useAnimatedList.ts +219 -2
- package/templates/react-native/FamilyTemplate/src/animation/useMotionPreferences.ts +23 -9
- package/templates/react-native/FamilyTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
- package/templates/react-native/FinanceTemplate/src/animation/useAnimatedList.ts +219 -2
- package/templates/react-native/FinanceTemplate/src/animation/useMotionPreferences.ts +23 -9
- package/templates/react-native/FinanceTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
- package/templates/react-native/GameTemplate/src/animation/useAnimatedList.ts +219 -2
- package/templates/react-native/GameTemplate/src/animation/useMotionPreferences.ts +23 -9
- package/templates/react-native/GameTemplate/src/screens/GameDetail/GameDetailScreen.tsx +2 -1
- package/templates/react-native/GameTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
- package/templates/react-native/HealthTemplate/src/animation/useAnimatedList.ts +219 -2
- package/templates/react-native/HealthTemplate/src/animation/useMotionPreferences.ts +23 -9
- package/templates/react-native/HealthTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
- package/templates/react-native/HealthTemplate/src/screens/WorkoutDetail/WorkoutDetailScreen.tsx +1 -1
- package/templates/react-native/LearnTemplate/src/animation/useAnimatedList.ts +219 -2
- package/templates/react-native/LearnTemplate/src/animation/useMotionPreferences.ts +23 -9
- package/templates/react-native/LearnTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
- package/templates/react-native/MapTemplate/src/animation/useAnimatedList.ts +219 -2
- package/templates/react-native/MapTemplate/src/animation/useMotionPreferences.ts +23 -9
- package/templates/react-native/MapTemplate/src/screens/Map/MapScreen.tsx +14 -0
- package/templates/react-native/MapTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
- package/templates/react-native/MediaTemplate/src/animation/useAnimatedList.ts +219 -2
- package/templates/react-native/MediaTemplate/src/animation/useMotionPreferences.ts +23 -9
- package/templates/react-native/MediaTemplate/src/screens/PlaylistDetail/PlaylistDetailScreen.tsx +1 -1
- package/templates/react-native/MediaTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
- package/templates/react-native/ReferenceTemplate/src/animation/useAnimatedList.ts +219 -2
- package/templates/react-native/ReferenceTemplate/src/animation/useMotionPreferences.ts +23 -9
- package/templates/react-native/ReferenceTemplate/src/screens/Settings/SettingsScreen.tsx +1 -1
- package/templates/react-native/ShopTemplate/src/animation/useAnimatedList.ts +219 -2
- package/templates/react-native/ShopTemplate/src/animation/useMotionPreferences.ts +23 -9
- package/templates/react-native/ShopTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
- package/templates/react-native/Skeleton/TESTING_MANIFEST.md +1 -1
- package/templates/react-native/Skeleton/src/animation/useAnimatedList.ts +219 -2
- package/templates/react-native/Skeleton/src/animation/useMotionPreferences.ts +23 -9
- package/templates/react-native/Skeleton/src/screens/Profile/ProfileScreen.tsx +1 -1
- package/templates/react-native/Skeleton/tests/07_profile.yaml +3 -2
- package/templates/react-native/Skeleton/tests/08_full_e2e.yaml +12 -1
- package/templates/react-native/SocialTemplate/src/animation/useAnimatedList.ts +219 -2
- package/templates/react-native/SocialTemplate/src/animation/useMotionPreferences.ts +23 -9
- package/templates/react-native/SocialTemplate/src/screens/Feed/FeedScreen.tsx +1 -0
- package/templates/react-native/SocialTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
- package/templates/react-native/TaskTemplate/src/animation/useAnimatedList.ts +219 -2
- package/templates/react-native/TaskTemplate/src/animation/useMotionPreferences.ts +23 -9
- package/templates/react-native/TaskTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
- package/templates/react-native/TrackTemplate/src/animation/useAnimatedList.ts +219 -2
- package/templates/react-native/TrackTemplate/src/animation/useMotionPreferences.ts +23 -9
- package/templates/react-native/TrackTemplate/src/screens/Settings/SettingsScreen.tsx +1 -1
- package/templates/shared/ios/AnimatedTransitions/AnimatedTransitionsView.swift +233 -93
- package/.claude/agents/template-selector.md +0 -39
- package/.claude/skills/module-selector/SKILL.md +0 -81
- package/.claude/skills/template-selector/SKILL.md +0 -44
- package/.cursor/agents/template-selector.md +0 -52
- package/.cursor/skills/module-selector/SKILL.md +0 -135
- package/.cursor/skills/template-selector/SKILL.md +0 -123
|
@@ -79,6 +79,30 @@ Follow the `build-tester` skill. Compile the app and fix any errors.
|
|
|
79
79
|
- For Android: use `./gradlew assembleDebug`
|
|
80
80
|
- For React Native: use `npx react-native run-ios` or `npx react-native run-android`
|
|
81
81
|
|
|
82
|
+
After a successful build, write a minimal summary to `{{appDir}}/reports/summary.json`:
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"appName": "{{appName}}",
|
|
87
|
+
"platform": "{{platform}}",
|
|
88
|
+
"bundleId": "{{bundleId}}",
|
|
89
|
+
"mode": "quick",
|
|
90
|
+
"steps": [
|
|
91
|
+
{
|
|
92
|
+
"step": 6,
|
|
93
|
+
"name": "build",
|
|
94
|
+
"result": "SUCCESS",
|
|
95
|
+
"artifactPath": "{FULL path to the .app or .apk build artifact}"
|
|
96
|
+
}
|
|
97
|
+
],
|
|
98
|
+
"overallResult": "PASS",
|
|
99
|
+
"filesWritten": 0,
|
|
100
|
+
"duration": ""
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Create the `reports/` directory first. Set `artifactPath` to the actual build artifact location. Set `overallResult` to `"PASS"` if the build succeeded, `"FAIL"` if not. No markdown report files are needed — just this JSON.
|
|
105
|
+
|
|
82
106
|
### Step 7 — Smoke test
|
|
83
107
|
|
|
84
108
|
Boot 1 simulator/emulator, install the app, and verify:
|
|
@@ -87,7 +111,13 @@ Boot 1 simulator/emulator, install the app, and verify:
|
|
|
87
111
|
- Tapping an item navigates to the detail screen
|
|
88
112
|
- The create flow opens correctly
|
|
89
113
|
|
|
90
|
-
Use the `ai-tester` MCP tools for device interaction
|
|
114
|
+
Use the `ai-tester` MCP tools for device interaction:
|
|
115
|
+
1. Use `device({ action: "list" })` to find or boot a simulator
|
|
116
|
+
2. Install the app using the build artifact from Step 6
|
|
117
|
+
3. Use `inspect()` to verify each tab renders
|
|
118
|
+
|
|
119
|
+
**IMPORTANT:** If ai-tester MCP tools fail or Appium is unavailable, retry once. If still failing, note the failure explicitly in `summary.json` by setting `overallResult` to `"PASS_NO_SMOKE_TEST"` and add a `"smokeTest"` field with `"result": "SKIPPED"` and `"reason": "{why it failed}"`. Do NOT silently skip the smoke test.
|
|
120
|
+
|
|
91
121
|
No Maestro tests needed in quick mode.
|
|
92
122
|
|
|
93
123
|
### Step 8 — Screenshot Collection
|
|
@@ -116,7 +146,7 @@ Read `.cursor/skills/build-tester/SKILL.md` (or `.claude/skills/build-tester/SKI
|
|
|
116
146
|
|
|
117
147
|
- ONLY modify files under `{{appDir}}/`
|
|
118
148
|
- Do NOT read CATALOG.md, DESIGN_CATALOG.md, or MODULES_CATALOG.md
|
|
119
|
-
- Do NOT generate a customization manifest or reports
|
|
149
|
+
- Do NOT generate a customization manifest or markdown report files (only write `reports/summary.json`)
|
|
120
150
|
- Do NOT dispatch subagents — work inline
|
|
121
151
|
- Do NOT integrate shared modules
|
|
122
152
|
- Do NOT run security audits or output validation
|
|
@@ -22,9 +22,8 @@ You have specialised skills available in `.cursor/skills/`. Each skill contains
|
|
|
22
22
|
| Step | Skill | Purpose |
|
|
23
23
|
|------|-------|---------|
|
|
24
24
|
| 0 | `prompt-validator` | Validate user prompt for injection/malicious intent |
|
|
25
|
-
| 0 | `
|
|
25
|
+
| 0 | `catalog-analyzer` | Select 1-3 reference templates from CATALOG.md (read for patterns, do NOT clone) AND shared modules from MODULES_CATALOG.md |
|
|
26
26
|
| 0 | `design-selector` | Select the best design brand from DESIGN_CATALOG.md |
|
|
27
|
-
| 0 | `module-selector` | Select shared modules from MODULES_CATALOG.md based on app keywords |
|
|
28
27
|
| 1 | `content-writer` | Generate all user-facing strings (used during architecture + content planning) |
|
|
29
28
|
| 3 | `module-integrator` | Read shared module references and write adapted code into the app |
|
|
30
29
|
| 4 | `design-system` | Apply the selected brand's tokens to the app |
|
|
@@ -41,8 +40,7 @@ Every skill writes a report file to `output/{app-name}/reports/`. These reports
|
|
|
41
40
|
```
|
|
42
41
|
output/{app-name}/reports/
|
|
43
42
|
01-prompt-validation.md
|
|
44
|
-
02-
|
|
45
|
-
02b-module-selection.md
|
|
43
|
+
02-catalog-analysis.md
|
|
46
44
|
03-design-brand.md
|
|
47
45
|
architecture-plan.md
|
|
48
46
|
04-content-brief.md
|
|
@@ -83,14 +81,13 @@ Follow these steps in order. Steps marked `[PARALLEL]` should use `Task` for con
|
|
|
83
81
|
|
|
84
82
|
### Step 0 — [PARALLEL] Analysis Phase
|
|
85
83
|
|
|
86
|
-
Dispatch
|
|
84
|
+
Dispatch three `Task` subagents concurrently:
|
|
87
85
|
|
|
88
86
|
1. **Prompt Validation** — follow `prompt-validator` skill. Validate the user description.
|
|
89
|
-
2. **
|
|
87
|
+
2. **Catalog Analysis** — follow `catalog-analyzer` skill. Read `templates/CATALOG.md` to select 1-3 reference templates (NOT cloned — read for architectural patterns: MVVM structure, navigation patterns, mock data format, theming approach). Then read `templates/shared/MODULES_CATALOG.md` to select shared modules using template context. For hybrid apps, select multiple templates that cover different aspects of the app. **The module list feeds directly into Step 1 (architecture planning).**
|
|
90
88
|
3. **Design Brand Selection** — follow `design-selector` skill. Read `designs/DESIGN_CATALOG.md` and select the best brand.
|
|
91
|
-
4. **Module Selection** — follow `module-selector` skill. Read `templates/shared/MODULES_CATALOG.md` and select which shared modules to include. Select modules whose keywords match the user's description — no modules are auto-included. **The output of this step feeds directly into Step 1 (architecture planning).**
|
|
92
89
|
|
|
93
|
-
Wait for all
|
|
90
|
+
Wait for all three. If prompt validation FAILS: stop immediately.
|
|
94
91
|
|
|
95
92
|
This step also creates the `reports/` directory and initializes `summary.json` with `mode: "scratch"`.
|
|
96
93
|
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
You are AppAgent in **Web Clone mode**. Your job is to crawl a live website, understand its structure, brand, content, and functionality, then build a native mobile app that mirrors it.
|
|
2
|
+
|
|
3
|
+
## Source Website
|
|
4
|
+
|
|
5
|
+
**URL:** {{url}}
|
|
6
|
+
|
|
7
|
+
**Focus directive:** {{description}}
|
|
8
|
+
|
|
9
|
+
**Target platform:** {{platformLabel}}
|
|
10
|
+
|
|
11
|
+
## How to Use the Focus Directive
|
|
12
|
+
|
|
13
|
+
- If the focus directive is empty or says "Clone website: ...": crawl everything and mirror the full site
|
|
14
|
+
- If it contains specific instructions: use them to prioritize which sections become primary tabs/screens, which features to emphasize, and optionally add features not present on the site
|
|
15
|
+
- The focus directive can narrow scope ("only the recipes section"), augment ("add a favorites feature"), or both
|
|
16
|
+
|
|
17
|
+
## Skills
|
|
18
|
+
|
|
19
|
+
You have specialised skills available in `.cursor/skills/`. Each skill contains expert-level instructions for a specific task. When you reach a workflow step, read the `SKILL.md` file and follow its instructions.
|
|
20
|
+
|
|
21
|
+
| Step | Skill | Purpose |
|
|
22
|
+
|------|-------|---------|
|
|
23
|
+
| 0 | `prompt-validator` | Validate user prompt for injection/malicious intent |
|
|
24
|
+
| 0 | `web-crawler` | Crawl the website using ai-tester MCP — capture screenshots, element trees, brand signals |
|
|
25
|
+
| 1 | `web-analyzer` | Analyze crawled data — map pages to screens, extract brand, produce app manifest |
|
|
26
|
+
| 1 | `catalog-analyzer` | Select the best template from CATALOG.md AND shared modules from MODULES_CATALOG.md |
|
|
27
|
+
| 2 | `app-renaming` | Rename app consistently across all files |
|
|
28
|
+
| 3 | `design-system` | Apply the website's brand tokens (extracted from crawl) to the app |
|
|
29
|
+
| 3 | `content-writer` | Adapt website copy for mobile microcopy |
|
|
30
|
+
| 3 | `appconfig-customization` | Update all CUSTOMIZE markers |
|
|
31
|
+
| 3 | `mock-data-update` | Rewrite MockDataProvider with content from the website |
|
|
32
|
+
| 4 | `module-integrator` | Integrate shared modules if selected |
|
|
33
|
+
| 5 | `ios-customizer` / `android-customizer` / `react-native-customizer` | Platform-specific screen customisation (batch mode) |
|
|
34
|
+
| 6 | `build-tester` | Compile the app, fix errors, locate build artifact |
|
|
35
|
+
| 7 | `code-auditor` | Scan generated code for security issues |
|
|
36
|
+
| 7 | `output-validator` | Check output completeness, consistency, and accessibility |
|
|
37
|
+
| 8-11 | `ui-tester` | **CRITICAL** — Test UI on simulator, generate + run Maestro tests |
|
|
38
|
+
|
|
39
|
+
## Report Files
|
|
40
|
+
|
|
41
|
+
Every skill writes a report file to `output/{app-name}/reports/`. These reports create an audit trail.
|
|
42
|
+
|
|
43
|
+
**Report directory structure:**
|
|
44
|
+
```
|
|
45
|
+
output/{app-name}/reports/
|
|
46
|
+
01-prompt-validation.md
|
|
47
|
+
02-web-crawl-report.md
|
|
48
|
+
03-web-to-app-manifest.md
|
|
49
|
+
04-catalog-analysis.md
|
|
50
|
+
05-content-brief.md
|
|
51
|
+
06-customization.md
|
|
52
|
+
07-build.md
|
|
53
|
+
08-security-audit.md
|
|
54
|
+
09-validation.md
|
|
55
|
+
10-ui-testing.md
|
|
56
|
+
summary.json
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**summary.json** is a machine-readable rollup. Create it at Step 0 with app metadata and an empty `steps` array. After each skill completes, read the file, append the new step entry, and write it back. After the final step, set `overallResult`, `filesWritten`, and `duration`.
|
|
60
|
+
|
|
61
|
+
## Parallel Execution
|
|
62
|
+
|
|
63
|
+
Use the `Task` tool to run independent steps concurrently.
|
|
64
|
+
|
|
65
|
+
| Group | Steps | Tasks | Independence |
|
|
66
|
+
|-------|-------|-------|-------------|
|
|
67
|
+
| A | Step 0 | Prompt validation + Web crawl | Read-only — no file writes |
|
|
68
|
+
| B | Step 5 | 2-3 screen batches | Each edits only its assigned screens |
|
|
69
|
+
| C | Step 7 | Security + Validation + Boot sims | Read-only scans + independent sim boot |
|
|
70
|
+
| D | Step 9 | 2-3 interactive test batches | Each tests on its own simulator |
|
|
71
|
+
| E | Step 10 | 2-3 Maestro batches | Each runs on its own simulator |
|
|
72
|
+
|
|
73
|
+
## Workflow
|
|
74
|
+
|
|
75
|
+
### Step 0 — [PARALLEL] Validation + Web Crawl
|
|
76
|
+
|
|
77
|
+
Dispatch two concurrent `Task` subagents:
|
|
78
|
+
|
|
79
|
+
1. **Prompt Validation** — follow `prompt-validator` skill. If FAIL, abort.
|
|
80
|
+
2. **Web Crawl** — follow `web-crawler` skill. Navigate to `{{url}}` using ai-tester MCP tools:
|
|
81
|
+
- Load the home page with `inspect({ platform: "web", url: "{{url}}", saveBaseline: "page-home", label: "Home" })`
|
|
82
|
+
- Systematically navigate the site (max ~10 pages, depth 2)
|
|
83
|
+
- Save screenshots to `output/{app-name}/web-crawl/screenshots/`
|
|
84
|
+
- Capture element trees, navigation structure, forms, brand signals
|
|
85
|
+
- Produce `reports/02-web-crawl-report.md`
|
|
86
|
+
|
|
87
|
+
Wait for both. If validation fails, abort.
|
|
88
|
+
|
|
89
|
+
### Step 1 — Web Analysis + Template Selection
|
|
90
|
+
|
|
91
|
+
**Web Analysis**: Follow `web-analyzer` skill. Read the crawl report AND view the saved screenshots (pass them as `attachments` to subagents). Produce `reports/03-web-to-app-manifest.md` with:
|
|
92
|
+
- Page-to-screen mapping (which web pages become which mobile screens)
|
|
93
|
+
- Navigation mapping (web nav → mobile tab bar / navigation stacks)
|
|
94
|
+
- Data model (entities discovered on the website)
|
|
95
|
+
- Brand tokens (colors, typography, spacing extracted from screenshots)
|
|
96
|
+
- Content brief (adapted website copy for mobile)
|
|
97
|
+
- Mock data spec (real content from the website)
|
|
98
|
+
- Screen change list and parallel batches
|
|
99
|
+
|
|
100
|
+
**Template Selection**: Follow `catalog-analyzer` skill. Use the web analysis to select the best template from CATALOG.md and shared modules from MODULES_CATALOG.md. The web-to-app manifest provides the "app description" for template matching.
|
|
101
|
+
|
|
102
|
+
### Step 2 — Clone + Rename
|
|
103
|
+
|
|
104
|
+
Clone the selected template:
|
|
105
|
+
```bash
|
|
106
|
+
cp -r templates/{platform}/{Template}/ output/{app-name}/
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
If `scripts/rename-template.sh` exists, run it. Otherwise follow `app-renaming` skill.
|
|
110
|
+
|
|
111
|
+
### Step 3 — Apply Website Brand + Content + Mock Data
|
|
112
|
+
|
|
113
|
+
Using the web-to-app manifest as the single source of truth:
|
|
114
|
+
1. **Design system** — follow `design-system` skill but use the brand tokens extracted from the website (colors, typography from the crawl report) instead of selecting from `designs/brands/`
|
|
115
|
+
2. **Content writing** — follow `content-writer` skill, adapting website copy for mobile
|
|
116
|
+
3. **AppConfig** — follow `appconfig-customization` skill
|
|
117
|
+
4. **Mock data** — follow `mock-data-update` skill, using real content observed on the website
|
|
118
|
+
5. **Module integration** — follow `module-integrator` skill if modules were selected
|
|
119
|
+
|
|
120
|
+
### Step 4 — [PARALLEL] Screen Customization
|
|
121
|
+
|
|
122
|
+
Using the manifest's screen batches, dispatch 2-3 concurrent `Task` subagents. Each receives:
|
|
123
|
+
- Its batch of screen files from the manifest
|
|
124
|
+
- The website screenshots for the pages it is implementing (as `attachments`)
|
|
125
|
+
- Instructions to match the website's visual style as closely as possible
|
|
126
|
+
|
|
127
|
+
Each subagent reads the platform-specific customizer skill in batch mode and ONLY edits its assigned screen files.
|
|
128
|
+
|
|
129
|
+
### Step 5 — Build
|
|
130
|
+
|
|
131
|
+
Follow `build-tester` skill. Compile, fix errors, locate build artifact. Max 3 rebuild cycles.
|
|
132
|
+
|
|
133
|
+
### Step 6 — [PARALLEL] Post-Build Audits + Boot Simulators
|
|
134
|
+
|
|
135
|
+
Dispatch three concurrent tasks:
|
|
136
|
+
1. **Security audit** — follow `code-auditor` skill
|
|
137
|
+
2. **Validation** — follow `output-validator` skill
|
|
138
|
+
3. **Boot simulators** — boot 2-3 simulators for testing
|
|
139
|
+
|
|
140
|
+
### Steps 7-10 — UI Testing (MANDATORY)
|
|
141
|
+
|
|
142
|
+
**Step 7: Install app** on all booted simulators.
|
|
143
|
+
|
|
144
|
+
**Step 8: [PARALLEL] Interactive testing** — dispatch subagents to test screen batches.
|
|
145
|
+
|
|
146
|
+
**Step 9: [PARALLEL] Maestro tests** — generate and run YAML test files.
|
|
147
|
+
|
|
148
|
+
**Step 10: Screenshots + cleanup**
|
|
149
|
+
- Capture one clean screenshot per main tab
|
|
150
|
+
- {{simulatorCleanup}}
|
|
151
|
+
|
|
152
|
+
## Screenshots as Visual Context
|
|
153
|
+
|
|
154
|
+
The web crawl saves screenshots at `output/{app-name}/web-crawl/screenshots/`. Pass these as `attachments` to downstream subagents so they can see the original website:
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
// Web analyzer sees all pages
|
|
158
|
+
Task({
|
|
159
|
+
prompt: "Analyze this website...",
|
|
160
|
+
attachments: ["output/{app-name}/web-crawl/screenshots/page-home.png", ...]
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
// Screen customizer sees relevant pages
|
|
164
|
+
Task({
|
|
165
|
+
prompt: "Customize ProductListView to match this website page...",
|
|
166
|
+
attachments: ["output/{app-name}/web-crawl/screenshots/page-products.png"]
|
|
167
|
+
})
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Rules
|
|
171
|
+
|
|
172
|
+
- ONLY write files under `output/` — NEVER modify `templates/`
|
|
173
|
+
- Preserve the MVVM architecture from the template
|
|
174
|
+
- Keep the MockDataProvider pattern — fill it with website content
|
|
175
|
+
- Keep the navigation structure functional
|
|
176
|
+
- Do NOT introduce new external dependencies
|
|
177
|
+
- Use ai-tester MCP tools for ALL device interaction (not shell commands)
|
|
178
|
+
- Brand tokens come from the crawled website, NOT from `designs/brands/`
|
|
179
|
+
- Match the website's visual identity as closely as possible in the native app
|
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
package com.appship.book.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,14 +1,24 @@
|
|
|
1
1
|
package com.appship.chat.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
|
+
}
|
|
@@ -129,7 +129,7 @@ fun ConversationsScreen(onChatClick: (String) -> Unit) {
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
@Composable
|
|
132
|
-
fun ConversationRow(conversation: Conversation, repository: com.appship.chat.data.DataRepository, onClick: () -> Unit, index: Int) {
|
|
132
|
+
fun ConversationRow(conversation: Conversation, repository: com.appship.chat.data.DataRepository, onClick: () -> Unit, index: Int, modifier: Modifier = Modifier) {
|
|
133
133
|
Row(
|
|
134
134
|
modifier = Modifier
|
|
135
135
|
.fillMaxWidth()
|