vibefast-cli 1.1.5 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (301) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +63 -169
  3. package/dist/__tests__/recipes.test.js +89 -85
  4. package/dist/__tests__/recipes.test.js.map +1 -1
  5. package/dist/commands/add.d.ts +1 -1
  6. package/dist/commands/add.d.ts.map +1 -1
  7. package/dist/commands/add.js +576 -588
  8. package/dist/commands/add.js.map +1 -1
  9. package/dist/commands/checklist.d.ts +1 -1
  10. package/dist/commands/checklist.d.ts.map +1 -1
  11. package/dist/commands/checklist.js +40 -39
  12. package/dist/commands/checklist.js.map +1 -1
  13. package/dist/commands/doctor.d.ts +1 -1
  14. package/dist/commands/doctor.js +22 -22
  15. package/dist/commands/doctor.js.map +1 -1
  16. package/dist/commands/env.d.ts +1 -1
  17. package/dist/commands/env.d.ts.map +1 -1
  18. package/dist/commands/env.js +58 -53
  19. package/dist/commands/env.js.map +1 -1
  20. package/dist/commands/health.d.ts +1 -1
  21. package/dist/commands/health.d.ts.map +1 -1
  22. package/dist/commands/health.js +101 -93
  23. package/dist/commands/health.js.map +1 -1
  24. package/dist/commands/init.d.ts +1 -1
  25. package/dist/commands/init.d.ts.map +1 -1
  26. package/dist/commands/init.js +416 -296
  27. package/dist/commands/init.js.map +1 -1
  28. package/dist/commands/remove.d.ts +1 -1
  29. package/dist/commands/remove.d.ts.map +1 -1
  30. package/dist/commands/remove.js +77 -64
  31. package/dist/commands/remove.js.map +1 -1
  32. package/dist/commands/status.d.ts +1 -1
  33. package/dist/commands/status.d.ts.map +1 -1
  34. package/dist/commands/status.js +15 -14
  35. package/dist/commands/status.js.map +1 -1
  36. package/dist/core/__tests__/detect.test.js +68 -34
  37. package/dist/core/__tests__/detect.test.js.map +1 -1
  38. package/dist/core/ast.d.ts +14 -0
  39. package/dist/core/ast.d.ts.map +1 -0
  40. package/dist/core/ast.js +239 -0
  41. package/dist/core/ast.js.map +1 -0
  42. package/dist/core/codemod.d.ts.map +1 -1
  43. package/dist/core/codemod.js +62 -44
  44. package/dist/core/codemod.js.map +1 -1
  45. package/dist/core/config.d.ts +10 -0
  46. package/dist/core/config.d.ts.map +1 -0
  47. package/dist/core/config.js +51 -0
  48. package/dist/core/config.js.map +1 -0
  49. package/dist/core/detect.d.ts +8 -2
  50. package/dist/core/detect.d.ts.map +1 -1
  51. package/dist/core/detect.js +52 -21
  52. package/dist/core/detect.js.map +1 -1
  53. package/dist/core/errors.d.ts.map +1 -1
  54. package/dist/core/errors.js +9 -8
  55. package/dist/core/errors.js.map +1 -1
  56. package/dist/core/exec.d.ts +16 -0
  57. package/dist/core/exec.d.ts.map +1 -0
  58. package/dist/core/exec.js +48 -0
  59. package/dist/core/exec.js.map +1 -0
  60. package/dist/core/manualSteps.d.ts +7 -0
  61. package/dist/core/manualSteps.d.ts.map +1 -0
  62. package/dist/core/manualSteps.js +59 -0
  63. package/dist/core/manualSteps.js.map +1 -0
  64. package/dist/core/paths.d.ts +3 -1
  65. package/dist/core/paths.d.ts.map +1 -1
  66. package/dist/core/paths.js +14 -10
  67. package/dist/core/paths.js.map +1 -1
  68. package/dist/core/spinner.d.ts +1 -1
  69. package/dist/core/spinner.d.ts.map +1 -1
  70. package/dist/core/spinner.js +38 -8
  71. package/dist/core/spinner.js.map +1 -1
  72. package/dist/core/vosk.d.ts.map +1 -1
  73. package/dist/core/vosk.js +50 -39
  74. package/dist/core/vosk.js.map +1 -1
  75. package/docs/manual-testing.md +91 -0
  76. package/package.json +6 -3
  77. package/recipes/audio-recorder/apps/native/src/app/audio-recorder/index.tsx +5 -0
  78. package/recipes/audio-recorder/recipe.json +3 -3
  79. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/audio-player.tsx +301 -0
  80. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/audio-recorder.tsx +373 -0
  81. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/audio-waveform.tsx +270 -0
  82. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/index.ts +4 -0
  83. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/recording-list.tsx +89 -0
  84. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/audio-player-demo.tsx +66 -0
  85. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/audio-recorder-cloud.tsx +68 -0
  86. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/audio-recorder-interview.tsx +102 -0
  87. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/basic.tsx +27 -0
  88. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/index.ts +5 -0
  89. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/with-recording-list-demo.tsx +82 -0
  90. package/recipes/audio-recorder-supabase/packages/backend/src/services/recordings.ts +369 -0
  91. package/recipes/audio-recorder-supabase/packages/backend/supabase/migrations/recordings.sql +70 -0
  92. package/recipes/audio-recorder-supabase/recipe.json +52 -0
  93. package/recipes/audio-recorder-supabase@latest.zip +0 -0
  94. package/recipes/audio-recorder@latest.zip +0 -0
  95. package/recipes/charts/apps/native/src/features/charts/components/bar-chart.tsx +3 -3
  96. package/recipes/charts/apps/native/src/features/charts/components/candlestick-chart.tsx +2 -2
  97. package/recipes/charts/apps/native/src/features/charts/components/chart-card.tsx +5 -5
  98. package/recipes/charts/apps/native/src/features/charts/components/column-chart.tsx +3 -3
  99. package/recipes/charts/apps/native/src/features/charts/components/doughnut-chart.tsx +20 -4
  100. package/recipes/charts/apps/native/src/features/charts/components/line-chart.tsx +7 -6
  101. package/recipes/charts/apps/native/src/features/charts/components/radar-chart.tsx +6 -4
  102. package/recipes/charts/apps/native/src/features/charts/components/radial-bar-chart.tsx +1 -1
  103. package/recipes/charts/apps/native/src/features/charts/components/stacked-bar-chart.tsx +5 -4
  104. package/recipes/charts/recipe.json +4 -13
  105. package/recipes/charts@latest.zip +0 -0
  106. package/recipes/chatbot/apps/native/src/app/chatbot/index.tsx +1 -0
  107. package/recipes/chatbot/apps/native/src/features/chatbot/components/chat-markdown.tsx +86 -86
  108. package/recipes/chatbot/apps/native/src/features/chatbot/components/markdown/code-block.tsx +86 -53
  109. package/recipes/chatbot/recipe.json +26 -92
  110. package/recipes/chatbot-supabase/apps/native/src/api-client/supabase/chatbot.ts +515 -0
  111. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/app/index.tsx +257 -0
  112. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-header-buttons.tsx +59 -0
  113. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-input-bar.tsx +485 -0
  114. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-markdown.tsx +575 -0
  115. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-message-bubble.tsx +223 -0
  116. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-settings-modal.tsx +161 -0
  117. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/image-preview-list.tsx +116 -0
  118. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/markdown/code-block.tsx +165 -0
  119. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/markdown/index.ts +10 -0
  120. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/markdown/table-renderer.tsx +129 -0
  121. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/message-error-boundary.tsx +78 -0
  122. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/message-list.tsx +170 -0
  123. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/model-selector.tsx +283 -0
  124. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/report-content-modal.tsx +188 -0
  125. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/suggested-messages.tsx +67 -0
  126. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/constants/models.ts +20 -0
  127. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/constants/report-reasons.ts +9 -0
  128. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-attachment-cache.ts +142 -0
  129. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-chat-config.ts +458 -0
  130. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-chat-handlers.ts +429 -0
  131. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-chatbot-settings.ts +89 -0
  132. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-conversation.ts +90 -0
  133. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-image-picker.ts +122 -0
  134. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-keyboard-coordinator.ts +161 -0
  135. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-smart-scroll-manager.ts +213 -0
  136. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/models/index.ts +86 -0
  137. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/models/models.ts +162 -0
  138. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/models/providers.ts +62 -0
  139. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/models/types.ts +40 -0
  140. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/services/file-uploader.ts +287 -0
  141. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/services/message-handler-service.ts +189 -0
  142. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/types/index.ts +70 -0
  143. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/utils/chat-telemetry.ts +91 -0
  144. package/recipes/chatbot-supabase/packages/backend/src/services/conversations.ts +243 -0
  145. package/recipes/chatbot-supabase/packages/backend/src/services/messages.ts +327 -0
  146. package/recipes/chatbot-supabase/packages/backend/supabase/functions/chat-stream/index.ts +347 -0
  147. package/recipes/chatbot-supabase/packages/backend/supabase/migrations/chatbot.sql +104 -0
  148. package/recipes/chatbot-supabase/recipe.json +106 -0
  149. package/recipes/chatbot-supabase@latest.zip +0 -0
  150. package/recipes/chatbot.zip +0 -0
  151. package/recipes/chatbot@latest.zip +0 -0
  152. package/recipes/image-analysis/packages/backend/convex/imageAnalysis/index.ts +2 -2
  153. package/recipes/image-analysis/packages/backend/convex/imageAnalysis.ts +0 -1
  154. package/recipes/image-analysis/recipe.json +15 -55
  155. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/analysis-options-screen.tsx +304 -0
  156. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/camera.tsx +221 -0
  157. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/image-capture-screen.tsx +333 -0
  158. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/loading-screen.tsx +214 -0
  159. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/loading.tsx +191 -0
  160. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/results.tsx +137 -0
  161. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/trait-details.tsx +172 -0
  162. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/use-analysis-data.ts +160 -0
  163. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/use-results-screen.ts +151 -0
  164. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/achievement-badge.tsx +77 -0
  165. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/achievement-card.tsx +75 -0
  166. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/achievement-unlocked-modal.tsx +162 -0
  167. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/achievements-section.tsx +44 -0
  168. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/advice-list.tsx +42 -0
  169. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/circular-progress.tsx +233 -0
  170. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/content-card.tsx +38 -0
  171. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/error-state.tsx +42 -0
  172. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/index.ts +9 -0
  173. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/loading-state.tsx +26 -0
  174. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/profile-image.tsx +60 -0
  175. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/results-header.tsx +62 -0
  176. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/score-display.tsx +54 -0
  177. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/share-options-modal.tsx +110 -0
  178. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/traits-grid.tsx +74 -0
  179. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/config/analysis-config.ts +80 -0
  180. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/config/master-analysis-config.ts +157 -0
  181. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/hooks/index.ts +1 -0
  182. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/hooks/use-analysis.ts +38 -0
  183. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/hooks/use-image-analysis.ts +208 -0
  184. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/services/analysis-service.ts +262 -0
  185. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/services/share-service.ts +176 -0
  186. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/services/trait-details-service.ts +289 -0
  187. package/recipes/image-analysis-supabase/packages/backend/src/services/image-analyses.ts +132 -0
  188. package/recipes/image-analysis-supabase/packages/backend/supabase/functions/analyze-image/index.ts +312 -0
  189. package/recipes/image-analysis-supabase/packages/backend/supabase/migrations/image_analysis.sql +42 -0
  190. package/recipes/image-analysis-supabase/recipe.json +90 -0
  191. package/recipes/image-analysis-supabase@latest.zip +0 -0
  192. package/recipes/image-analysis@latest.zip +0 -0
  193. package/recipes/image-generator/apps/native/src/features/image-generator/app/index.tsx +16 -2
  194. package/recipes/image-generator/apps/native/src/features/image-generator/components/image-model-selector.tsx +11 -5
  195. package/recipes/image-generator/apps/native/src/features/image-generator/hooks/use-image-generator.ts +11 -5
  196. package/recipes/image-generator/packages/backend/convex/imageGeneration/index.ts +2 -2
  197. package/recipes/image-generator/recipe.json +16 -39
  198. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/app/_layout.tsx +26 -0
  199. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/app/gallery.tsx +217 -0
  200. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/app/index.tsx +251 -0
  201. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/components/gallery-image.tsx +25 -0
  202. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/components/image-detail-modal.tsx +215 -0
  203. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/components/image-model-selector.tsx +216 -0
  204. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/components/image-placeholder.tsx +26 -0
  205. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/hooks/use-image-gallery.ts +71 -0
  206. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/hooks/use-image-generator-settings.ts +152 -0
  207. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/hooks/use-image-generator.ts +103 -0
  208. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/models/models.ts +66 -0
  209. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/services/image-gallery-service.ts +96 -0
  210. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/services/image-save-service.ts +120 -0
  211. package/recipes/image-generator-supabase/packages/backend/supabase/functions/generate-image/index.ts +291 -0
  212. package/recipes/image-generator-supabase/packages/backend/supabase/migrations/image_generator.sql +71 -0
  213. package/recipes/image-generator-supabase/recipe.json +86 -0
  214. package/recipes/image-generator-supabase@latest.zip +0 -0
  215. package/recipes/image-generator@latest.zip +0 -0
  216. package/recipes/ios-widget/recipe.json +15 -24
  217. package/recipes/ios-widget@latest.zip +0 -0
  218. package/recipes/onboarding/apps/native/src/features/onboarding/analytics/index.ts +9 -0
  219. package/recipes/onboarding/apps/native/src/features/onboarding/components/onboarding-with-analytics.tsx +141 -0
  220. package/recipes/onboarding/apps/native/src/features/onboarding/components/onboarding.tsx +173 -0
  221. package/recipes/onboarding/apps/native/src/features/onboarding/config/onboarding-flow-config.ts +189 -0
  222. package/recipes/onboarding/apps/native/src/features/onboarding/demo-one/app/index.tsx +42 -0
  223. package/recipes/onboarding/apps/native/src/features/onboarding/demo-one/data.ts +32 -0
  224. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/app/index.tsx +43 -0
  225. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/interactive-onboarding.tsx +222 -0
  226. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/ai-tone-step.tsx +133 -0
  227. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/currency-step.tsx +165 -0
  228. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-ai-step.tsx +199 -0
  229. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-chatbot-step.tsx +154 -0
  230. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-manual-step.tsx +156 -0
  231. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-scan-step.tsx +158 -0
  232. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/main-reason-step.tsx +139 -0
  233. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/notification-step.tsx +129 -0
  234. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/overspend-step.tsx +138 -0
  235. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/personalizing-step.tsx +190 -0
  236. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/rating-step.tsx +98 -0
  237. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/reminder-step.tsx +181 -0
  238. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/safety-step.tsx +110 -0
  239. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/struggle-step.tsx +139 -0
  240. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/welcome-step.tsx +217 -0
  241. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/ui/onboarding-header.tsx +58 -0
  242. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/constants.ts +179 -0
  243. package/recipes/onboarding/apps/native/src/features/onboarding/hooks/use-onboarding-analytics.ts +323 -0
  244. package/recipes/onboarding/apps/native/src/features/onboarding/services/onboarding-analytics.ts +432 -0
  245. package/recipes/onboarding/recipe.json +15 -0
  246. package/recipes/onboarding@latest.zip +0 -0
  247. package/recipes/payments/recipe.json +28 -61
  248. package/recipes/payments-supabase/apps/native/src/features/payments/README.md +200 -0
  249. package/recipes/payments-supabase/apps/native/src/features/payments/app/local-paywall.tsx +194 -0
  250. package/recipes/payments-supabase/apps/native/src/features/payments/app/remote-paywall.tsx +79 -0
  251. package/recipes/payments-supabase/apps/native/src/features/payments/components/payment-initializer.tsx +95 -0
  252. package/recipes/payments-supabase/apps/native/src/features/payments/components/paywall-error-state.tsx +60 -0
  253. package/recipes/payments-supabase/apps/native/src/features/payments/components/paywall-local-mode.tsx +116 -0
  254. package/recipes/payments-supabase/apps/native/src/features/payments/components/paywall-product-card.tsx +133 -0
  255. package/recipes/payments-supabase/apps/native/src/features/payments/components/paywall-remote-mode.tsx +146 -0
  256. package/recipes/payments-supabase/apps/native/src/features/payments/hooks/use-entitlement.ts +63 -0
  257. package/recipes/payments-supabase/apps/native/src/features/payments/index.ts +8 -0
  258. package/recipes/payments-supabase/apps/native/src/features/payments/services/revenuecat-adapter.ts +407 -0
  259. package/recipes/payments-supabase/packages/backend/src/services/payments.ts +201 -0
  260. package/recipes/payments-supabase/packages/backend/supabase/migrations/payments.sql +35 -0
  261. package/recipes/payments-supabase/recipe.json +72 -0
  262. package/recipes/payments-supabase@latest.zip +0 -0
  263. package/recipes/payments@latest.zip +0 -0
  264. package/recipes/quiz/apps/native/src/features/quiz/index.tsx +1 -2
  265. package/recipes/quiz/recipe.json +6 -9
  266. package/recipes/quiz@latest.zip +0 -0
  267. package/recipes/tracker-app/apps/native/src/features/tracker-app/app/index.tsx +1 -2
  268. package/recipes/tracker-app/recipe.json +7 -10
  269. package/recipes/tracker-app@latest.zip +0 -0
  270. package/recipes/voice-bot/recipe.json +8 -68
  271. package/recipes/voice-bot.zip +0 -0
  272. package/recipes/voice-bot@latest.zip +0 -0
  273. package/recipes/wake-word/recipe.json +10 -9
  274. package/recipes/wake-word.zip +0 -0
  275. package/recipes/wake-word@latest.zip +0 -0
  276. package/recipes/charts/apps/native/src/app/(root)/(protected)/charts/index.tsx +0 -3
  277. package/recipes/chatbot/packages/backend/convex/lib/rateLimit.ts +0 -100
  278. package/recipes/chatbot/packages/backend/convex/lib/telemetry.ts +0 -29
  279. package/recipes/chatbot/packages/backend/convex/ragKnowledge.ts +0 -0
  280. package/recipes/image-analysis/apps/native/assets/features/image-analyzer/front.jpg +0 -0
  281. package/recipes/image-analysis/apps/native/assets/features/image-analyzer/side.jpg +0 -0
  282. package/recipes/image-analysis/apps/native/assets/features/image-analyzer/threeQuarter.jpg +0 -0
  283. package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/_layout.tsx +0 -5
  284. package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/analysis-options.tsx +0 -50
  285. package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/camera.tsx +0 -2
  286. package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/index.tsx +0 -50
  287. package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/loading.tsx +0 -50
  288. package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/results.tsx +0 -2
  289. package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/trait-details.tsx +0 -3
  290. package/recipes/image-analysis/packages/backend/convex/imageAnalysisFunctions.ts +0 -325
  291. package/recipes/image-analysis/packages/backend/convex/lib/ai/imageAnalysisAdapter.ts +0 -200
  292. package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/index.tsx +0 -74
  293. package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/local.tsx +0 -25
  294. package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/remote.tsx +0 -23
  295. package/recipes/quiz/apps/native/src/app/(root)/(protected)/quiz/index.tsx +0 -47
  296. package/recipes/tracker-app/apps/native/src/app/(root)/(protected)/tracker-app/index.tsx +0 -1
  297. package/recipes/voice-bot/apps/native/src/app/(root)/(protected)/voice-bot/index.tsx +0 -27
  298. package/recipes/voice-bot/packages/backend/convex/router.ts +0 -81
  299. /package/recipes/{chatbot/apps/native/src/app/(root)/(protected) → chatbot-supabase/apps/native/src/app}/chatbot/index.tsx +0 -0
  300. /package/recipes/{image-generator/apps/native/src/app/(root)/(protected) → image-generator-supabase/apps/native/src/app}/image-generator/gallery.tsx +0 -0
  301. /package/recipes/{image-generator/apps/native/src/app/(root)/(protected) → image-generator-supabase/apps/native/src/app}/image-generator/index.tsx +0 -0
