kasy-cli 1.21.9 → 1.22.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 (267) hide show
  1. package/lib/commands/add.js +93 -80
  2. package/lib/commands/configure.js +100 -32
  3. package/lib/commands/doctor.js +28 -2
  4. package/lib/commands/new.js +80 -37
  5. package/lib/commands/notifications.js +1 -1
  6. package/lib/commands/remove.js +43 -15
  7. package/lib/commands/run.js +2 -2
  8. package/lib/commands/update.js +2 -2
  9. package/lib/scaffold/CHANGELOG.json +14 -0
  10. package/lib/scaffold/backends/api/generator.js +14 -14
  11. package/lib/scaffold/backends/api/patch/README.md +83 -0
  12. package/lib/scaffold/backends/api/patch/lib/core/data/api/storage_api.dart +1 -1
  13. package/lib/scaffold/backends/api/patch/lib/core/data/entities/user_entity.dart +5 -0
  14. package/lib/scaffold/backends/api/patch/lib/{environnements.dart → environments.dart} +3 -11
  15. package/lib/scaffold/backends/api/patch/lib/features/ai_chat/api/ai_chat_api.dart +108 -0
  16. package/lib/scaffold/backends/api/patch/lib/features/ai_chat/api/ai_chat_conversation_entity.dart +51 -0
  17. package/lib/scaffold/backends/api/patch/lib/features/{llm_chat/api/llm_chat_message_entity.dart → ai_chat/api/ai_chat_message_entity.dart} +5 -5
  18. package/lib/scaffold/backends/api/patch/lib/features/{llm_chat/providers/llm_chat_notifier.dart → ai_chat/providers/ai_chat_notifier.dart} +71 -38
  19. package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api.dart +2 -2
  20. package/lib/scaffold/backends/api/patch/lib/features/feedbacks/api/feature_request_api.dart +54 -0
  21. package/lib/scaffold/backends/api/patch/lib/features/onboarding/models/user_info.dart +16 -16
  22. package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/components/onboarding_att_setup.dart +4 -3
  23. package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +38 -28
  24. package/lib/scaffold/backends/api/patch/lib/features/settings/ui/components/admin/admin_users_api.dart +100 -0
  25. package/lib/scaffold/backends/api/patch/lib/features/{subscription → subscriptions}/api/entities/subscription_entity.dart +13 -0
  26. package/lib/scaffold/backends/api/patch/lib/features/subscriptions/api/stripe_backend_api.dart +60 -0
  27. package/lib/scaffold/backends/api/patch/lib/features/{subscription → subscriptions}/api/subscription_api.dart +1 -1
  28. package/lib/scaffold/backends/api/pubspec.yaml.tpl +4 -5
  29. package/lib/scaffold/backends/firebase/deploy.js +87 -13
  30. package/lib/scaffold/backends/firebase/generator.js +5 -5
  31. package/lib/scaffold/backends/firebase/tokens.js +4 -4
  32. package/lib/scaffold/backends/supabase/deploy.js +63 -11
  33. package/lib/scaffold/backends/supabase/edge-functions/admin-list-users/index.ts +149 -0
  34. package/lib/scaffold/backends/supabase/edge-functions/{llm-chat → ai-chat}/index.ts +19 -19
  35. package/lib/scaffold/backends/supabase/edge-functions/revenuecat-webhook/index.ts +2 -0
  36. package/lib/scaffold/backends/supabase/edge-functions/stripe-create-checkout-session/index.ts +123 -0
  37. package/lib/scaffold/backends/supabase/edge-functions/stripe-create-portal-session/index.ts +97 -0
  38. package/lib/scaffold/backends/supabase/edge-functions/stripe-list-prices/index.ts +83 -0
  39. package/lib/scaffold/backends/supabase/edge-functions/stripe-webhook/index.ts +138 -0
  40. package/lib/scaffold/backends/supabase/generator.js +17 -17
  41. package/lib/scaffold/backends/supabase/migrations/20240101000009_ai_messages.sql +50 -0
  42. package/lib/scaffold/backends/supabase/migrations/20240101000012_stripe_customers.sql +36 -0
  43. package/lib/scaffold/backends/supabase/migrations/20240101000013_admin_role.sql +62 -0
  44. package/lib/scaffold/backends/supabase/patch/lib/core/data/entities/user_entity.dart +4 -0
  45. package/lib/scaffold/backends/supabase/patch/lib/{environnements.dart → environments.dart} +3 -13
  46. package/lib/scaffold/backends/supabase/patch/lib/features/ai_chat/api/ai_chat_api.dart +95 -0
  47. package/lib/scaffold/backends/supabase/patch/lib/features/ai_chat/api/ai_chat_conversation_entity.dart +52 -0
  48. package/lib/scaffold/backends/supabase/patch/lib/features/{llm_chat/api/llm_chat_message_entity.dart → ai_chat/api/ai_chat_message_entity.dart} +6 -6
  49. package/lib/scaffold/backends/supabase/patch/lib/features/{llm_chat/providers/llm_chat_notifier.dart → ai_chat/providers/ai_chat_notifier.dart} +63 -35
  50. package/lib/scaffold/backends/supabase/patch/lib/features/authentication/api/authentication_api.dart +1 -1
  51. package/lib/scaffold/backends/supabase/patch/lib/features/feedbacks/api/feature_request_api.dart +46 -0
  52. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/models/user_info.dart +16 -16
  53. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/components/onboarding_att_setup.dart +4 -3
  54. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +38 -28
  55. package/lib/scaffold/backends/supabase/patch/lib/features/settings/ui/components/admin/admin_users_api.dart +93 -0
  56. package/lib/scaffold/backends/supabase/patch/lib/features/{subscription → subscriptions}/api/entities/subscription_entity.dart +13 -0
  57. package/lib/scaffold/backends/supabase/patch/lib/features/subscriptions/api/stripe_backend_api.dart +52 -0
  58. package/lib/scaffold/backends/supabase/patch/lib/features/{subscription → subscriptions}/api/subscription_api.dart +1 -1
  59. package/lib/scaffold/backends/supabase/pubspec.yaml.tpl +4 -5
  60. package/lib/scaffold/backends/supabase/tokens.js +3 -3
  61. package/lib/scaffold/catalog.js +9 -11
  62. package/lib/scaffold/generate.js +45 -31
  63. package/lib/scaffold/shared/generator-utils.js +188 -81
  64. package/lib/scaffold/shared/sort-imports.js +191 -0
  65. package/lib/scaffold/shared/template-strings.js +3 -3
  66. package/lib/utils/checks.js +2 -2
  67. package/lib/utils/i18n/messages-en.js +50 -35
  68. package/lib/utils/i18n/messages-es.js +50 -35
  69. package/lib/utils/i18n/messages-pt.js +52 -37
  70. package/lib/utils/updates.js +15 -15
  71. package/package.json +1 -1
  72. package/templates/firebase/.env.example +2 -2
  73. package/templates/firebase/android/app/src/main/res/drawable/background.png +0 -0
  74. package/templates/firebase/android/app/src/main/res/drawable-night/background.png +0 -0
  75. package/templates/firebase/android/app/src/main/res/drawable-night-v21/background.png +0 -0
  76. package/templates/firebase/android/app/src/main/res/drawable-v21/background.png +0 -0
  77. package/templates/firebase/android/app/src/main/res/values-night-v31/styles.xml +1 -1
  78. package/templates/firebase/android/app/src/main/res/values-v31/styles.xml +1 -1
  79. package/templates/firebase/assets/images/logo_wordmark_dark.png +0 -0
  80. package/templates/firebase/assets/images/logo_wordmark_light.png +0 -0
  81. package/templates/firebase/docs/revenuecat-setup.es.md +1 -1
  82. package/templates/firebase/docs/revenuecat-setup.pt.md +1 -1
  83. package/templates/firebase/firestore.rules +24 -5
  84. package/templates/firebase/functions/package-lock.json +22 -1
  85. package/templates/firebase/functions/package.json +2 -1
  86. package/templates/firebase/functions/src/admin/functions.ts +113 -0
  87. package/templates/firebase/functions/src/{llm_chat → ai_chat}/index.ts +16 -16
  88. package/templates/firebase/functions/src/index.ts +8 -2
  89. package/templates/firebase/functions/src/notifications/device_triggers.ts +2 -2
  90. package/templates/firebase/functions/src/notifications/triggers.ts +3 -3
  91. package/templates/firebase/functions/src/subscriptions/models/subscription_status.ts +2 -0
  92. package/templates/firebase/functions/src/subscriptions/stripe_functions.ts +222 -0
  93. package/templates/firebase/functions/src/subscriptions/subscriptions_functions.ts +2 -2
  94. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png +0 -0
  95. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png +0 -0
  96. package/templates/firebase/lib/components/components.dart +4 -1
  97. package/templates/firebase/lib/components/kasy_app_bar.dart +22 -7
  98. package/templates/firebase/lib/components/kasy_avatar.dart +7 -6
  99. package/templates/firebase/lib/components/kasy_button.dart +23 -99
  100. package/templates/firebase/lib/components/kasy_dialog.dart +11 -11
  101. package/templates/firebase/lib/components/kasy_image_viewer.dart +84 -0
  102. package/templates/firebase/lib/components/{kasy_sidebar_pro.dart → kasy_sidebar.dart} +692 -425
  103. package/templates/firebase/lib/components/kasy_status_tag.dart +91 -0
  104. package/templates/firebase/lib/components/kasy_text_area.dart +1 -3
  105. package/templates/firebase/lib/components/kasy_text_field.dart +5 -6
  106. package/templates/firebase/lib/components/kasy_text_field_otp.dart +1 -3
  107. package/templates/firebase/lib/components/kasy_toast.dart +2 -2
  108. package/templates/firebase/lib/components/kasy_web_header.dart +209 -0
  109. package/templates/firebase/lib/core/ads/ads_provider.dart +1 -1
  110. package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +57 -4
  111. package/templates/firebase/lib/core/bottom_menu/bottom_router.dart +19 -91
  112. package/templates/firebase/lib/core/bottom_menu/kasy_bottom_bar_factory.dart +196 -96
  113. package/templates/firebase/lib/core/bottom_menu/web_content_wrapper.dart +41 -0
  114. package/templates/firebase/lib/core/config/app_env.dart +5 -11
  115. package/templates/firebase/lib/core/config/features.dart +5 -4
  116. package/templates/firebase/lib/core/data/api/analytics_api.dart +1 -1
  117. package/templates/firebase/lib/core/data/api/http_client.dart +1 -1
  118. package/templates/firebase/lib/core/data/entities/user_entity.dart +3 -0
  119. package/templates/firebase/lib/core/data/models/entitlement.dart +35 -0
  120. package/templates/firebase/lib/core/data/models/subscription.dart +13 -186
  121. package/templates/firebase/lib/core/data/models/user.dart +11 -0
  122. package/templates/firebase/lib/core/data/repositories/user_repository.dart +1 -1
  123. package/templates/firebase/lib/core/home_widgets/home_widget_background_task.dart +1 -1
  124. package/templates/firebase/lib/core/home_widgets/home_widget_mywidget_service.dart +1 -1
  125. package/templates/firebase/lib/core/icons/kasy_icons.dart +31 -1
  126. package/templates/firebase/lib/core/rating/api/rating_api.dart +2 -2
  127. package/templates/firebase/lib/core/states/logout_action.dart +25 -0
  128. package/templates/firebase/lib/core/states/user_state_notifier.dart +3 -3
  129. package/templates/firebase/lib/core/theme/colors.dart +488 -188
  130. package/templates/firebase/lib/core/theme/radius.dart +22 -11
  131. package/templates/firebase/lib/core/theme/shadows.dart +66 -0
  132. package/templates/firebase/lib/core/theme/texts.dart +75 -41
  133. package/templates/firebase/lib/core/theme/universal_theme.dart +9 -4
  134. package/templates/firebase/lib/core/web_device_preview/web_device_preview.dart +5 -4
  135. package/templates/firebase/lib/core/web_viewport_scale.dart +52 -0
  136. package/templates/firebase/lib/core/widgets/kasy_brand_badge.dart +118 -0
  137. package/templates/firebase/lib/core/widgets/kasy_brand_logo.dart +40 -0
  138. package/templates/firebase/lib/core/widgets/kasy_focus_ring.dart +125 -0
  139. package/templates/firebase/lib/core/widgets/kasy_hover.dart +33 -13
  140. package/templates/firebase/lib/core/widgets/kasy_pressable_depth.dart +18 -13
  141. package/templates/firebase/lib/{environnements.dart → environments.dart} +3 -14
  142. package/templates/firebase/lib/features/ai_chat/ai_chat_page.dart +159 -0
  143. package/templates/firebase/lib/features/ai_chat/api/ai_chat_api.dart +107 -0
  144. package/templates/firebase/lib/features/ai_chat/api/ai_chat_conversation_entity.dart +64 -0
  145. package/templates/firebase/lib/features/{llm_chat/api/llm_chat_message_entity.dart → ai_chat/api/ai_chat_message_entity.dart} +6 -6
  146. package/templates/firebase/lib/features/{llm_chat/providers/llm_chat_notifier.dart → ai_chat/providers/ai_chat_notifier.dart} +73 -38
  147. package/templates/firebase/lib/features/ai_chat/providers/ai_conversations_notifier.dart +103 -0
  148. package/templates/firebase/lib/features/{llm_chat/ui/widgets/llm_chat_avatars.dart → ai_chat/ui/widgets/ai_chat_avatars.dart} +13 -13
  149. package/templates/firebase/lib/features/{llm_chat/ui/widgets/llm_chat_composer.dart → ai_chat/ui/widgets/ai_chat_composer.dart} +10 -10
  150. package/templates/firebase/lib/features/{llm_chat/llm_chat_page.dart → ai_chat/ui/widgets/ai_chat_conversation_view.dart} +80 -67
  151. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_list.dart +285 -0
  152. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_tile.dart +163 -0
  153. package/templates/firebase/lib/features/authentication/api/authentication_api.dart +52 -13
  154. package/templates/firebase/lib/features/authentication/api/popup_dismiss_watcher.dart +12 -0
  155. package/templates/firebase/lib/features/authentication/api/popup_dismiss_watcher_web.dart +35 -0
  156. package/templates/firebase/lib/features/authentication/ui/recover_password_page.dart +108 -68
  157. package/templates/firebase/lib/features/authentication/ui/signin_page.dart +38 -51
  158. package/templates/firebase/lib/features/authentication/ui/signup_page.dart +38 -51
  159. package/templates/firebase/lib/features/authentication/ui/widgets/auth_card_scaffold.dart +118 -0
  160. package/templates/firebase/lib/features/authentication/ui/widgets/recover_password_result.dart +61 -44
  161. package/templates/firebase/lib/features/feedbacks/api/feature_request_api.dart +32 -0
  162. package/templates/firebase/lib/features/feedbacks/models/feedback_state.dart +5 -5
  163. package/templates/firebase/lib/features/feedbacks/providers/feedback_page_notifier.dart +2 -2
  164. package/templates/firebase/lib/features/home/design_system_page.dart +808 -170
  165. package/templates/firebase/lib/features/home/home_components_page.dart +6 -3
  166. package/templates/firebase/lib/features/home/home_components_preview_page.dart +6 -6
  167. package/templates/firebase/lib/features/home/home_components_preview_registry.dart +325 -186
  168. package/templates/firebase/lib/features/home/home_feed.dart +289 -0
  169. package/templates/firebase/lib/features/home/home_image_grid.dart +355 -0
  170. package/templates/firebase/lib/features/home/home_page.dart +11 -250
  171. package/templates/firebase/lib/features/{local_reminder → local_reminders}/providers/reminder_notifier.dart +1 -1
  172. package/templates/firebase/lib/features/{local_reminder → local_reminders}/ui/reminder_page.dart +2 -2
  173. package/templates/firebase/lib/features/notifications/shared/att_permission.dart +1 -1
  174. package/templates/firebase/lib/features/notifications/ui/request_notification_permission.dart +25 -61
  175. package/templates/firebase/lib/features/notifications/ui/widgets/permission_request_view.dart +117 -0
  176. package/templates/firebase/lib/features/onboarding/models/user_info.dart +16 -16
  177. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_att_setup.dart +4 -3
  178. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_features.dart +7 -9
  179. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_loader.dart +71 -48
  180. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +3 -2
  181. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_questions.dart +5 -5
  182. package/templates/firebase/lib/features/onboarding/ui/onboarding_page.dart +4 -4
  183. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_background.dart +4 -2
  184. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_feature.dart +39 -121
  185. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_illustration_scaffold.dart +105 -70
  186. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_module_mockups.dart +639 -0
  187. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_progress.dart +62 -50
  188. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +38 -28
  189. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_step_header.dart +75 -0
  190. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_sticky_footer.dart +16 -5
  191. package/templates/firebase/lib/features/onboarding/ui/widgets/selectable_row_tile.dart +26 -22
  192. package/templates/firebase/lib/features/settings/settings_page.dart +601 -90
  193. package/templates/firebase/lib/features/settings/ui/components/admin/admin_page.dart +1193 -0
  194. package/templates/firebase/lib/features/settings/ui/components/admin/admin_paywalls.dart +1 -1
  195. package/templates/firebase/lib/features/settings/ui/components/admin/admin_routes.dart +2 -3
  196. package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_api.dart +86 -0
  197. package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_tab.dart +1215 -0
  198. package/templates/firebase/lib/features/settings/ui/components/admin/send_push_notification_page.dart +236 -0
  199. package/templates/firebase/lib/features/settings/ui/components/avatar_component.dart +3 -3
  200. package/templates/firebase/lib/features/settings/ui/widgets/kasy_user_avatar.dart +77 -0
  201. package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +1 -0
  202. package/templates/firebase/lib/features/{subscription → subscriptions}/api/entities/subscription_entity.dart +17 -0
  203. package/templates/firebase/lib/features/{subscription → subscriptions}/api/inapp_subscription_api.dart +67 -46
  204. package/templates/firebase/lib/features/subscriptions/api/revenuecat_product.dart +189 -0
  205. package/templates/firebase/lib/features/subscriptions/api/stripe_backend_api.dart +55 -0
  206. package/templates/firebase/lib/features/subscriptions/api/stripe_payment_api.dart +82 -0
  207. package/templates/firebase/lib/features/subscriptions/api/stripe_product.dart +178 -0
  208. package/templates/firebase/lib/features/{subscription → subscriptions}/api/subscription_api.dart +1 -1
  209. package/templates/firebase/lib/features/subscriptions/api/subscription_payment_api.dart +53 -0
  210. package/templates/firebase/lib/features/subscriptions/api/subscription_payment_api_provider.dart +21 -0
  211. package/templates/firebase/lib/features/{subscription → subscriptions}/providers/premium_page_provider.dart +9 -6
  212. package/templates/firebase/lib/features/{subscription → subscriptions}/repositories/subscription_repository.dart +26 -30
  213. package/{lib/scaffold/backends/supabase/patch/lib/features/subscription → templates/firebase/lib/features/subscriptions}/shared/maybeshow_premium.dart +0 -2
  214. package/templates/firebase/lib/features/subscriptions/shared/subscription_management.dart +45 -0
  215. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/active_premium_content.dart +28 -4
  216. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_minimal.dart +7 -7
  217. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_row.dart +8 -8
  218. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_with_switch.dart +7 -7
  219. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/premium_content.dart +7 -7
  220. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/premium_page_factory.dart +5 -5
  221. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/premium_page.dart +5 -5
  222. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/comparison_table.dart +1 -1
  223. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/paywall_empty_state.dart +4 -4
  224. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_bottom_menu.dart +3 -4
  225. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/selectable_col.dart +1 -1
  226. package/templates/firebase/lib/i18n/en.i18n.json +171 -46
  227. package/templates/firebase/lib/i18n/es.i18n.json +175 -50
  228. package/templates/firebase/lib/i18n/pt.i18n.json +166 -41
  229. package/templates/firebase/lib/main.dart +6 -3
  230. package/templates/firebase/lib/router.dart +15 -23
  231. package/templates/firebase/pubspec.yaml +4 -5
  232. package/templates/firebase/test/core/data/repositories/user_repository_test.dart +3 -3
  233. package/templates/firebase/test/core/states/user_state_notifier_test.dart +4 -4
  234. package/templates/firebase/test/core/widgets/focus_ring_shape_test.dart +55 -0
  235. package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_inapp_subscription_api.dart +11 -4
  236. package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_revenuecat_product.dart +1 -0
  237. package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_subscription_api.dart +2 -2
  238. package/templates/firebase/test/features/{subscription → subscriptions}/subscription_page_test.dart +4 -4
  239. package/templates/firebase/test/test_utils.dart +6 -6
  240. package/templates/firebase/web/index.html +5 -2
  241. package/lib/scaffold/backends/api/patch/lib/features/llm_chat/api/llm_chat_api.dart +0 -58
  242. package/lib/scaffold/backends/api/patch/lib/features/subscription/shared/maybeshow_premium.dart +0 -86
  243. package/lib/scaffold/backends/supabase/migrations/20240101000009_llm_messages.sql +0 -22
  244. package/lib/scaffold/backends/supabase/patch/lib/features/llm_chat/api/llm_chat_api.dart +0 -47
  245. package/templates/firebase/assets/images/onboarding/authentication-login-template.jpg +0 -0
  246. package/templates/firebase/assets/images/onboarding/img2.jpg +0 -0
  247. package/templates/firebase/assets/images/onboarding/img3.jpg +0 -0
  248. package/templates/firebase/assets/images/onboarding/notifications.png +0 -0
  249. package/templates/firebase/assets/images/onboarding/purchase.png +0 -0
  250. package/templates/firebase/lib/core/sidebar/kasy_sidebar.dart +0 -2021
  251. package/templates/firebase/lib/features/authentication/ui/widgets/auth_brand.dart +0 -35
  252. package/templates/firebase/lib/features/home/home_features_page.dart +0 -207
  253. package/templates/firebase/lib/features/llm_chat/api/llm_chat_api.dart +0 -50
  254. package/templates/firebase/lib/features/settings/ui/components/admin/admin_bottom_sheet.dart +0 -316
  255. package/templates/firebase/lib/features/subscription/shared/maybeshow_premium.dart +0 -85
  256. /package/templates/firebase/lib/features/{local_reminder → local_reminders}/repositories/reminder_preferences.dart +0 -0
  257. /package/templates/firebase/lib/features/{subscription → subscriptions}/providers/models/premium_state.dart +0 -0
  258. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/feature_line.dart +0 -0
  259. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_background.dart +0 -0
  260. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_background_gradient.dart +0 -0
  261. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_banner.dart +0 -0
  262. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_card.dart +0 -0
  263. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_close_button.dart +0 -0
  264. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_feature.dart +0 -0
  265. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/selectable_row.dart +0 -0
  266. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/trial_switcher.dart +0 -0
  267. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/unsubscribe_feedback_popup.dart +0 -0
