vibefast-cli 1.1.3 → 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.
Files changed (300) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +63 -169
  3. package/dist/__tests__/recipes.test.js +25 -3
  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 +547 -543
  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 +35 -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 +79 -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/{imageAnalysisFunctions.ts → imageAnalysis.ts} +5 -5
  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 +57 -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 +59 -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 +51 -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/lib/ai/imageAnalysisAdapter.ts +0 -200
  291. package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/index.tsx +0 -74
  292. package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/local.tsx +0 -25
  293. package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/remote.tsx +0 -23
  294. package/recipes/quiz/apps/native/src/app/(root)/(protected)/quiz/index.tsx +0 -47
  295. package/recipes/tracker-app/apps/native/src/app/(root)/(protected)/tracker-app/index.tsx +0 -1
  296. package/recipes/voice-bot/apps/native/src/app/(root)/(protected)/voice-bot/index.tsx +0 -27
  297. package/recipes/voice-bot/packages/backend/convex/router.ts +0 -81
  298. /package/recipes/{chatbot/apps/native/src/app/(root)/(protected) → chatbot-supabase/apps/native/src/app}/chatbot/index.tsx +0 -0
  299. /package/recipes/{image-generator/apps/native/src/app/(root)/(protected) → image-generator-supabase/apps/native/src/app}/image-generator/gallery.tsx +0 -0
  300. /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';