vibefast-cli 1.1.5 → 1.2.1
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/CHANGELOG.md +32 -0
- package/README.md +63 -169
- package/dist/commands/add.d.ts +1 -1
- package/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +547 -589
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/checklist.d.ts +1 -1
- package/dist/commands/checklist.d.ts.map +1 -1
- package/dist/commands/checklist.js +40 -39
- package/dist/commands/checklist.js.map +1 -1
- package/dist/commands/doctor.d.ts +1 -1
- package/dist/commands/doctor.js +22 -22
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/env.d.ts +1 -1
- package/dist/commands/env.d.ts.map +1 -1
- package/dist/commands/env.js +58 -53
- package/dist/commands/env.js.map +1 -1
- package/dist/commands/health.d.ts +1 -1
- package/dist/commands/health.d.ts.map +1 -1
- package/dist/commands/health.js +101 -93
- package/dist/commands/health.js.map +1 -1
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +416 -296
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/remove.d.ts +1 -1
- package/dist/commands/remove.d.ts.map +1 -1
- package/dist/commands/remove.js +77 -64
- package/dist/commands/remove.js.map +1 -1
- package/dist/commands/status.d.ts +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +15 -14
- package/dist/commands/status.js.map +1 -1
- package/dist/core/__tests__/detect.test.js +68 -34
- package/dist/core/__tests__/detect.test.js.map +1 -1
- package/dist/core/ast.d.ts +14 -0
- package/dist/core/ast.d.ts.map +1 -0
- package/dist/core/ast.js +239 -0
- package/dist/core/ast.js.map +1 -0
- package/dist/core/codemod.d.ts.map +1 -1
- package/dist/core/codemod.js +62 -44
- package/dist/core/codemod.js.map +1 -1
- package/dist/core/config.d.ts +10 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +51 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/detect.d.ts +8 -2
- package/dist/core/detect.d.ts.map +1 -1
- package/dist/core/detect.js +52 -21
- package/dist/core/detect.js.map +1 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +9 -8
- package/dist/core/errors.js.map +1 -1
- package/dist/core/exec.d.ts +16 -0
- package/dist/core/exec.d.ts.map +1 -0
- package/dist/core/exec.js +48 -0
- package/dist/core/exec.js.map +1 -0
- package/dist/core/manualSteps.d.ts +7 -0
- package/dist/core/manualSteps.d.ts.map +1 -0
- package/dist/core/manualSteps.js +59 -0
- package/dist/core/manualSteps.js.map +1 -0
- package/dist/core/paths.d.ts +3 -1
- package/dist/core/paths.d.ts.map +1 -1
- package/dist/core/paths.js +14 -10
- package/dist/core/paths.js.map +1 -1
- package/dist/core/spinner.d.ts +1 -1
- package/dist/core/spinner.d.ts.map +1 -1
- package/dist/core/spinner.js +38 -8
- package/dist/core/spinner.js.map +1 -1
- package/dist/core/vosk.d.ts.map +1 -1
- package/dist/core/vosk.js +50 -39
- package/dist/core/vosk.js.map +1 -1
- package/docs/manual-testing.md +91 -0
- package/package.json +6 -3
- package/recipes/audio-recorder/apps/native/src/app/audio-recorder/index.tsx +5 -0
- package/recipes/audio-recorder/recipe.json +3 -3
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/audio-player.tsx +301 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/audio-recorder.tsx +373 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/audio-waveform.tsx +270 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/index.ts +4 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/recording-list.tsx +89 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/audio-player-demo.tsx +66 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/audio-recorder-cloud.tsx +68 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/audio-recorder-interview.tsx +102 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/basic.tsx +27 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/index.ts +5 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/with-recording-list-demo.tsx +82 -0
- package/recipes/audio-recorder-supabase/packages/backend/src/services/recordings.ts +369 -0
- package/recipes/audio-recorder-supabase/packages/backend/supabase/migrations/recordings.sql +70 -0
- package/recipes/audio-recorder-supabase/recipe.json +35 -0
- package/recipes/audio-recorder-supabase@latest.zip +0 -0
- package/recipes/audio-recorder@latest.zip +0 -0
- package/recipes/charts/apps/native/src/features/charts/components/bar-chart.tsx +3 -3
- package/recipes/charts/apps/native/src/features/charts/components/candlestick-chart.tsx +2 -2
- package/recipes/charts/apps/native/src/features/charts/components/chart-card.tsx +5 -5
- package/recipes/charts/apps/native/src/features/charts/components/column-chart.tsx +3 -3
- package/recipes/charts/apps/native/src/features/charts/components/doughnut-chart.tsx +20 -4
- package/recipes/charts/apps/native/src/features/charts/components/line-chart.tsx +7 -6
- package/recipes/charts/apps/native/src/features/charts/components/radar-chart.tsx +6 -4
- package/recipes/charts/apps/native/src/features/charts/components/radial-bar-chart.tsx +1 -1
- package/recipes/charts/apps/native/src/features/charts/components/stacked-bar-chart.tsx +5 -4
- package/recipes/charts/recipe.json +4 -13
- package/recipes/charts@latest.zip +0 -0
- package/recipes/chatbot/apps/native/src/app/chatbot/index.tsx +1 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/chat-markdown.tsx +86 -86
- package/recipes/chatbot/apps/native/src/features/chatbot/components/markdown/code-block.tsx +86 -53
- package/recipes/chatbot/recipe.json +26 -92
- package/recipes/chatbot-supabase/apps/native/src/api-client/supabase/chatbot.ts +515 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/app/index.tsx +257 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-header-buttons.tsx +59 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-input-bar.tsx +485 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-markdown.tsx +575 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-message-bubble.tsx +223 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-settings-modal.tsx +161 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/image-preview-list.tsx +116 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/markdown/code-block.tsx +165 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/markdown/index.ts +10 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/markdown/table-renderer.tsx +129 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/message-error-boundary.tsx +78 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/message-list.tsx +170 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/model-selector.tsx +283 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/report-content-modal.tsx +188 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/suggested-messages.tsx +67 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/constants/models.ts +20 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/constants/report-reasons.ts +9 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-attachment-cache.ts +142 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-chat-config.ts +458 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-chat-handlers.ts +429 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-chatbot-settings.ts +89 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-conversation.ts +90 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-image-picker.ts +122 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-keyboard-coordinator.ts +161 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-smart-scroll-manager.ts +213 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/models/index.ts +86 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/models/models.ts +162 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/models/providers.ts +62 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/models/types.ts +40 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/services/file-uploader.ts +287 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/services/message-handler-service.ts +189 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/types/index.ts +70 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/utils/chat-telemetry.ts +91 -0
- package/recipes/chatbot-supabase/packages/backend/src/services/conversations.ts +243 -0
- package/recipes/chatbot-supabase/packages/backend/src/services/messages.ts +327 -0
- package/recipes/chatbot-supabase/packages/backend/supabase/functions/chat-stream/index.ts +347 -0
- package/recipes/chatbot-supabase/packages/backend/supabase/migrations/chatbot.sql +104 -0
- package/recipes/chatbot-supabase/recipe.json +79 -0
- package/recipes/chatbot-supabase@latest.zip +0 -0
- package/recipes/chatbot.zip +0 -0
- package/recipes/chatbot@latest.zip +0 -0
- package/recipes/image-analysis/packages/backend/convex/imageAnalysis/index.ts +2 -2
- package/recipes/image-analysis/packages/backend/convex/imageAnalysis.ts +0 -1
- package/recipes/image-analysis/recipe.json +15 -55
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/analysis-options-screen.tsx +304 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/camera.tsx +221 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/image-capture-screen.tsx +333 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/loading-screen.tsx +214 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/loading.tsx +191 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/results.tsx +137 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/trait-details.tsx +172 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/use-analysis-data.ts +160 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/use-results-screen.ts +151 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/achievement-badge.tsx +77 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/achievement-card.tsx +75 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/achievement-unlocked-modal.tsx +162 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/achievements-section.tsx +44 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/advice-list.tsx +42 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/circular-progress.tsx +233 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/content-card.tsx +38 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/error-state.tsx +42 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/index.ts +9 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/loading-state.tsx +26 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/profile-image.tsx +60 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/results-header.tsx +62 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/score-display.tsx +54 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/share-options-modal.tsx +110 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/traits-grid.tsx +74 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/config/analysis-config.ts +80 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/config/master-analysis-config.ts +157 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/hooks/index.ts +1 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/hooks/use-analysis.ts +38 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/hooks/use-image-analysis.ts +208 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/services/analysis-service.ts +262 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/services/share-service.ts +176 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/services/trait-details-service.ts +289 -0
- package/recipes/image-analysis-supabase/packages/backend/src/services/image-analyses.ts +132 -0
- package/recipes/image-analysis-supabase/packages/backend/supabase/functions/analyze-image/index.ts +312 -0
- package/recipes/image-analysis-supabase/packages/backend/supabase/migrations/image_analysis.sql +42 -0
- package/recipes/image-analysis-supabase/recipe.json +57 -0
- package/recipes/image-analysis-supabase@latest.zip +0 -0
- package/recipes/image-analysis@latest.zip +0 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/app/index.tsx +16 -2
- package/recipes/image-generator/apps/native/src/features/image-generator/components/image-model-selector.tsx +11 -5
- package/recipes/image-generator/apps/native/src/features/image-generator/hooks/use-image-generator.ts +11 -5
- package/recipes/image-generator/packages/backend/convex/imageGeneration/index.ts +2 -2
- package/recipes/image-generator/recipe.json +16 -39
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/app/_layout.tsx +26 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/app/gallery.tsx +217 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/app/index.tsx +251 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/components/gallery-image.tsx +25 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/components/image-detail-modal.tsx +215 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/components/image-model-selector.tsx +216 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/components/image-placeholder.tsx +26 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/hooks/use-image-gallery.ts +71 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/hooks/use-image-generator-settings.ts +152 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/hooks/use-image-generator.ts +103 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/models/models.ts +66 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/services/image-gallery-service.ts +96 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/services/image-save-service.ts +120 -0
- package/recipes/image-generator-supabase/packages/backend/supabase/functions/generate-image/index.ts +291 -0
- package/recipes/image-generator-supabase/packages/backend/supabase/migrations/image_generator.sql +71 -0
- package/recipes/image-generator-supabase/recipe.json +59 -0
- package/recipes/image-generator-supabase@latest.zip +0 -0
- package/recipes/image-generator@latest.zip +0 -0
- package/recipes/ios-widget/recipe.json +15 -24
- package/recipes/ios-widget@latest.zip +0 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/analytics/index.ts +9 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/components/onboarding-with-analytics.tsx +141 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/components/onboarding.tsx +173 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/config/onboarding-flow-config.ts +189 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/demo-one/app/index.tsx +42 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/demo-one/data.ts +32 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/app/index.tsx +43 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/interactive-onboarding.tsx +222 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/ai-tone-step.tsx +133 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/currency-step.tsx +165 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-ai-step.tsx +199 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-chatbot-step.tsx +154 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-manual-step.tsx +156 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-scan-step.tsx +158 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/main-reason-step.tsx +139 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/notification-step.tsx +129 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/overspend-step.tsx +138 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/personalizing-step.tsx +190 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/rating-step.tsx +98 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/reminder-step.tsx +181 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/safety-step.tsx +110 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/struggle-step.tsx +139 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/welcome-step.tsx +217 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/ui/onboarding-header.tsx +58 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/constants.ts +179 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/hooks/use-onboarding-analytics.ts +323 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/services/onboarding-analytics.ts +432 -0
- package/recipes/onboarding/recipe.json +15 -0
- package/recipes/onboarding@latest.zip +0 -0
- package/recipes/payments/recipe.json +28 -61
- package/recipes/payments-supabase/apps/native/src/features/payments/README.md +200 -0
- package/recipes/payments-supabase/apps/native/src/features/payments/app/local-paywall.tsx +194 -0
- package/recipes/payments-supabase/apps/native/src/features/payments/app/remote-paywall.tsx +79 -0
- package/recipes/payments-supabase/apps/native/src/features/payments/components/payment-initializer.tsx +95 -0
- package/recipes/payments-supabase/apps/native/src/features/payments/components/paywall-error-state.tsx +60 -0
- package/recipes/payments-supabase/apps/native/src/features/payments/components/paywall-local-mode.tsx +116 -0
- package/recipes/payments-supabase/apps/native/src/features/payments/components/paywall-product-card.tsx +133 -0
- package/recipes/payments-supabase/apps/native/src/features/payments/components/paywall-remote-mode.tsx +146 -0
- package/recipes/payments-supabase/apps/native/src/features/payments/hooks/use-entitlement.ts +63 -0
- package/recipes/payments-supabase/apps/native/src/features/payments/index.ts +8 -0
- package/recipes/payments-supabase/apps/native/src/features/payments/services/revenuecat-adapter.ts +407 -0
- package/recipes/payments-supabase/packages/backend/src/services/payments.ts +201 -0
- package/recipes/payments-supabase/packages/backend/supabase/migrations/payments.sql +35 -0
- package/recipes/payments-supabase/recipe.json +51 -0
- package/recipes/payments-supabase@latest.zip +0 -0
- package/recipes/payments@latest.zip +0 -0
- package/recipes/quiz/apps/native/src/features/quiz/index.tsx +1 -2
- package/recipes/quiz/recipe.json +6 -9
- package/recipes/quiz@latest.zip +0 -0
- package/recipes/tracker-app/apps/native/src/features/tracker-app/app/index.tsx +1 -2
- package/recipes/tracker-app/recipe.json +7 -10
- package/recipes/tracker-app@latest.zip +0 -0
- package/recipes/voice-bot/recipe.json +8 -68
- package/recipes/voice-bot.zip +0 -0
- package/recipes/voice-bot@latest.zip +0 -0
- package/recipes/wake-word/recipe.json +10 -9
- package/recipes/wake-word.zip +0 -0
- package/recipes/wake-word@latest.zip +0 -0
- package/recipes/charts/apps/native/src/app/(root)/(protected)/charts/index.tsx +0 -3
- package/recipes/chatbot/packages/backend/convex/lib/rateLimit.ts +0 -100
- package/recipes/chatbot/packages/backend/convex/lib/telemetry.ts +0 -29
- package/recipes/chatbot/packages/backend/convex/ragKnowledge.ts +0 -0
- package/recipes/image-analysis/apps/native/assets/features/image-analyzer/front.jpg +0 -0
- package/recipes/image-analysis/apps/native/assets/features/image-analyzer/side.jpg +0 -0
- package/recipes/image-analysis/apps/native/assets/features/image-analyzer/threeQuarter.jpg +0 -0
- package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/_layout.tsx +0 -5
- package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/analysis-options.tsx +0 -50
- package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/camera.tsx +0 -2
- package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/index.tsx +0 -50
- package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/loading.tsx +0 -50
- package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/results.tsx +0 -2
- package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/trait-details.tsx +0 -3
- package/recipes/image-analysis/packages/backend/convex/imageAnalysisFunctions.ts +0 -325
- package/recipes/image-analysis/packages/backend/convex/lib/ai/imageAnalysisAdapter.ts +0 -200
- package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/index.tsx +0 -74
- package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/local.tsx +0 -25
- package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/remote.tsx +0 -23
- package/recipes/quiz/apps/native/src/app/(root)/(protected)/quiz/index.tsx +0 -47
- package/recipes/tracker-app/apps/native/src/app/(root)/(protected)/tracker-app/index.tsx +0 -1
- package/recipes/voice-bot/apps/native/src/app/(root)/(protected)/voice-bot/index.tsx +0 -27
- package/recipes/voice-bot/packages/backend/convex/router.ts +0 -81
- /package/recipes/{chatbot/apps/native/src/app/(root)/(protected) → chatbot-supabase/apps/native/src/app}/chatbot/index.tsx +0 -0
- /package/recipes/{image-generator/apps/native/src/app/(root)/(protected) → image-generator-supabase/apps/native/src/app}/image-generator/gallery.tsx +0 -0
- /package/recipes/{image-generator/apps/native/src/app/(root)/(protected) → image-generator-supabase/apps/native/src/app}/image-generator/index.tsx +0 -0
package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/camera.tsx
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { MaterialIcons } from '@expo/vector-icons';
|
|
2
|
+
import { type CameraType, CameraView, useCameraPermissions } from 'expo-camera';
|
|
3
|
+
import { Stack, useLocalSearchParams, useRouter } from 'expo-router';
|
|
4
|
+
import React, { useCallback, useRef, useState } from 'react';
|
|
5
|
+
import { ActivityIndicator, Alert, TouchableOpacity, View } from 'react-native';
|
|
6
|
+
|
|
7
|
+
import { Button, Image, Text } from '@/components/ui';
|
|
8
|
+
import { useCameraCaptureStore } from '@/features/image-analyzer/hooks/use-image-analysis';
|
|
9
|
+
|
|
10
|
+
type CameraScreenParams = {
|
|
11
|
+
stepId: string;
|
|
12
|
+
stepLabel: string;
|
|
13
|
+
stepDescription: string;
|
|
14
|
+
guideText: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Dedicated camera screen for capturing images with frame overlay guide.
|
|
19
|
+
* Based on coin-identifier-real camera implementation.
|
|
20
|
+
*/
|
|
21
|
+
export default function CameraScreen() {
|
|
22
|
+
const router = useRouter();
|
|
23
|
+
const params = useLocalSearchParams() as CameraScreenParams;
|
|
24
|
+
const setCapturedImage = useCameraCaptureStore(
|
|
25
|
+
(state) => state.setCapturedImage,
|
|
26
|
+
);
|
|
27
|
+
const [permission, requestPermission] = useCameraPermissions();
|
|
28
|
+
const [facing, setFacing] = useState<CameraType>('back');
|
|
29
|
+
const [capturedImageUri, setCapturedImageUri] = useState<string | null>(null);
|
|
30
|
+
const [isCapturing, setIsCapturing] = useState(false);
|
|
31
|
+
const cameraRef = useRef<CameraView>(null);
|
|
32
|
+
|
|
33
|
+
// Handle photo capture
|
|
34
|
+
const handleTakePhoto = useCallback(async () => {
|
|
35
|
+
if (!cameraRef.current || isCapturing) return;
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
setIsCapturing(true);
|
|
39
|
+
const photo = await cameraRef.current.takePictureAsync({
|
|
40
|
+
quality: 0.8,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
if (photo?.uri) {
|
|
44
|
+
setCapturedImageUri(photo.uri);
|
|
45
|
+
}
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error('Error taking photo:', error);
|
|
48
|
+
Alert.alert('Error', 'Failed to take photo. Please try again.');
|
|
49
|
+
} finally {
|
|
50
|
+
setIsCapturing(false);
|
|
51
|
+
}
|
|
52
|
+
}, [isCapturing]);
|
|
53
|
+
|
|
54
|
+
// Handle retake
|
|
55
|
+
const handleRetake = useCallback(() => {
|
|
56
|
+
setCapturedImageUri(null);
|
|
57
|
+
}, []);
|
|
58
|
+
|
|
59
|
+
// Handle accept photo
|
|
60
|
+
const handleAccept = useCallback(() => {
|
|
61
|
+
if (capturedImageUri) {
|
|
62
|
+
// Store the captured image in Zustand store
|
|
63
|
+
setCapturedImage(capturedImageUri, params.stepId);
|
|
64
|
+
// Simply navigate back
|
|
65
|
+
router.back();
|
|
66
|
+
}
|
|
67
|
+
}, [capturedImageUri, setCapturedImage, params.stepId, router]);
|
|
68
|
+
|
|
69
|
+
// Toggle camera facing
|
|
70
|
+
const toggleCameraFacing = useCallback(() => {
|
|
71
|
+
setFacing((current) => (current === 'back' ? 'front' : 'back'));
|
|
72
|
+
}, []);
|
|
73
|
+
|
|
74
|
+
// Check camera permissions
|
|
75
|
+
if (!permission) {
|
|
76
|
+
return (
|
|
77
|
+
<View className="flex-1 items-center justify-center bg-black">
|
|
78
|
+
<ActivityIndicator size="large" color="white" />
|
|
79
|
+
</View>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!permission.granted) {
|
|
84
|
+
return (
|
|
85
|
+
<View className="flex-1 items-center justify-center bg-black px-6">
|
|
86
|
+
<Text className="mb-4 text-center text-white">
|
|
87
|
+
Camera permission is required to take photos
|
|
88
|
+
</Text>
|
|
89
|
+
<Button
|
|
90
|
+
label="Grant Permission"
|
|
91
|
+
onPress={requestPermission}
|
|
92
|
+
variant="outline"
|
|
93
|
+
/>
|
|
94
|
+
</View>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Render photo preview
|
|
99
|
+
if (capturedImageUri) {
|
|
100
|
+
return (
|
|
101
|
+
<>
|
|
102
|
+
<Stack.Screen options={{ headerShown: false }} />
|
|
103
|
+
<View className="flex-1 bg-black">
|
|
104
|
+
<Image
|
|
105
|
+
source={{ uri: capturedImageUri }}
|
|
106
|
+
className="flex-1"
|
|
107
|
+
contentFit="contain"
|
|
108
|
+
/>
|
|
109
|
+
|
|
110
|
+
{/* Overlay with step info */}
|
|
111
|
+
<View className="absolute inset-x-0 top-12 px-6">
|
|
112
|
+
<View className="rounded-xl bg-black/70 p-4">
|
|
113
|
+
<Text className="text-center text-lg font-bold text-white">
|
|
114
|
+
{params.stepLabel}
|
|
115
|
+
</Text>
|
|
116
|
+
<Text className="text-center text-sm text-white/80">
|
|
117
|
+
{params.stepDescription}
|
|
118
|
+
</Text>
|
|
119
|
+
</View>
|
|
120
|
+
</View>
|
|
121
|
+
|
|
122
|
+
{/* Action buttons */}
|
|
123
|
+
<View className="absolute inset-x-0 bottom-12 flex-row justify-center gap-x-4 px-6">
|
|
124
|
+
<Button
|
|
125
|
+
label="Retake"
|
|
126
|
+
variant="outline"
|
|
127
|
+
onPress={handleRetake}
|
|
128
|
+
className="flex-1 border-slate-500 bg-slate-800"
|
|
129
|
+
textClassName="text-black"
|
|
130
|
+
/>
|
|
131
|
+
<Button
|
|
132
|
+
label="Accept"
|
|
133
|
+
onPress={handleAccept}
|
|
134
|
+
className="flex-1"
|
|
135
|
+
style={{ backgroundColor: '#059669' }}
|
|
136
|
+
/>
|
|
137
|
+
</View>
|
|
138
|
+
</View>
|
|
139
|
+
</>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Render camera view
|
|
144
|
+
return (
|
|
145
|
+
<>
|
|
146
|
+
<Stack.Screen options={{ headerShown: false }} />
|
|
147
|
+
<View className="flex-1 bg-black">
|
|
148
|
+
<CameraView
|
|
149
|
+
ref={cameraRef}
|
|
150
|
+
style={{ flex: 1 }}
|
|
151
|
+
facing={facing}
|
|
152
|
+
mode="picture"
|
|
153
|
+
>
|
|
154
|
+
{/* Header with step info */}
|
|
155
|
+
<View className="absolute inset-x-0 top-12 px-6">
|
|
156
|
+
<View className="rounded-xl bg-black/70 p-4">
|
|
157
|
+
<Text className="text-center text-lg font-bold text-white">
|
|
158
|
+
{params.stepLabel}
|
|
159
|
+
</Text>
|
|
160
|
+
<Text className="text-center text-sm text-white/80">
|
|
161
|
+
📸 {params.guideText}
|
|
162
|
+
</Text>
|
|
163
|
+
</View>
|
|
164
|
+
</View>
|
|
165
|
+
|
|
166
|
+
{/* Frame guide overlay */}
|
|
167
|
+
<View className="absolute inset-0 items-center justify-center">
|
|
168
|
+
<View className="relative size-80">
|
|
169
|
+
{/* Frame */}
|
|
170
|
+
<View className="absolute inset-0 rounded-2xl border-2 border-white/60" />
|
|
171
|
+
|
|
172
|
+
{/* Corner guides */}
|
|
173
|
+
<View className="absolute left-2 top-2 size-6 border-l-4 border-t-4 border-white" />
|
|
174
|
+
<View className="absolute right-2 top-2 size-6 border-r-4 border-t-4 border-white" />
|
|
175
|
+
<View className="absolute bottom-2 left-2 size-6 border-b-4 border-l-4 border-white" />
|
|
176
|
+
<View className="absolute bottom-2 right-2 size-6 border-b-4 border-r-4 border-white" />
|
|
177
|
+
|
|
178
|
+
{/* Center guide */}
|
|
179
|
+
<View className="absolute inset-0 items-center justify-center">
|
|
180
|
+
<View className="h-1 w-8 bg-white/60" />
|
|
181
|
+
<View className="absolute h-8 w-1 bg-white/60" />
|
|
182
|
+
</View>
|
|
183
|
+
</View>
|
|
184
|
+
</View>
|
|
185
|
+
|
|
186
|
+
{/* Controls */}
|
|
187
|
+
<View className="absolute inset-x-0 bottom-12 flex-row items-center justify-center px-6">
|
|
188
|
+
{/* Close button */}
|
|
189
|
+
<TouchableOpacity
|
|
190
|
+
onPress={() => router.back()}
|
|
191
|
+
className="absolute left-6 size-12 items-center justify-center rounded-full bg-black/50"
|
|
192
|
+
>
|
|
193
|
+
<Text className="text-xl text-white">✕</Text>
|
|
194
|
+
</TouchableOpacity>
|
|
195
|
+
|
|
196
|
+
{/* Shutter button */}
|
|
197
|
+
<TouchableOpacity
|
|
198
|
+
onPress={handleTakePhoto}
|
|
199
|
+
disabled={isCapturing}
|
|
200
|
+
className="size-20 items-center justify-center rounded-full border-4 border-white bg-white/20"
|
|
201
|
+
>
|
|
202
|
+
{isCapturing ? (
|
|
203
|
+
<ActivityIndicator size="large" color="white" />
|
|
204
|
+
) : (
|
|
205
|
+
<View className="size-16 rounded-full bg-white" />
|
|
206
|
+
)}
|
|
207
|
+
</TouchableOpacity>
|
|
208
|
+
|
|
209
|
+
{/* Flip camera button */}
|
|
210
|
+
<TouchableOpacity
|
|
211
|
+
onPress={toggleCameraFacing}
|
|
212
|
+
className="absolute right-6 size-12 items-center justify-center rounded-full bg-black/50"
|
|
213
|
+
>
|
|
214
|
+
<MaterialIcons name="cameraswitch" size={24} color="white" />
|
|
215
|
+
</TouchableOpacity>
|
|
216
|
+
</View>
|
|
217
|
+
</CameraView>
|
|
218
|
+
</View>
|
|
219
|
+
</>
|
|
220
|
+
);
|
|
221
|
+
}
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import * as ImagePicker from 'expo-image-picker';
|
|
2
|
+
import { Stack, useRouter } from 'expo-router';
|
|
3
|
+
import React, { useCallback, useEffect, useState } from 'react';
|
|
4
|
+
import { Alert, ScrollView, TouchableOpacity, View } from 'react-native';
|
|
5
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
6
|
+
|
|
7
|
+
import { Button, Image, Text } from '@/components/ui';
|
|
8
|
+
import type { AnalysisFlowConfig } from '@/features/image-analyzer/config/master-analysis-config';
|
|
9
|
+
import { useCameraCaptureStore } from '@/features/image-analyzer/hooks/use-image-analysis';
|
|
10
|
+
import { translate } from '@/lib';
|
|
11
|
+
|
|
12
|
+
export type CapturedImage = {
|
|
13
|
+
uri: string;
|
|
14
|
+
stepId: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type ImageCaptureScreenProps = {
|
|
18
|
+
config: AnalysisFlowConfig;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Multi-step image capture screen that works with any analysis configuration.
|
|
23
|
+
* Guides users through capturing photos based on the provided config.
|
|
24
|
+
*/
|
|
25
|
+
export function ImageCaptureScreen({ config }: ImageCaptureScreenProps) {
|
|
26
|
+
const router = useRouter();
|
|
27
|
+
const { capturedImageUri, stepId, clearCapturedImage } =
|
|
28
|
+
useCameraCaptureStore();
|
|
29
|
+
const [currentStepIndex, setCurrentStepIndex] = useState(0);
|
|
30
|
+
const [capturedImages, setCapturedImages] = useState<
|
|
31
|
+
(CapturedImage | null)[]
|
|
32
|
+
>(new Array(config.photoSteps.length).fill(null));
|
|
33
|
+
|
|
34
|
+
const currentStep = config.photoSteps[currentStepIndex];
|
|
35
|
+
const allImagesCaptured = capturedImages.every((img) => img !== null);
|
|
36
|
+
|
|
37
|
+
// Handle incoming captured image from camera screen via Zustand store
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (capturedImageUri && stepId) {
|
|
40
|
+
// Find which step this image belongs to
|
|
41
|
+
const capturedStepIndex = config.photoSteps.findIndex(
|
|
42
|
+
(step) => step.id === stepId,
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
if (capturedStepIndex === -1) {
|
|
46
|
+
console.error('❌ Invalid stepId:', stepId);
|
|
47
|
+
clearCapturedImage();
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Update the captured images array
|
|
52
|
+
setCapturedImages((prevImages) => {
|
|
53
|
+
const newCapturedImages = [...prevImages];
|
|
54
|
+
newCapturedImages[capturedStepIndex] = {
|
|
55
|
+
uri: capturedImageUri,
|
|
56
|
+
stepId: stepId,
|
|
57
|
+
};
|
|
58
|
+
return newCapturedImages;
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Only auto-advance if we're currently viewing the step we just captured
|
|
62
|
+
// This prevents jumping when user manually navigates to previous steps
|
|
63
|
+
setCurrentStepIndex((currentIndex) => {
|
|
64
|
+
if (currentIndex === capturedStepIndex) {
|
|
65
|
+
// We're on the step we just captured, advance to next if not last
|
|
66
|
+
const isLastStep = capturedStepIndex === config.photoSteps.length - 1;
|
|
67
|
+
return isLastStep ? capturedStepIndex : capturedStepIndex + 1;
|
|
68
|
+
}
|
|
69
|
+
// User is viewing a different step, don't auto-advance
|
|
70
|
+
return currentIndex;
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
clearCapturedImage();
|
|
74
|
+
}
|
|
75
|
+
}, [capturedImageUri, stepId, clearCapturedImage, config.photoSteps]);
|
|
76
|
+
|
|
77
|
+
// Handle camera launch
|
|
78
|
+
const handleOpenCamera = useCallback(async () => {
|
|
79
|
+
try {
|
|
80
|
+
const { status } = await ImagePicker.requestCameraPermissionsAsync();
|
|
81
|
+
if (status !== 'granted') {
|
|
82
|
+
Alert.alert(
|
|
83
|
+
'Permission Required',
|
|
84
|
+
'Camera permission is required to take photos.',
|
|
85
|
+
);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
router.push({
|
|
90
|
+
pathname: '/analysis/[type]/camera' as any,
|
|
91
|
+
params: {
|
|
92
|
+
type: config.id,
|
|
93
|
+
stepId: currentStep.id,
|
|
94
|
+
stepLabel: currentStep.label,
|
|
95
|
+
stepDescription: currentStep.description,
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
} catch (error) {
|
|
99
|
+
console.error('Error requesting camera permission:', error);
|
|
100
|
+
Alert.alert('Error', 'Failed to open camera. Please try again.');
|
|
101
|
+
}
|
|
102
|
+
}, [currentStep, router, config.id]);
|
|
103
|
+
|
|
104
|
+
// Handle gallery selection
|
|
105
|
+
const handleSelectFromGallery = useCallback(async () => {
|
|
106
|
+
try {
|
|
107
|
+
const { status } =
|
|
108
|
+
await ImagePicker.requestMediaLibraryPermissionsAsync();
|
|
109
|
+
if (status !== 'granted') {
|
|
110
|
+
Alert.alert(
|
|
111
|
+
'Permission Required',
|
|
112
|
+
'Photo library permission is required to select images.',
|
|
113
|
+
);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const result = await ImagePicker.launchImageLibraryAsync({
|
|
118
|
+
mediaTypes: ImagePicker.MediaTypeOptions.Images,
|
|
119
|
+
allowsEditing: true,
|
|
120
|
+
aspect: [4, 3],
|
|
121
|
+
quality: 0.8,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
if (!result.canceled && result.assets[0]) {
|
|
125
|
+
const imageUri = result.assets[0].uri;
|
|
126
|
+
|
|
127
|
+
setCapturedImages((prevImages) => {
|
|
128
|
+
const newCapturedImages = [...prevImages];
|
|
129
|
+
newCapturedImages[currentStepIndex] = {
|
|
130
|
+
uri: imageUri,
|
|
131
|
+
stepId: currentStep.id,
|
|
132
|
+
};
|
|
133
|
+
return newCapturedImages;
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Auto-advance to next step if not last
|
|
137
|
+
const isLastStep = currentStepIndex === config.photoSteps.length - 1;
|
|
138
|
+
if (!isLastStep) {
|
|
139
|
+
setCurrentStepIndex(currentStepIndex + 1);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
} catch (error) {
|
|
143
|
+
console.error('Error selecting from gallery:', error);
|
|
144
|
+
Alert.alert('Error', 'Failed to select image. Please try again.');
|
|
145
|
+
}
|
|
146
|
+
}, [currentStepIndex, currentStep.id, config.photoSteps.length]);
|
|
147
|
+
|
|
148
|
+
// Handle step switching - allow clicking any step freely
|
|
149
|
+
const handleStepSwitch = useCallback((targetIndex: number) => {
|
|
150
|
+
setCurrentStepIndex(targetIndex);
|
|
151
|
+
}, []);
|
|
152
|
+
|
|
153
|
+
// Handle continue - either to options or directly to loading based on config
|
|
154
|
+
const handleContinue = useCallback(() => {
|
|
155
|
+
const imageUris = capturedImages.map((img) => img!.uri);
|
|
156
|
+
|
|
157
|
+
if (config.skipOptionsScreen) {
|
|
158
|
+
// Go directly to loading with default preferences
|
|
159
|
+
router.push({
|
|
160
|
+
pathname: '/analysis/[type]/loading' as any,
|
|
161
|
+
params: {
|
|
162
|
+
type: config.id,
|
|
163
|
+
imageUris: JSON.stringify(imageUris),
|
|
164
|
+
analysisGoal: config.defaultPreferences?.goal || 'comprehensive',
|
|
165
|
+
feedbackStyle:
|
|
166
|
+
config.defaultPreferences?.feedbackStyle || 'professional',
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
} else {
|
|
170
|
+
// Go to options screen
|
|
171
|
+
router.push({
|
|
172
|
+
pathname: '/analysis/[type]/analysis-options' as any,
|
|
173
|
+
params: {
|
|
174
|
+
type: config.id,
|
|
175
|
+
imageUris: JSON.stringify(imageUris),
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}, [capturedImages, router, config]);
|
|
180
|
+
|
|
181
|
+
const insets = useSafeAreaInsets();
|
|
182
|
+
|
|
183
|
+
return (
|
|
184
|
+
<>
|
|
185
|
+
<Stack.Screen
|
|
186
|
+
options={{
|
|
187
|
+
title: `${config.name} ${translate('analysis.setup_title')}`,
|
|
188
|
+
headerBackButtonDisplayMode: 'generic',
|
|
189
|
+
}}
|
|
190
|
+
/>
|
|
191
|
+
<ScrollView
|
|
192
|
+
className="flex-1"
|
|
193
|
+
contentContainerStyle={{ paddingBottom: 140 + insets.bottom }}
|
|
194
|
+
showsVerticalScrollIndicator={false}
|
|
195
|
+
>
|
|
196
|
+
<View className="px-6 pt-4">
|
|
197
|
+
{/* Connected Step Progress Indicator */}
|
|
198
|
+
<View className="mb-8">
|
|
199
|
+
<View className="relative flex-row items-center justify-between">
|
|
200
|
+
{config.photoSteps.map((_, index) => (
|
|
201
|
+
<React.Fragment key={index}>
|
|
202
|
+
{/* Step Circle */}
|
|
203
|
+
<TouchableOpacity
|
|
204
|
+
onPress={() => handleStepSwitch(index)}
|
|
205
|
+
className={`relative z-10 size-16 items-center justify-center rounded-full border-2 ${
|
|
206
|
+
capturedImages[index] !== null
|
|
207
|
+
? 'border-green-500 bg-green-500'
|
|
208
|
+
: index === currentStepIndex
|
|
209
|
+
? 'border-primary-600 bg-primary-600'
|
|
210
|
+
: 'border-neutral-300 bg-white dark:border-neutral-600 dark:bg-neutral-800'
|
|
211
|
+
}`}
|
|
212
|
+
>
|
|
213
|
+
{capturedImages[index] !== null ? (
|
|
214
|
+
<Text className="text-2xl font-bold text-white">✓</Text>
|
|
215
|
+
) : index === currentStepIndex ? (
|
|
216
|
+
<Text className="text-xl font-semibold text-white">
|
|
217
|
+
{index + 1}
|
|
218
|
+
</Text>
|
|
219
|
+
) : (
|
|
220
|
+
<Text className="text-xl font-semibold text-neutral-500 dark:text-neutral-400">
|
|
221
|
+
{index + 1}
|
|
222
|
+
</Text>
|
|
223
|
+
)}
|
|
224
|
+
</TouchableOpacity>
|
|
225
|
+
|
|
226
|
+
{/* Connecting Line */}
|
|
227
|
+
{index < config.photoSteps.length - 1 && (
|
|
228
|
+
<View
|
|
229
|
+
className={`mx-3 h-0.5 flex-1 ${
|
|
230
|
+
capturedImages[index] !== null
|
|
231
|
+
? 'bg-green-500'
|
|
232
|
+
: index < currentStepIndex
|
|
233
|
+
? 'bg-primary-600'
|
|
234
|
+
: 'bg-neutral-300 dark:bg-neutral-600'
|
|
235
|
+
}`}
|
|
236
|
+
/>
|
|
237
|
+
)}
|
|
238
|
+
</React.Fragment>
|
|
239
|
+
))}
|
|
240
|
+
</View>
|
|
241
|
+
|
|
242
|
+
{/* Step Labels */}
|
|
243
|
+
{/* <View className="mt-4 flex-row justify-between px-2">
|
|
244
|
+
{config.photoSteps.map((step, index) => (
|
|
245
|
+
<View key={step.id} className="w-28 items-center">
|
|
246
|
+
<Text
|
|
247
|
+
numberOfLines={1}
|
|
248
|
+
className={`text-center text-xs font-medium ${
|
|
249
|
+
capturedImages[index] !== null
|
|
250
|
+
? 'text-green-600'
|
|
251
|
+
: index === currentStepIndex
|
|
252
|
+
? 'text-primary-600'
|
|
253
|
+
: 'text-neutral-500 dark:text-neutral-400'
|
|
254
|
+
}`}
|
|
255
|
+
>
|
|
256
|
+
{step.label}
|
|
257
|
+
</Text>
|
|
258
|
+
</View>
|
|
259
|
+
))}
|
|
260
|
+
</View> */}
|
|
261
|
+
</View>
|
|
262
|
+
|
|
263
|
+
{/* Current Step Info */}
|
|
264
|
+
<View className="mb-8 rounded-3xl bg-card p-5 dark:bg-card">
|
|
265
|
+
<View className="mb-1 ml-2 flex-row items-center">
|
|
266
|
+
<Text className="flex-1 text-lg font-semibold text-neutral-900 dark:text-neutral-100">
|
|
267
|
+
{currentStep.label}
|
|
268
|
+
</Text>
|
|
269
|
+
</View>
|
|
270
|
+
<Text className="mb-4 ml-2 text-neutral-600 dark:text-neutral-300">
|
|
271
|
+
{currentStep.description}
|
|
272
|
+
</Text>
|
|
273
|
+
|
|
274
|
+
{/* Guide Image or Captured Image */}
|
|
275
|
+
<Image
|
|
276
|
+
source={
|
|
277
|
+
capturedImages[currentStepIndex]
|
|
278
|
+
? { uri: capturedImages[currentStepIndex]!.uri }
|
|
279
|
+
: currentStep.guideImage
|
|
280
|
+
}
|
|
281
|
+
className="aspect-square w-full rounded-3xl"
|
|
282
|
+
contentFit="cover"
|
|
283
|
+
/>
|
|
284
|
+
</View>
|
|
285
|
+
</View>
|
|
286
|
+
</ScrollView>
|
|
287
|
+
|
|
288
|
+
{/* Bottom Action Bar */}
|
|
289
|
+
<View
|
|
290
|
+
className="absolute inset-x-0 bottom-0 bg-white px-6 py-4 shadow-lg dark:bg-neutral-900"
|
|
291
|
+
style={{ paddingBottom: insets.bottom }}
|
|
292
|
+
>
|
|
293
|
+
{/* Action Buttons */}
|
|
294
|
+
<View className="mb-1 flex-row gap-x-3">
|
|
295
|
+
<Button
|
|
296
|
+
onPress={handleOpenCamera}
|
|
297
|
+
label={
|
|
298
|
+
capturedImages[currentStepIndex] ? 'Retake Photo' : 'Take Photo'
|
|
299
|
+
}
|
|
300
|
+
className="flex-1 bg-primary-600"
|
|
301
|
+
textClassName="text-white font-semibold"
|
|
302
|
+
/>
|
|
303
|
+
|
|
304
|
+
<Button
|
|
305
|
+
onPress={handleSelectFromGallery}
|
|
306
|
+
label="Gallery"
|
|
307
|
+
variant="outline"
|
|
308
|
+
className="flex-1 border-neutral-300 dark:border-neutral-600"
|
|
309
|
+
textClassName="text-neutral-700 dark:text-neutral-300 text-center"
|
|
310
|
+
/>
|
|
311
|
+
</View>
|
|
312
|
+
|
|
313
|
+
<Button
|
|
314
|
+
onPress={handleContinue}
|
|
315
|
+
disabled={!allImagesCaptured}
|
|
316
|
+
label={
|
|
317
|
+
config.skipOptionsScreen ? 'Start Analysis' : 'Continue to Options'
|
|
318
|
+
}
|
|
319
|
+
className={`${
|
|
320
|
+
allImagesCaptured
|
|
321
|
+
? 'bg-neutral-800'
|
|
322
|
+
: 'bg-neutral-300 dark:bg-neutral-700'
|
|
323
|
+
}`}
|
|
324
|
+
textClassName={`font-semibold ${
|
|
325
|
+
allImagesCaptured
|
|
326
|
+
? 'text-white'
|
|
327
|
+
: 'text-neutral-500 dark:text-neutral-400'
|
|
328
|
+
}`}
|
|
329
|
+
/>
|
|
330
|
+
</View>
|
|
331
|
+
</>
|
|
332
|
+
);
|
|
333
|
+
}
|