@@ -45,6 +45,7 @@ class KasyHover extends ConsumerStatefulWidget {
45
45
  this.semanticLabel,
46
46
  this.hapticEnabled = true,
47
47
  this.hoverEnabled = true,
48
+ this.pressEnabled = true,
48
49
  this.hoverColor,
49
50
  this.pressColor,
50
51
  });
@@ -75,6 +76,13 @@ class KasyHover extends ConsumerStatefulWidget {
75
76
  /// settings) where a hover highlight is visually unwanted.
76
77
  final bool hoverEnabled;
77
78
 
79
+ /// Whether the pressed-state background fill is shown on tap.
80
+ ///
81
+ /// When false, tapping triggers [onTap] (and haptic/cursor) with no visible
82
+ /// background highlight. Use on plain list rows (e.g. settings) where the tap
83
+ /// should just navigate without leaving a grey flash behind.
84
+ final bool pressEnabled;
85
+
78
86
  /// Exact background colour used while the pointer hovers over the widget.
79
87
  ///
80
88
  /// When non-null, this solid colour replaces the default semi-transparent
@@ -117,22 +125,34 @@ class _KasyHoverState extends ConsumerState<KasyHover> {
117
125
  Color _overlayColor(BuildContext context) {
118
126
  final bool isDark = Theme.of(context).brightness == Brightness.dark;
119
127
  final Color base = widget.pressColor ?? context.colors.onSurface;
120
- if (_pressed) return base.withValues(alpha: isDark ? 0.04 : 0.10);
121
- if (_hovered && widget.hoverEnabled) {
122
- // Solid hover colour used for nav items where hover must exactly
123
- // match the selected background.
124
- if (widget.hoverColor != null) return widget.hoverColor!;
125
- return base.withValues(alpha: isDark ? 0.02 : 0.06);
126
- }
127
- // IMPORTANT: when hoverColor is set, the resting state must be the same
128
- // colour at alpha=0 (not Colors.transparent = 0x00000000 black).
129
- // AnimatedContainer lerps between the two values lerping from black-
130
- // transparent to a light solid colour passes through dark intermediates,
131
- // causing a visible flash. Lerping from the same hue at alpha=0 to
132
- // alpha=1 only changes opacity → no dark artefact.
128
+
129
+ // Nav-item style: a solid hover/selected colour was provided. Keep the
130
+ // resting, hover and pressed states on the SAME hue only the intensity
131
+ // changes, never the colour — so clicking never flashes.
132
+ //
133
+ // Resting must be that hue at alpha=0 (NOT Colors.transparent, which is
134
+ // transparent *black*): an AnimatedContainer lerping from black-transparent
135
+ // to a light solid passes through dark intermediates and flickers.
136
+ // Press = the hover fill blended a touch deeper, so hover→press→hover stays
137
+ // continuous instead of swapping to a different translucent overlay.
133
138
  if (widget.hoverColor != null) {
139
+ if (_pressed && widget.pressEnabled) {
140
+ return Color.alphaBlend(
141
+ base.withValues(alpha: isDark ? 0.06 : 0.08),
142
+ widget.hoverColor!,
143
+ );
144
+ }
145
+ if (_hovered && widget.hoverEnabled) return widget.hoverColor!;
134
146
  return widget.hoverColor!.withValues(alpha: 0);
135
147
  }
148
+
149
+ // Plain rows: a subtle translucent overlay that fades in on hover/press.
150
+ if (_pressed && widget.pressEnabled) {
151
+ return base.withValues(alpha: isDark ? 0.04 : 0.10);
152
+ }
153
+ if (_hovered && widget.hoverEnabled) {
154
+ return base.withValues(alpha: isDark ? 0.02 : 0.06);
155
+ }
136
156
  return Colors.transparent;
137
157
  }
138
158
 
@@ -10,20 +10,20 @@ import 'package:kasy_kit/core/haptics/haptic_feedback_notifier.dart';
10
10
  /// Press-in with slight overshoot-back (same tactility as [KasyAlert] actions).
11
11
  ///
12
12
  /// No Material ripple. Optional [pressOverlayColor] + [clipBorderRadius]:
13
- /// clarão rápido no tap (véu), não enquanto o dedo apenas descansa em cima.
14
- /// Também expõe semântica para TalkBack/VoiceOver e haptic leve opcional ([hapticFeedbackEnabled]).
13
+ /// a quick flash on tap (veil), not while the finger merely rests on it.
14
+ /// Also exposes semantics for TalkBack/VoiceOver and an optional light haptic ([hapticFeedbackEnabled]).
15
15
  class KasyPressableDepth extends ConsumerStatefulWidget {
16
16
  final Widget child;
17
17
  final VoidCallback onPressed;
18
18
  final String semanticLabel;
19
19
 
20
- /// Breve clarão no tap (véu translúcido); não durante o só pressionar.
20
+ /// Brief flash on tap (translucent veil); not while merely pressing.
21
21
  final Color? pressOverlayColor;
22
22
 
23
23
  /// Required with [pressOverlayColor] when the surface has rounded clips (pill, etc.).
24
24
  final BorderRadius? clipBorderRadius;
25
25
 
26
- /// Se false, não chama [HapticFeedback.lightImpact] no tap.
26
+ /// If false, does not call [HapticFeedback.lightImpact] on tap.
27
27
  final bool hapticFeedbackEnabled;
28
28
 
29
29
  const KasyPressableDepth({
@@ -122,15 +122,20 @@ class _KasyPressableDepthState extends ConsumerState<KasyPressableDepth>
122
122
 
123
123
  return Transform.scale(
124
124
  scale: _depthScale,
125
- child: ClipRRect(
126
- borderRadius: widget.clipBorderRadius!,
127
- child: Stack(
128
- fit: StackFit.passthrough,
129
- children: <Widget>[
130
- rawChild,
131
- Positioned.fill(child: pressVeil),
132
- ],
133
- ),
125
+ // Clip only the press veil to the rounded shape — never [rawChild]. The
126
+ // child may host a keyboard focus ring that paints just outside its box;
127
+ // clipping the whole stack would shave that ring off.
128
+ child: Stack(
129
+ fit: StackFit.passthrough,
130
+ children: <Widget>[
131
+ rawChild,
132
+ Positioned.fill(
133
+ child: ClipRRect(
134
+ borderRadius: widget.clipBorderRadius!,
135
+ child: pressVeil,
136
+ ),
137
+ ),
138
+ ],
134
139
  ),
135
140
  );
136
141
  }
@@ -6,7 +6,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
6
6
  import 'package:kasy_kit/core/config/app_env.dart';
7
7
  import 'package:kasy_kit/core/states/user_state_notifier.dart';
8
8
 
9
- part 'environnements.freezed.dart';
9
+ part 'environments.freezed.dart';
10
10
 
11
11
  // URLs for terms of service and privacy policy
12
12
  const kTermsUrl = '';
@@ -37,10 +37,6 @@ sealed class Environment with _$Environment {
37
37
  /// (only if you want to use in-app purchases with RevenueCat)
38
38
  String? revenueCatIOSApiKey,
39
39
 
40
- /// RevenueCat Web Billing API key (rcb_xxx or rcb_sb_xxx)
41
- /// (only if you want to use subscriptions on web)
42
- String? revenueCatWebApiKey,
43
-
44
40
  /// this is used to open the app store page of your app for reviews
45
41
  String? appStoreId,
46
42
 
@@ -73,10 +69,6 @@ sealed class Environment with _$Environment {
73
69
  /// (only if you want to use in-app purchases with RevenueCat)
74
70
  String? revenueCatIOSApiKey,
75
71
 
76
- /// RevenueCat Web Billing API key (rcb_xxx or rcb_sb_xxx)
77
- /// (only if you want to use subscriptions on web)
78
- String? revenueCatWebApiKey,
79
-
80
72
  /// only if you want to use ads
81
73
  String? androidInterstitialAdUnitId,
82
74
 
@@ -113,7 +105,6 @@ sealed class Environment with _$Environment {
113
105
  appStoreId: '',
114
106
  revenueCatAndroidApiKey: AppEnv.rcAndroidApiKey,
115
107
  revenueCatIOSApiKey: AppEnv.rcIosApiKey,
116
- revenueCatWebApiKey: AppEnv.rcWebApiKey,
117
108
  mixpanelToken: AppEnv.mixpanelToken,
118
109
  // ─── Authentication mode ──────────────────────────────────────────
119
110
  // AuthenticationMode.anonymous (default) → a hidden anonymous Firebase Auth
@@ -134,7 +125,6 @@ sealed class Environment with _$Environment {
134
125
  appStoreId: AppEnv.appStoreId,
135
126
  revenueCatAndroidApiKey: AppEnv.rcAndroidApiKey,
136
127
  revenueCatIOSApiKey: AppEnv.rcIosApiKey,
137
- revenueCatWebApiKey: AppEnv.rcWebApiKey,
138
128
  sentryDsn: AppEnv.sentryDsn,
139
129
  mixpanelToken: AppEnv.mixpanelToken,
140
130
  // See dev comment above for details on each mode.
@@ -150,9 +140,8 @@ sealed class Environment with _$Environment {
150
140
  revenueCatIOSApiKey != null && revenueCatIOSApiKey!.isNotEmpty,
151
141
  TargetPlatform.android =>
152
142
  revenueCatAndroidApiKey != null && revenueCatAndroidApiKey!.isNotEmpty,
153
- _ =>
154
- kIsWeb &&
155
- (revenueCatWebApiKey != null && revenueCatWebApiKey!.isNotEmpty),
143
+ // RevenueCat is mobile-only; web/desktop are never RevenueCat-configured.
144
+ _ => false,
156
145
  };
157
146
  }
158
147
 
@@ -0,0 +1,159 @@
1
+ import 'package:flutter/material.dart';
2
+ import 'package:flutter_riverpod/flutter_riverpod.dart';
3
+ import 'package:kasy_kit/components/components.dart';
4
+ import 'package:kasy_kit/core/haptics/kasy_haptics.dart';
5
+ import 'package:kasy_kit/core/theme/theme.dart';
6
+ import 'package:kasy_kit/core/widgets/responsive_layout.dart';
7
+ import 'package:kasy_kit/features/ai_chat/providers/ai_conversations_notifier.dart';
8
+ import 'package:kasy_kit/features/ai_chat/ui/widgets/ai_chat_avatars.dart';
9
+ import 'package:kasy_kit/features/ai_chat/ui/widgets/ai_chat_conversation_view.dart';
10
+ import 'package:kasy_kit/features/ai_chat/ui/widgets/ai_conversation_list.dart';
11
+ import 'package:kasy_kit/i18n/translations.g.dart';
12
+
13
+ /// Support / AI chat screen with conversation history.
14
+ ///
15
+ /// Responsive:
16
+ /// - phones (small): single pane. The conversation list is shown until a
17
+ /// conversation is opened; opening one swaps to the thread (back returns).
18
+ /// - tablet/desktop (medium+): two panes — the list beside a full-height
19
+ /// container that hosts the active thread (or a placeholder when none).
20
+ class AiChatPage extends StatelessWidget {
21
+ /// When true the page renders as a root bottom-tab (theme toggle, no back).
22
+ final bool isRootTab;
23
+
24
+ const AiChatPage({super.key, this.isRootTab = false});
25
+
26
+ @override
27
+ Widget build(BuildContext context) {
28
+ return ResponsiveLayout(
29
+ small: _MobileLayout(isRootTab: isRootTab),
30
+ medium: const _WideLayout(),
31
+ );
32
+ }
33
+ }
34
+
35
+ class _MobileLayout extends StatelessWidget {
36
+ const _MobileLayout({required this.isRootTab});
37
+
38
+ final bool isRootTab;
39
+
40
+ @override
41
+ Widget build(BuildContext context) {
42
+ // Phones show only the conversation list; opening a conversation pushes a
43
+ // full-screen route (so the bottom nav bar is hidden for the composer).
44
+ return Scaffold(
45
+ backgroundColor: context.colors.background,
46
+ body: Column(
47
+ children: [
48
+ KasyAppBar(
49
+ title: t.ai_chat.title,
50
+ style: isRootTab
51
+ ? KasyAppBarStyle.rootTab
52
+ : KasyAppBarStyle.subpage,
53
+ onThemeToggle: isRootTab
54
+ ? () {
55
+ KasyHaptics.light(context);
56
+ ThemeProvider.of(context).toggle();
57
+ }
58
+ : null,
59
+ ),
60
+ const Expanded(
61
+ child: AiConversationList(swipeToDelete: true, isPhone: true),
62
+ ),
63
+ ],
64
+ ),
65
+ );
66
+ }
67
+ }
68
+
69
+ class _WideLayout extends ConsumerWidget {
70
+ const _WideLayout();
71
+
72
+ @override
73
+ Widget build(BuildContext context, WidgetRef ref) {
74
+ final selectedId = ref.watch(selectedConversationIdProvider);
75
+
76
+ return Scaffold(
77
+ backgroundColor: context.colors.background,
78
+ body: Row(
79
+ crossAxisAlignment: CrossAxisAlignment.stretch,
80
+ children: [
81
+ DecoratedBox(
82
+ // Vertical hairline matching the sidebar/web-header line.
83
+ decoration: BoxDecoration(
84
+ border: Border(
85
+ right: BorderSide(color: context.colors.border, width: 0.5),
86
+ ),
87
+ ),
88
+ child: const SizedBox(
89
+ width: 340,
90
+ child: AiConversationList(),
91
+ ),
92
+ ),
93
+ Expanded(
94
+ child: Padding(
95
+ padding: const EdgeInsets.all(KasySpacing.md),
96
+ child: _DetailContainer(
97
+ child: selectedId == null
98
+ ? const _DetailPlaceholder()
99
+ : const AiChatConversationView(),
100
+ ),
101
+ ),
102
+ ),
103
+ ],
104
+ ),
105
+ );
106
+ }
107
+ }
108
+
109
+ /// Rounded, full-height surface that hosts the active thread (it scrolls
110
+ /// internally). Mirrors the Figma reading-pane container.
111
+ class _DetailContainer extends StatelessWidget {
112
+ const _DetailContainer({required this.child});
113
+
114
+ final Widget child;
115
+
116
+ @override
117
+ Widget build(BuildContext context) {
118
+ return Container(
119
+ clipBehavior: Clip.antiAlias,
120
+ decoration: BoxDecoration(
121
+ color: context.colors.surface,
122
+ borderRadius: BorderRadius.circular(24),
123
+ border: Border.all(color: context.colors.border, width: 0.5),
124
+ ),
125
+ child: child,
126
+ );
127
+ }
128
+ }
129
+
130
+ class _DetailPlaceholder extends StatelessWidget {
131
+ const _DetailPlaceholder();
132
+
133
+ @override
134
+ Widget build(BuildContext context) {
135
+ return Center(
136
+ child: Padding(
137
+ padding: const EdgeInsets.all(KasySpacing.lg),
138
+ child: Column(
139
+ mainAxisSize: MainAxisSize.min,
140
+ children: [
141
+ const AiChatAssistantAvatar(
142
+ diameter: kAiChatEmptyAvatarDiameter,
143
+ showShadow: true,
144
+ ),
145
+ const SizedBox(height: KasySpacing.md),
146
+ Text(
147
+ t.ai_chat.no_conversation_selected,
148
+ textAlign: TextAlign.center,
149
+ style: context.textTheme.bodyLarge?.copyWith(
150
+ color: context.colors.muted,
151
+ height: 1.4,
152
+ ),
153
+ ),
154
+ ],
155
+ ),
156
+ ),
157
+ );
158
+ }
159
+ }
@@ -0,0 +1,107 @@
1
+ import 'package:cloud_firestore/cloud_firestore.dart';
2
+ import 'package:flutter_riverpod/flutter_riverpod.dart';
3
+ import 'package:kasy_kit/features/ai_chat/api/ai_chat_conversation_entity.dart';
4
+ import 'package:kasy_kit/features/ai_chat/api/ai_chat_message_entity.dart';
5
+ import 'package:logger/logger.dart';
6
+
7
+ final aiChatApiProvider = Provider<AiChatApi>(
8
+ (ref) => AiChatApi(client: FirebaseFirestore.instance),
9
+ );
10
+
11
+ /// Firestore layout (one user has many conversations, each has many messages):
12
+ /// users/{userId}/ai_conversations/{conversationId}
13
+ /// users/{userId}/ai_conversations/{conversationId}/messages/{messageId}
14
+ class AiChatApi {
15
+ final FirebaseFirestore _client;
16
+ final Logger _logger = Logger();
17
+
18
+ AiChatApi({required FirebaseFirestore client}) : _client = client;
19
+
20
+ CollectionReference<Map<String, dynamic>> _conversationsRef(String userId) =>
21
+ _client.collection('users').doc(userId).collection('ai_conversations');
22
+
23
+ CollectionReference<Map<String, dynamic>> _messagesRef(
24
+ String userId,
25
+ String conversationId,
26
+ ) => _conversationsRef(userId).doc(conversationId).collection('messages');
27
+
28
+ // ---------------------------------------------------------------------------
29
+ // Conversations
30
+ // ---------------------------------------------------------------------------
31
+
32
+ /// Returns the user's conversations, most recently updated first.
33
+ Future<List<AiChatConversationEntity>> loadConversations(
34
+ String userId,
35
+ ) async {
36
+ final snapshot = await _conversationsRef(
37
+ userId,
38
+ ).orderBy('updatedAt', descending: true).get();
39
+ return snapshot.docs
40
+ .map((doc) => AiChatConversationEntity.fromFirestore(doc.id, doc.data()))
41
+ .toList();
42
+ }
43
+
44
+ /// Creates an empty conversation and returns it (with its generated id).
45
+ Future<AiChatConversationEntity> createConversation(String userId) async {
46
+ final now = DateTime.now();
47
+ final seed = AiChatConversationEntity(
48
+ id: '',
49
+ createdAt: now,
50
+ updatedAt: now,
51
+ );
52
+ final ref = await _conversationsRef(userId).add(seed.toFirestore());
53
+ return AiChatConversationEntity(
54
+ id: ref.id,
55
+ createdAt: now,
56
+ updatedAt: now,
57
+ );
58
+ }
59
+
60
+ /// Deletes a conversation and all of its messages.
61
+ Future<void> deleteConversation(String userId, String conversationId) async {
62
+ final messages = await _messagesRef(userId, conversationId).get();
63
+ final batch = _client.batch();
64
+ for (final doc in messages.docs) {
65
+ batch.delete(doc.reference);
66
+ }
67
+ batch.delete(_conversationsRef(userId).doc(conversationId));
68
+ await batch.commit();
69
+ }
70
+
71
+ // ---------------------------------------------------------------------------
72
+ // Messages
73
+ // ---------------------------------------------------------------------------
74
+
75
+ /// Returns all messages in a conversation, oldest first.
76
+ Future<List<AiChatMessageEntity>> loadMessages(
77
+ String userId,
78
+ String conversationId,
79
+ ) async {
80
+ final snapshot = await _messagesRef(
81
+ userId,
82
+ conversationId,
83
+ ).orderBy('createdAt', descending: false).get();
84
+ return snapshot.docs
85
+ .map((doc) => AiChatMessageEntity.fromFirestore(doc.id, doc.data()))
86
+ .toList();
87
+ }
88
+
89
+ /// Persists a message and denormalizes it onto the parent conversation
90
+ /// (last message preview + updatedAt) so the list stays cheap to render.
91
+ Future<void> saveMessage(
92
+ String userId,
93
+ String conversationId,
94
+ AiChatMessageEntity message,
95
+ ) async {
96
+ try {
97
+ await _messagesRef(userId, conversationId).add(message.toFirestore());
98
+ await _conversationsRef(userId).doc(conversationId).update({
99
+ 'updatedAt': Timestamp.fromDate(message.createdAt),
100
+ 'lastMessageRole': message.role,
101
+ 'lastMessageContent': message.content,
102
+ });
103
+ } catch (e) {
104
+ _logger.e('Failed to persist AI message: $e');
105
+ }
106
+ }
107
+ }
@@ -0,0 +1,64 @@
1
+ import 'package:cloud_firestore/cloud_firestore.dart';
2
+
3
+ /// Firestore document mapping for a single AI chat conversation.
4
+ /// Stored at: users/{userId}/ai_conversations/{conversationId}
5
+ ///
6
+ /// The last message is denormalized onto the conversation so the list can be
7
+ /// rendered with a single read per conversation (no need to open each
8
+ /// messages subcollection just to show a preview + timestamp).
9
+ class AiChatConversationEntity {
10
+ final String id;
11
+ final DateTime createdAt;
12
+ final DateTime updatedAt;
13
+
14
+ /// Role of the most recent message ('user' or 'assistant'), or null when the
15
+ /// conversation has no messages yet.
16
+ final String? lastMessageRole;
17
+
18
+ /// Content of the most recent message, or null when empty.
19
+ final String? lastMessageContent;
20
+
21
+ const AiChatConversationEntity({
22
+ required this.id,
23
+ required this.createdAt,
24
+ required this.updatedAt,
25
+ this.lastMessageRole,
26
+ this.lastMessageContent,
27
+ });
28
+
29
+ bool get isEmpty => lastMessageContent == null;
30
+
31
+ AiChatConversationEntity copyWith({
32
+ DateTime? updatedAt,
33
+ String? lastMessageRole,
34
+ String? lastMessageContent,
35
+ }) {
36
+ return AiChatConversationEntity(
37
+ id: id,
38
+ createdAt: createdAt,
39
+ updatedAt: updatedAt ?? this.updatedAt,
40
+ lastMessageRole: lastMessageRole ?? this.lastMessageRole,
41
+ lastMessageContent: lastMessageContent ?? this.lastMessageContent,
42
+ );
43
+ }
44
+
45
+ factory AiChatConversationEntity.fromFirestore(
46
+ String id,
47
+ Map<String, dynamic> data,
48
+ ) {
49
+ return AiChatConversationEntity(
50
+ id: id,
51
+ createdAt: (data['createdAt'] as Timestamp?)?.toDate() ?? DateTime.now(),
52
+ updatedAt: (data['updatedAt'] as Timestamp?)?.toDate() ?? DateTime.now(),
53
+ lastMessageRole: data['lastMessageRole'] as String?,
54
+ lastMessageContent: data['lastMessageContent'] as String?,
55
+ );
56
+ }
57
+
58
+ Map<String, dynamic> toFirestore() => {
59
+ 'createdAt': Timestamp.fromDate(createdAt),
60
+ 'updatedAt': Timestamp.fromDate(updatedAt),
61
+ 'lastMessageRole': lastMessageRole,
62
+ 'lastMessageContent': lastMessageContent,
63
+ };
64
+ }
@@ -1,25 +1,25 @@
1
1
  import 'package:cloud_firestore/cloud_firestore.dart';
2
2
 
3
- /// Firestore document mapping for a single LLM chat message.
4
- /// Stored at: users/{userId}/llm_messages/{messageId}
5
- class LlmChatMessageEntity {
3
+ /// Firestore document mapping for a single AI chat message.
4
+ /// Stored at: users/{userId}/ai_messages/{messageId}
5
+ class AiChatMessageEntity {
6
6
  final String? id;
7
7
  final String role; // 'user' or 'assistant'
8
8
  final String content;
9
9
  final DateTime createdAt;
10
10
 
11
- const LlmChatMessageEntity({
11
+ const AiChatMessageEntity({
12
12
  this.id,
13
13
  required this.role,
14
14
  required this.content,
15
15
  required this.createdAt,
16
16
  });
17
17
 
18
- factory LlmChatMessageEntity.fromFirestore(
18
+ factory AiChatMessageEntity.fromFirestore(
19
19
  String id,
20
20
  Map<String, dynamic> data,
21
21
  ) {
22
- return LlmChatMessageEntity(
22
+ return AiChatMessageEntity(
23
23
  id: id,
24
24
  role: data['role'] as String,
25
25
  content: data['content'] as String,