@@ -0,0 +1,373 @@
1
+ import {
2
+ AudioModule,
3
+ type RecordingOptions,
4
+ RecordingPresets,
5
+ setAudioModeAsync,
6
+ useAudioRecorder,
7
+ useAudioRecorderState,
8
+ } from 'expo-audio';
9
+ import { getInfoAsync } from 'expo-file-system/legacy';
10
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
11
+ import { Alert, Animated, View, type ViewStyle } from 'react-native';
12
+
13
+ import { Button, Icon, Text } from '@/components/ui';
14
+ import colors from '@/components/ui/colors';
15
+ import { useThemeConfig } from '@/lib/use-theme-config';
16
+
17
+ import { AudioPlayer } from './audio-player';
18
+ import { AudioWaveform } from './audio-waveform';
19
+
20
+ export interface AudioRecorderProps {
21
+ style?: ViewStyle;
22
+ quality?: 'high' | 'low';
23
+ showWaveform?: boolean;
24
+ showTimer?: boolean;
25
+ maxDuration?: number; // in seconds
26
+ onRecordingComplete?: (uri: string) => void;
27
+ onRecordingStart?: () => void;
28
+ onRecordingStop?: () => void;
29
+ customRecordingOptions?: RecordingOptions;
30
+ }
31
+
32
+ export function AudioRecorder({
33
+ style,
34
+ quality = 'high',
35
+ showWaveform = true,
36
+ showTimer = true,
37
+ maxDuration,
38
+ onRecordingComplete,
39
+ onRecordingStart,
40
+ onRecordingStop,
41
+ customRecordingOptions,
42
+ }: AudioRecorderProps) {
43
+ const theme = useThemeConfig();
44
+ const recordingOptions =
45
+ customRecordingOptions ||
46
+ (quality === 'high'
47
+ ? RecordingPresets.HIGH_QUALITY
48
+ : RecordingPresets.LOW_QUALITY);
49
+
50
+ const recorder = useAudioRecorder(recordingOptions);
51
+ const [permissionGranted, setPermissionGranted] = useState(false);
52
+ // Use recorder state duration instead of custom interval
53
+ const [recordingUri, setRecordingUri] = useState<string | null>(null);
54
+ const [isRecording, setIsRecording] = useState(false);
55
+
56
+ // Waveform data for real-time visualization
57
+ const [waveformData, setWaveformData] = useState<number[]>(
58
+ Array.from({ length: 30 }, () => 0.2),
59
+ );
60
+
61
+ // Animation values
62
+ const recordingPulse = useRef(new Animated.Value(1)).current;
63
+
64
+ // Request permissions on mount
65
+ useEffect(() => {
66
+ (async () => {
67
+ try {
68
+ const status = await AudioModule.requestRecordingPermissionsAsync();
69
+ setPermissionGranted(status.granted);
70
+
71
+ if (!status.granted) {
72
+ Alert.alert(
73
+ 'Permission Required',
74
+ 'Please grant microphone permission to record audio.',
75
+ [{ text: 'OK' }],
76
+ );
77
+ }
78
+ // Make sure the OS session is configured for recording & playback in silent mode
79
+ await setAudioModeAsync({
80
+ allowsRecording: true,
81
+ playsInSilentMode: true,
82
+ });
83
+ } catch (error) {
84
+ console.error('Error requesting permissions:', error);
85
+ setPermissionGranted(false);
86
+ }
87
+ })();
88
+ }, []);
89
+
90
+ // Recording pulse animation
91
+ useEffect(() => {
92
+ if (isRecording) {
93
+ const pulse = Animated.loop(
94
+ Animated.sequence([
95
+ Animated.timing(recordingPulse, {
96
+ toValue: 1.2,
97
+ duration: 600,
98
+ useNativeDriver: true,
99
+ }),
100
+ Animated.timing(recordingPulse, {
101
+ toValue: 1,
102
+ duration: 600,
103
+ useNativeDriver: true,
104
+ }),
105
+ ]),
106
+ );
107
+ pulse.start();
108
+
109
+ return () => pulse.stop();
110
+ }
111
+ recordingPulse.setValue(1);
112
+ }, [isRecording, recordingPulse]);
113
+
114
+ const recState = useAudioRecorderState(recorder, 100); // updates ~every 100ms, includes time & metering
115
+ const durationSeconds = recState.durationMillis / 1000;
116
+
117
+ // Real-time waveform updates during recording
118
+ useEffect(() => {
119
+ if (!isRecording) {
120
+ setWaveformData(Array.from({ length: 30 }, () => 0.2));
121
+ return;
122
+ }
123
+ // Use metering from recorder state when available, otherwise simulate
124
+ const db = typeof recState.metering === 'number' ? recState.metering : null;
125
+ const level =
126
+ db !== null
127
+ ? Math.max(0.1, Math.min(1.0, (db + 50) / 50))
128
+ : 0.35 + (Math.random() - 0.5) * 0.25; // slightly more varied fallback
129
+
130
+ setWaveformData((prev) => [...prev.slice(1), level]);
131
+ }, [
132
+ isRecording,
133
+ recState.metering,
134
+ recState.canRecord,
135
+ recState.durationMillis,
136
+ recState.isRecording,
137
+ ]);
138
+
139
+ // No custom duration interval; rely on recState.durationMillis
140
+
141
+ const handleStopRecording = useCallback(async () => {
142
+ try {
143
+ console.log('Stopping recording...');
144
+ setIsRecording(false);
145
+
146
+ await recorder.stop();
147
+ const uri = recorder.uri;
148
+ console.log('Recording stopped, URI:', uri);
149
+
150
+ if (uri) {
151
+ // === START DEBUG CODE ===
152
+ try {
153
+ const fileInfo = await getInfoAsync(uri);
154
+ console.log('Recorded File Info:', fileInfo);
155
+ if (fileInfo.exists && fileInfo.size === 0) {
156
+ Alert.alert(
157
+ 'Debug Info',
158
+ 'The recording was created but is empty (0 bytes). This is likely still a permissions issue.',
159
+ );
160
+ }
161
+ } catch (e) {
162
+ console.error("Couldn't get file info", e);
163
+ }
164
+ // === END DEBUG CODE ===
165
+
166
+ setRecordingUri(uri);
167
+ onRecordingComplete?.(uri);
168
+ }
169
+
170
+ onRecordingStop?.();
171
+ } catch (error) {
172
+ console.error('Error stopping recording:', error);
173
+ Alert.alert('Error', 'Failed to stop recording. Please try again.');
174
+ }
175
+ }, [recorder, onRecordingComplete, onRecordingStop]);
176
+
177
+ // Auto-stop recording when max duration is reached
178
+ useEffect(() => {
179
+ if (maxDuration && durationSeconds >= maxDuration && isRecording) {
180
+ handleStopRecording();
181
+ }
182
+ }, [durationSeconds, maxDuration, isRecording, handleStopRecording]);
183
+
184
+ const handleStartRecording = async () => {
185
+ if (!permissionGranted) {
186
+ Alert.alert(
187
+ 'Permission Required',
188
+ 'Microphone permission is required to record audio.',
189
+ );
190
+ return;
191
+ }
192
+
193
+ try {
194
+ console.log('Starting recording...');
195
+ setRecordingUri(null);
196
+ setIsRecording(true);
197
+
198
+ // Enable metering in recording options
199
+ const meteringOptions = {
200
+ ...recordingOptions,
201
+ isMeteringEnabled: true,
202
+ };
203
+
204
+ await recorder.prepareToRecordAsync(meteringOptions);
205
+ recorder.record();
206
+
207
+ onRecordingStart?.();
208
+ console.log('Recording started successfully');
209
+ } catch (error) {
210
+ console.error('Error starting recording:', error);
211
+ setIsRecording(false);
212
+ // nothing to stop here; using recState
213
+ Alert.alert('Error', 'Failed to start recording. Please try again.');
214
+ }
215
+ };
216
+
217
+ const handleDeleteRecording = () => {
218
+ Alert.alert(
219
+ 'Delete Recording',
220
+ 'Are you sure you want to delete this recording?',
221
+ [
222
+ { text: 'Cancel', style: 'cancel' },
223
+ {
224
+ text: 'Delete',
225
+ style: 'destructive',
226
+ onPress: () => {
227
+ setRecordingUri(null);
228
+ },
229
+ },
230
+ ],
231
+ );
232
+ };
233
+
234
+ const handleSaveRecording = () => {
235
+ if (recordingUri && onRecordingComplete) {
236
+ onRecordingComplete(recordingUri);
237
+ }
238
+ };
239
+
240
+ const formatTime = (seconds: number) => {
241
+ const mins = Math.floor(seconds / 60);
242
+ const secs = Math.floor(seconds % 60);
243
+ const centisecs = Math.floor((seconds % 1) * 100);
244
+ return `${mins}:${secs.toString().padStart(2, '0')}.${centisecs
245
+ .toString()
246
+ .padStart(2, '0')}`;
247
+ };
248
+
249
+ if (!permissionGranted) {
250
+ return (
251
+ <View
252
+ className="items-center rounded-lg bg-secondary p-2 pt-28"
253
+ style={style}
254
+ >
255
+ <Text className="text-text text-center">
256
+ Microphone permission is required to record audio.
257
+ </Text>
258
+ </View>
259
+ );
260
+ }
261
+
262
+ return (
263
+ <View
264
+ className="items-center rounded-lg bg-secondary p-2 pt-28"
265
+ style={style}
266
+ >
267
+ {recordingUri && !isRecording ? (
268
+ <View className="items-center">
269
+ <AudioPlayer
270
+ key={recordingUri} // Force fresh mount on new recording
271
+ source={{ uri: recordingUri }}
272
+ showControls={true}
273
+ showWaveform={true}
274
+ showTimer={true}
275
+ autoPlay={false}
276
+ onPlaybackStatusUpdate={(status) => {
277
+ console.log('Playback status:', status);
278
+ }}
279
+ />
280
+ <View className="flex-row items-center gap-3">
281
+ <Button
282
+ variant="outline"
283
+ size="icon"
284
+ onPress={handleDeleteRecording}
285
+ className="size-12"
286
+ >
287
+ <Icon name="trash" size={20} color={theme.colors.foreground} />
288
+ </Button>
289
+
290
+ <Button
291
+ variant="default"
292
+ size="slim"
293
+ onPress={handleSaveRecording}
294
+ className="rounded-2xl bg-green-600 px-8 dark:bg-green-600"
295
+ >
296
+ <Text className="text-white">Save</Text>
297
+ </Button>
298
+ </View>
299
+ </View>
300
+ ) : (
301
+ <View className="w-full">
302
+ {/* Recording Status */}
303
+ <View className="h-9 flex-row items-center justify-center">
304
+ {isRecording && (
305
+ <View className="flex-row items-center">
306
+ <Icon name="ellipse" size={8} color={colors.danger[500]} />
307
+ <Text className="ml-2 text-danger-500">Recording</Text>
308
+ </View>
309
+ )}
310
+ </View>
311
+
312
+ {/* Waveform Visualization */}
313
+ {showWaveform && (
314
+ <View className="mb-2 items-center">
315
+ <AudioWaveform
316
+ data={waveformData}
317
+ isPlaying={false}
318
+ progress={0}
319
+ height={80}
320
+ barCount={30}
321
+ barWidth={6}
322
+ barGap={2}
323
+ activeColor={isRecording ? '#EF4444' : '#6b7280'} //
324
+ inactiveColor="#444444"
325
+ animated={false}
326
+ />
327
+ </View>
328
+ )}
329
+ {/* Timer */}
330
+ {showTimer && (
331
+ <View className="mb-2 items-center">
332
+ <Text
333
+ className={`font-mono ${isRecording ? 'text-danger-500' : 'text-text'}`}
334
+ >
335
+ {formatTime(durationSeconds)}
336
+ </Text>
337
+ {maxDuration && (
338
+ <Text className="text-muted">
339
+ Max: {formatTime(maxDuration)}
340
+ </Text>
341
+ )}
342
+ </View>
343
+ )}
344
+
345
+ {/* Controls */}
346
+ <View className="mb-3 items-center">
347
+ {!isRecording && !recordingUri && (
348
+ <Animated.View style={{ transform: [{ scale: recordingPulse }] }}>
349
+ <Button
350
+ variant="default"
351
+ onPress={handleStartRecording}
352
+ className="size-20 rounded-full bg-danger-500"
353
+ >
354
+ <Icon name="mic" size={36} color={theme.colors.background} />
355
+ </Button>
356
+ </Animated.View>
357
+ )}
358
+
359
+ {isRecording && (
360
+ <Button
361
+ variant="default"
362
+ onPress={handleStopRecording}
363
+ className="size-20 rounded-full bg-danger-500"
364
+ >
365
+ <Icon name="stop" size={36} color={theme.colors.background} />
366
+ </Button>
367
+ )}
368
+ </View>
369
+ </View>
370
+ )}
371
+ </View>
372
+ );
373
+ }
@@ -0,0 +1,270 @@
1
+ import { Canvas, RoundedRect } from '@shopify/react-native-skia';
2
+ import React, { useEffect, useMemo, useRef, useState } from 'react';
3
+ import { PanResponder, StyleSheet, View, type ViewStyle } from 'react-native';
4
+
5
+ export interface AudioWaveformProps {
6
+ data?: number[]; // Audio amplitude data
7
+ isPlaying?: boolean;
8
+ progress?: number; // 0-100
9
+ onSeek?: (position: number) => void;
10
+ onSeekStart?: () => void;
11
+ onSeekEnd?: () => void;
12
+ style?: ViewStyle & { className?: string };
13
+ height?: number;
14
+ barCount?: number;
15
+ barWidth?: number;
16
+ barGap?: number;
17
+ activeColor?: string;
18
+ inactiveColor?: string;
19
+ animated?: boolean;
20
+ showProgress?: boolean;
21
+ interactive?: boolean; // New prop to enable seeking
22
+ }
23
+
24
+ export function AudioWaveform({
25
+ data,
26
+ isPlaying = false,
27
+ progress = 0,
28
+ onSeek,
29
+ onSeekStart,
30
+ onSeekEnd,
31
+ style,
32
+ height = 60,
33
+ barCount = 50,
34
+ barWidth = 3,
35
+ barGap = 2,
36
+ activeColor,
37
+ inactiveColor,
38
+ animated = true,
39
+ showProgress = false,
40
+ interactive = false,
41
+ }: AudioWaveformProps) {
42
+ const finalActiveColor = activeColor || 'hsl(0, 84.2%, 60.2%)'; // destructive
43
+ const finalInactiveColor = inactiveColor || 'hsl(215.4, 16.3%, 56.9%)'; // textMuted
44
+
45
+ // Generate sample data if none provided
46
+ const waveformData = useMemo(
47
+ () => data || generateSampleWaveform(barCount),
48
+ [data, barCount],
49
+ );
50
+
51
+ // Internal display data used for drawing (allows lightweight animation without many Animated.Values)
52
+ const [displayData, setDisplayData] = useState<number[]>(() => {
53
+ const base = waveformData;
54
+ return Array.from({ length: barCount }, (_, i) => base[i] || 0.2);
55
+ });
56
+
57
+ // Container ref for measuring dimensions
58
+ const containerRef = useRef<View>(null);
59
+ const containerWidth = useRef(0);
60
+
61
+ // Calculate total width including gaps
62
+ const totalWidth = barCount * barWidth + (barCount - 1) * barGap;
63
+
64
+ // Calculate progress line position more accurately
65
+ // const getProgressLinePosition = () => {
66
+ // const progressRatio = Math.max(0, Math.min(100, progress)) / 100;
67
+
68
+ // if (progressRatio === 0) return 0;
69
+ // if (progressRatio === 1) return totalWidth - 1; // Slight offset to keep within bounds
70
+
71
+ // // Calculate which bar the progress falls on
72
+ // const exactBarPosition = progressRatio * barCount;
73
+ // const barIndex = Math.floor(exactBarPosition);
74
+ // const barProgress = exactBarPosition - barIndex;
75
+
76
+ // // Calculate position accounting for bars and gaps
77
+ // let position = barIndex * (barWidth + barGap);
78
+ // position += barProgress * barWidth;
79
+
80
+ // // Ensure we don't exceed the waveform width
81
+ // return Math.min(position, totalWidth - 1);
82
+ // };
83
+
84
+ // Update display data when incoming data changes (real-time recorder case)
85
+ useEffect(() => {
86
+ if (data && !animated) {
87
+ setDisplayData(() =>
88
+ Array.from({ length: barCount }, (_, i) => data[i] || 0.2),
89
+ );
90
+ }
91
+ }, [data, animated, barCount]);
92
+
93
+ // Enhanced animation system for smooth playback
94
+ useEffect(() => {
95
+ if (isPlaying && animated && !showProgress) {
96
+ let raf: number;
97
+ const tick = () => {
98
+ const t = Date.now() / 600; // slower phase
99
+ setDisplayData((prev) =>
100
+ prev.map((_, i) => {
101
+ const base = waveformData[i] || 0.2;
102
+ const variation = 0.9 + Math.sin(t + i * 0.15) * 0.1;
103
+ return Math.max(0.1, Math.min(1, base * variation));
104
+ }),
105
+ );
106
+ raf = requestAnimationFrame(tick);
107
+ };
108
+ raf = requestAnimationFrame(tick);
109
+ return () => cancelAnimationFrame(raf);
110
+ }
111
+ }, [isPlaying, animated, showProgress, waveformData]);
112
+
113
+ // Pan responder for seeking
114
+ const panResponder = useRef(
115
+ PanResponder.create({
116
+ onStartShouldSetPanResponder: () => {
117
+ console.log(
118
+ 'AudioWaveform: onStartShouldSetPanResponder, interactive:',
119
+ interactive,
120
+ );
121
+ return interactive;
122
+ },
123
+ onMoveShouldSetPanResponder: () => {
124
+ console.log(
125
+ 'AudioWaveform: onMoveShouldSetPanResponder, interactive:',
126
+ interactive,
127
+ );
128
+ return interactive;
129
+ },
130
+ onMoveShouldSetPanResponderCapture: () => interactive,
131
+ onPanResponderGrant: (evt) => {
132
+ if (!interactive) return;
133
+ console.log(
134
+ 'AudioWaveform: PanResponder grant, locationX:',
135
+ evt.nativeEvent.locationX,
136
+ );
137
+ onSeekStart?.();
138
+ handleSeek(evt.nativeEvent.locationX);
139
+ },
140
+ onPanResponderMove: (evt) => {
141
+ if (!interactive) return;
142
+ handleSeek(evt.nativeEvent.locationX);
143
+ },
144
+ onPanResponderRelease: () => {
145
+ if (!interactive) return;
146
+ console.log('AudioWaveform: PanResponder release');
147
+ onSeekEnd?.();
148
+ },
149
+ onPanResponderTerminate: () => {
150
+ if (!interactive) return;
151
+ onSeekEnd?.();
152
+ },
153
+ }),
154
+ ).current;
155
+
156
+ const handleSeek = (x: number) => {
157
+ if (!interactive || !onSeek) return;
158
+
159
+ console.log(
160
+ 'AudioWaveform: handleSeek called with x:',
161
+ x,
162
+ 'totalWidth:',
163
+ totalWidth,
164
+ );
165
+ const clampedX = Math.max(0, Math.min(totalWidth, x));
166
+ const seekPercentage = (clampedX / totalWidth) * 100;
167
+ console.log('AudioWaveform: seeking to percentage:', seekPercentage);
168
+ onSeek(seekPercentage);
169
+ };
170
+
171
+ // Removed unused handleBarPress helper to satisfy lint rules
172
+
173
+ const onLayout = (event: any) => {
174
+ containerWidth.current = event.nativeEvent.layout.width;
175
+ };
176
+
177
+ const bars = useMemo(() => {
178
+ const progressRatio = progress / 100;
179
+ return Array.from({ length: barCount }, (_, index) => {
180
+ const barProgress = (index + 0.5) / barCount;
181
+ const isActive = showProgress ? barProgress <= progressRatio : true;
182
+ const isPast = showProgress ? barProgress > progressRatio : false;
183
+ let opacity = 1;
184
+ if (showProgress && isPast) {
185
+ const distance = barProgress - progressRatio;
186
+ opacity = Math.max(0.3, 1 - distance * 2);
187
+ }
188
+ const heightPx = 4 + (height * 0.9 - 4) * (displayData[index] || 0.2);
189
+ const x = index * (barWidth + barGap);
190
+ const y = (height - heightPx) / 2;
191
+ const color = isActive ? finalActiveColor : finalInactiveColor;
192
+ return {
193
+ x,
194
+ y,
195
+ width: barWidth,
196
+ height: heightPx,
197
+ color,
198
+ opacity,
199
+ } as const;
200
+ });
201
+ }, [
202
+ barCount,
203
+ barGap,
204
+ barWidth,
205
+ height,
206
+ displayData,
207
+ finalActiveColor,
208
+ finalInactiveColor,
209
+ showProgress,
210
+ progress,
211
+ ]);
212
+
213
+ return (
214
+ <View
215
+ style={[styles.container, { height }, style]}
216
+ onLayout={onLayout}
217
+ ref={containerRef}
218
+ >
219
+ <View
220
+ style={[styles.waveform, { width: totalWidth }]}
221
+ {...(interactive ? panResponder.panHandlers : {})}
222
+ >
223
+ <Canvas style={{ width: totalWidth, height }}>
224
+ {bars.map((b, idx) => (
225
+ <RoundedRect
226
+ key={idx}
227
+ x={b.x}
228
+ y={b.y}
229
+ width={b.width}
230
+ height={b.height}
231
+ color={b.color}
232
+ opacity={b.opacity}
233
+ r={b.width / 2}
234
+ />
235
+ ))}
236
+ </Canvas>
237
+ </View>
238
+ </View>
239
+ );
240
+ }
241
+
242
+ const styles = StyleSheet.create({
243
+ container: {
244
+ justifyContent: 'center',
245
+ alignItems: 'center',
246
+ position: 'relative',
247
+ },
248
+ waveform: {
249
+ flexDirection: 'row',
250
+ alignItems: 'center',
251
+ justifyContent: 'center',
252
+ position: 'relative',
253
+ },
254
+ barContainer: {},
255
+ bar: {},
256
+ progressLine: {},
257
+ touchOverlay: {
258
+ position: 'absolute',
259
+ backgroundColor: 'transparent',
260
+ },
261
+ });
262
+
263
+ // Helper to generate sample data
264
+ function generateSampleWaveform(count: number): number[] {
265
+ return Array.from({ length: count }, (_, i) => {
266
+ const base = Math.sin((i / count) * Math.PI * 4) * 0.3 + 0.4;
267
+ const noise = (Math.random() - 0.5) * 0.2;
268
+ return Math.max(0.1, Math.min(1, base + noise));
269
+ });
270
+ }
@@ -0,0 +1,4 @@
1
+ export * from './audio-player';
2
+ export * from './audio-recorder';
3
+ export * from './audio-waveform';
4
+ export * from './recording-list';