kasy-cli 1.21.9 → 1.23.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 (269) 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 +22 -6
  67. package/lib/utils/env-tools.js +7 -0
  68. package/lib/utils/flutter-install.js +114 -0
  69. package/lib/utils/i18n/messages-en.js +52 -35
  70. package/lib/utils/i18n/messages-es.js +52 -35
  71. package/lib/utils/i18n/messages-pt.js +54 -37
  72. package/lib/utils/updates.js +15 -15
  73. package/package.json +1 -1
  74. package/templates/firebase/.env.example +2 -2
  75. package/templates/firebase/android/app/src/main/res/drawable/background.png +0 -0
  76. package/templates/firebase/android/app/src/main/res/drawable-night/background.png +0 -0
  77. package/templates/firebase/android/app/src/main/res/drawable-night-v21/background.png +0 -0
  78. package/templates/firebase/android/app/src/main/res/drawable-v21/background.png +0 -0
  79. package/templates/firebase/android/app/src/main/res/values-night-v31/styles.xml +1 -1
  80. package/templates/firebase/android/app/src/main/res/values-v31/styles.xml +1 -1
  81. package/templates/firebase/assets/images/logo_wordmark_dark.png +0 -0
  82. package/templates/firebase/assets/images/logo_wordmark_light.png +0 -0
  83. package/templates/firebase/docs/revenuecat-setup.es.md +1 -1
  84. package/templates/firebase/docs/revenuecat-setup.pt.md +1 -1
  85. package/templates/firebase/firestore.rules +24 -5
  86. package/templates/firebase/functions/package-lock.json +22 -1
  87. package/templates/firebase/functions/package.json +2 -1
  88. package/templates/firebase/functions/src/admin/functions.ts +113 -0
  89. package/templates/firebase/functions/src/{llm_chat → ai_chat}/index.ts +16 -16
  90. package/templates/firebase/functions/src/index.ts +8 -2
  91. package/templates/firebase/functions/src/notifications/device_triggers.ts +2 -2
  92. package/templates/firebase/functions/src/notifications/triggers.ts +3 -3
  93. package/templates/firebase/functions/src/subscriptions/models/subscription_status.ts +2 -0
  94. package/templates/firebase/functions/src/subscriptions/stripe_functions.ts +222 -0
  95. package/templates/firebase/functions/src/subscriptions/subscriptions_functions.ts +2 -2
  96. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png +0 -0
  97. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png +0 -0
  98. package/templates/firebase/lib/components/components.dart +4 -1
  99. package/templates/firebase/lib/components/kasy_app_bar.dart +22 -7
  100. package/templates/firebase/lib/components/kasy_avatar.dart +7 -6
  101. package/templates/firebase/lib/components/kasy_button.dart +23 -99
  102. package/templates/firebase/lib/components/kasy_dialog.dart +11 -11
  103. package/templates/firebase/lib/components/kasy_image_viewer.dart +84 -0
  104. package/templates/firebase/lib/components/{kasy_sidebar_pro.dart → kasy_sidebar.dart} +702 -425
  105. package/templates/firebase/lib/components/kasy_status_tag.dart +91 -0
  106. package/templates/firebase/lib/components/kasy_text_area.dart +1 -3
  107. package/templates/firebase/lib/components/kasy_text_field.dart +5 -6
  108. package/templates/firebase/lib/components/kasy_text_field_otp.dart +1 -3
  109. package/templates/firebase/lib/components/kasy_toast.dart +2 -2
  110. package/templates/firebase/lib/components/kasy_web_header.dart +209 -0
  111. package/templates/firebase/lib/core/ads/ads_provider.dart +1 -1
  112. package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +136 -23
  113. package/templates/firebase/lib/core/bottom_menu/bottom_router.dart +19 -91
  114. package/templates/firebase/lib/core/bottom_menu/kasy_bottom_bar_factory.dart +196 -96
  115. package/templates/firebase/lib/core/bottom_menu/web_content_wrapper.dart +54 -0
  116. package/templates/firebase/lib/core/config/app_env.dart +5 -11
  117. package/templates/firebase/lib/core/config/features.dart +5 -4
  118. package/templates/firebase/lib/core/data/api/analytics_api.dart +1 -1
  119. package/templates/firebase/lib/core/data/api/http_client.dart +1 -1
  120. package/templates/firebase/lib/core/data/entities/user_entity.dart +3 -0
  121. package/templates/firebase/lib/core/data/models/entitlement.dart +35 -0
  122. package/templates/firebase/lib/core/data/models/subscription.dart +13 -186
  123. package/templates/firebase/lib/core/data/models/user.dart +11 -0
  124. package/templates/firebase/lib/core/data/repositories/user_repository.dart +1 -1
  125. package/templates/firebase/lib/core/home_widgets/home_widget_background_task.dart +1 -1
  126. package/templates/firebase/lib/core/home_widgets/home_widget_mywidget_service.dart +1 -1
  127. package/templates/firebase/lib/core/icons/kasy_icons.dart +31 -1
  128. package/templates/firebase/lib/core/rating/api/rating_api.dart +2 -2
  129. package/templates/firebase/lib/core/states/logout_action.dart +25 -0
  130. package/templates/firebase/lib/core/states/user_state_notifier.dart +3 -3
  131. package/templates/firebase/lib/core/theme/colors.dart +488 -188
  132. package/templates/firebase/lib/core/theme/radius.dart +22 -11
  133. package/templates/firebase/lib/core/theme/shadows.dart +66 -0
  134. package/templates/firebase/lib/core/theme/texts.dart +75 -41
  135. package/templates/firebase/lib/core/theme/universal_theme.dart +9 -4
  136. package/templates/firebase/lib/core/web_device_preview/web_device_preview.dart +5 -4
  137. package/templates/firebase/lib/core/web_viewport_scale.dart +52 -0
  138. package/templates/firebase/lib/core/widgets/kasy_brand_badge.dart +118 -0
  139. package/templates/firebase/lib/core/widgets/kasy_brand_logo.dart +40 -0
  140. package/templates/firebase/lib/core/widgets/kasy_focus_ring.dart +125 -0
  141. package/templates/firebase/lib/core/widgets/kasy_hover.dart +53 -14
  142. package/templates/firebase/lib/core/widgets/kasy_pressable_depth.dart +18 -13
  143. package/templates/firebase/lib/{environnements.dart → environments.dart} +3 -14
  144. package/templates/firebase/lib/features/ai_chat/ai_chat_page.dart +159 -0
  145. package/templates/firebase/lib/features/ai_chat/api/ai_chat_api.dart +107 -0
  146. package/templates/firebase/lib/features/ai_chat/api/ai_chat_conversation_entity.dart +64 -0
  147. package/templates/firebase/lib/features/{llm_chat/api/llm_chat_message_entity.dart → ai_chat/api/ai_chat_message_entity.dart} +6 -6
  148. package/templates/firebase/lib/features/{llm_chat/providers/llm_chat_notifier.dart → ai_chat/providers/ai_chat_notifier.dart} +73 -38
  149. package/templates/firebase/lib/features/ai_chat/providers/ai_conversations_notifier.dart +103 -0
  150. package/templates/firebase/lib/features/{llm_chat/ui/widgets/llm_chat_avatars.dart → ai_chat/ui/widgets/ai_chat_avatars.dart} +13 -13
  151. package/templates/firebase/lib/features/{llm_chat/ui/widgets/llm_chat_composer.dart → ai_chat/ui/widgets/ai_chat_composer.dart} +10 -10
  152. package/templates/firebase/lib/features/{llm_chat/llm_chat_page.dart → ai_chat/ui/widgets/ai_chat_conversation_view.dart} +80 -67
  153. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_list.dart +285 -0
  154. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_tile.dart +163 -0
  155. package/templates/firebase/lib/features/authentication/api/authentication_api.dart +52 -13
  156. package/templates/firebase/lib/features/authentication/api/popup_dismiss_watcher.dart +12 -0
  157. package/templates/firebase/lib/features/authentication/api/popup_dismiss_watcher_web.dart +35 -0
  158. package/templates/firebase/lib/features/authentication/ui/recover_password_page.dart +108 -68
  159. package/templates/firebase/lib/features/authentication/ui/signin_page.dart +38 -51
  160. package/templates/firebase/lib/features/authentication/ui/signup_page.dart +38 -51
  161. package/templates/firebase/lib/features/authentication/ui/widgets/auth_card_scaffold.dart +128 -0
  162. package/templates/firebase/lib/features/authentication/ui/widgets/recover_password_result.dart +61 -44
  163. package/templates/firebase/lib/features/feedbacks/api/feature_request_api.dart +32 -0
  164. package/templates/firebase/lib/features/feedbacks/models/feedback_state.dart +5 -5
  165. package/templates/firebase/lib/features/feedbacks/providers/feedback_page_notifier.dart +2 -2
  166. package/templates/firebase/lib/features/home/design_system_page.dart +808 -170
  167. package/templates/firebase/lib/features/home/home_components_page.dart +6 -3
  168. package/templates/firebase/lib/features/home/home_components_preview_page.dart +6 -6
  169. package/templates/firebase/lib/features/home/home_components_preview_registry.dart +325 -186
  170. package/templates/firebase/lib/features/home/home_feed.dart +289 -0
  171. package/templates/firebase/lib/features/home/home_image_grid.dart +355 -0
  172. package/templates/firebase/lib/features/home/home_page.dart +11 -250
  173. package/templates/firebase/lib/features/{local_reminder → local_reminders}/providers/reminder_notifier.dart +1 -1
  174. package/templates/firebase/lib/features/{local_reminder → local_reminders}/ui/reminder_page.dart +2 -2
  175. package/templates/firebase/lib/features/notifications/shared/att_permission.dart +1 -1
  176. package/templates/firebase/lib/features/notifications/ui/request_notification_permission.dart +25 -61
  177. package/templates/firebase/lib/features/notifications/ui/widgets/permission_request_view.dart +117 -0
  178. package/templates/firebase/lib/features/onboarding/models/user_info.dart +16 -16
  179. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_att_setup.dart +4 -3
  180. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_features.dart +7 -9
  181. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_loader.dart +71 -48
  182. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +3 -2
  183. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_questions.dart +5 -5
  184. package/templates/firebase/lib/features/onboarding/ui/onboarding_page.dart +4 -4
  185. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_background.dart +4 -2
  186. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_feature.dart +39 -121
  187. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_illustration_scaffold.dart +105 -70
  188. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_module_mockups.dart +639 -0
  189. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_progress.dart +62 -50
  190. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +38 -28
  191. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_step_header.dart +75 -0
  192. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_sticky_footer.dart +16 -5
  193. package/templates/firebase/lib/features/onboarding/ui/widgets/selectable_row_tile.dart +26 -22
  194. package/templates/firebase/lib/features/settings/settings_page.dart +601 -90
  195. package/templates/firebase/lib/features/settings/ui/components/admin/admin_page.dart +1193 -0
  196. package/templates/firebase/lib/features/settings/ui/components/admin/admin_paywalls.dart +1 -1
  197. package/templates/firebase/lib/features/settings/ui/components/admin/admin_routes.dart +2 -3
  198. package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_api.dart +86 -0
  199. package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_tab.dart +1215 -0
  200. package/templates/firebase/lib/features/settings/ui/components/admin/send_push_notification_page.dart +236 -0
  201. package/templates/firebase/lib/features/settings/ui/components/avatar_component.dart +3 -3
  202. package/templates/firebase/lib/features/settings/ui/widgets/kasy_user_avatar.dart +77 -0
  203. package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +1 -0
  204. package/templates/firebase/lib/features/{subscription → subscriptions}/api/entities/subscription_entity.dart +17 -0
  205. package/templates/firebase/lib/features/{subscription → subscriptions}/api/inapp_subscription_api.dart +67 -46
  206. package/templates/firebase/lib/features/subscriptions/api/revenuecat_product.dart +189 -0
  207. package/templates/firebase/lib/features/subscriptions/api/stripe_backend_api.dart +55 -0
  208. package/templates/firebase/lib/features/subscriptions/api/stripe_payment_api.dart +82 -0
  209. package/templates/firebase/lib/features/subscriptions/api/stripe_product.dart +178 -0
  210. package/templates/firebase/lib/features/{subscription → subscriptions}/api/subscription_api.dart +1 -1
  211. package/templates/firebase/lib/features/subscriptions/api/subscription_payment_api.dart +53 -0
  212. package/templates/firebase/lib/features/subscriptions/api/subscription_payment_api_provider.dart +21 -0
  213. package/templates/firebase/lib/features/{subscription → subscriptions}/providers/premium_page_provider.dart +9 -6
  214. package/templates/firebase/lib/features/{subscription → subscriptions}/repositories/subscription_repository.dart +26 -30
  215. package/{lib/scaffold/backends/supabase/patch/lib/features/subscription → templates/firebase/lib/features/subscriptions}/shared/maybeshow_premium.dart +0 -2
  216. package/templates/firebase/lib/features/subscriptions/shared/subscription_management.dart +45 -0
  217. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/active_premium_content.dart +28 -4
  218. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_minimal.dart +7 -7
  219. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_row.dart +8 -8
  220. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_with_switch.dart +7 -7
  221. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/premium_content.dart +7 -7
  222. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/premium_page_factory.dart +5 -5
  223. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/premium_page.dart +5 -5
  224. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/comparison_table.dart +1 -1
  225. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/paywall_empty_state.dart +4 -4
  226. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_bottom_menu.dart +3 -4
  227. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/selectable_col.dart +1 -1
  228. package/templates/firebase/lib/i18n/en.i18n.json +171 -46
  229. package/templates/firebase/lib/i18n/es.i18n.json +175 -50
  230. package/templates/firebase/lib/i18n/pt.i18n.json +166 -41
  231. package/templates/firebase/lib/main.dart +6 -3
  232. package/templates/firebase/lib/router.dart +15 -23
  233. package/templates/firebase/pubspec.yaml +5 -6
  234. package/templates/firebase/test/core/data/repositories/user_repository_test.dart +3 -3
  235. package/templates/firebase/test/core/states/user_state_notifier_test.dart +4 -4
  236. package/templates/firebase/test/core/widgets/focus_ring_shape_test.dart +55 -0
  237. package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_inapp_subscription_api.dart +11 -4
  238. package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_revenuecat_product.dart +1 -0
  239. package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_subscription_api.dart +2 -2
  240. package/templates/firebase/test/features/{subscription → subscriptions}/subscription_page_test.dart +4 -4
  241. package/templates/firebase/test/test_utils.dart +6 -6
  242. package/templates/firebase/web/index.html +5 -2
  243. package/lib/scaffold/backends/api/patch/lib/features/llm_chat/api/llm_chat_api.dart +0 -58
  244. package/lib/scaffold/backends/api/patch/lib/features/subscription/shared/maybeshow_premium.dart +0 -86
  245. package/lib/scaffold/backends/supabase/migrations/20240101000009_llm_messages.sql +0 -22
  246. package/lib/scaffold/backends/supabase/patch/lib/features/llm_chat/api/llm_chat_api.dart +0 -47
  247. package/templates/firebase/assets/images/onboarding/authentication-login-template.jpg +0 -0
  248. package/templates/firebase/assets/images/onboarding/img2.jpg +0 -0
  249. package/templates/firebase/assets/images/onboarding/img3.jpg +0 -0
  250. package/templates/firebase/assets/images/onboarding/notifications.png +0 -0
  251. package/templates/firebase/assets/images/onboarding/purchase.png +0 -0
  252. package/templates/firebase/lib/core/sidebar/kasy_sidebar.dart +0 -2021
  253. package/templates/firebase/lib/features/authentication/ui/widgets/auth_brand.dart +0 -35
  254. package/templates/firebase/lib/features/home/home_features_page.dart +0 -207
  255. package/templates/firebase/lib/features/llm_chat/api/llm_chat_api.dart +0 -50
  256. package/templates/firebase/lib/features/settings/ui/components/admin/admin_bottom_sheet.dart +0 -316
  257. package/templates/firebase/lib/features/subscription/shared/maybeshow_premium.dart +0 -85
  258. /package/templates/firebase/lib/features/{local_reminder → local_reminders}/repositories/reminder_preferences.dart +0 -0
  259. /package/templates/firebase/lib/features/{subscription → subscriptions}/providers/models/premium_state.dart +0 -0
  260. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/feature_line.dart +0 -0
  261. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_background.dart +0 -0
  262. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_background_gradient.dart +0 -0
  263. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_banner.dart +0 -0
  264. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_card.dart +0 -0
  265. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_close_button.dart +0 -0
  266. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_feature.dart +0 -0
  267. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/selectable_row.dart +0 -0
  268. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/trial_switcher.dart +0 -0
  269. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/unsubscribe_feedback_popup.dart +0 -0
@@ -6,6 +6,7 @@ import 'package:flutter/services.dart';
6
6
  import 'package:flutter_riverpod/flutter_riverpod.dart';
7
7
  import 'package:kasy_kit/core/haptics/haptic_feedback_notifier.dart';
8
8
  import 'package:kasy_kit/core/theme/theme.dart';
9
+ import 'package:kasy_kit/core/widgets/kasy_focus_ring.dart';
9
10
 
10
11
  /// Universal hover/press wrapper — works on any widget shape, any platform.
11
12
  ///
@@ -45,8 +46,10 @@ class KasyHover extends ConsumerStatefulWidget {
45
46
  this.semanticLabel,
46
47
  this.hapticEnabled = true,
47
48
  this.hoverEnabled = true,
49
+ this.pressEnabled = true,
48
50
  this.hoverColor,
49
51
  this.pressColor,
52
+ this.focusable = false,
50
53
  });
51
54
 
52
55
  final Widget child;
@@ -75,6 +78,13 @@ class KasyHover extends ConsumerStatefulWidget {
75
78
  /// settings) where a hover highlight is visually unwanted.
76
79
  final bool hoverEnabled;
77
80
 
81
+ /// Whether the pressed-state background fill is shown on tap.
82
+ ///
83
+ /// When false, tapping triggers [onTap] (and haptic/cursor) with no visible
84
+ /// background highlight. Use on plain list rows (e.g. settings) where the tap
85
+ /// should just navigate without leaving a grey flash behind.
86
+ final bool pressEnabled;
87
+
78
88
  /// Exact background colour used while the pointer hovers over the widget.
79
89
  ///
80
90
  /// When non-null, this solid colour replaces the default semi-transparent
@@ -95,6 +105,13 @@ class KasyHover extends ConsumerStatefulWidget {
95
105
  /// background is already tinted so the feedback stays on-palette.
96
106
  final Color? pressColor;
97
107
 
108
+ /// When true, the control becomes a keyboard tab-stop by wrapping its visual
109
+ /// in the kit's [KasyFocusRing]: a focus ring appears during keyboard
110
+ /// navigation (never on pointer/touch) and Enter/Space triggers [onTap].
111
+ /// Pointer and touch behaviour are unchanged. Defaults to false so existing
112
+ /// call sites stay plain, non-focusable rows.
113
+ final bool focusable;
114
+
98
115
  @override
99
116
  ConsumerState<KasyHover> createState() => _KasyHoverState();
100
117
  }
@@ -117,22 +134,34 @@ class _KasyHoverState extends ConsumerState<KasyHover> {
117
134
  Color _overlayColor(BuildContext context) {
118
135
  final bool isDark = Theme.of(context).brightness == Brightness.dark;
119
136
  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.
137
+
138
+ // Nav-item style: a solid hover/selected colour was provided. Keep the
139
+ // resting, hover and pressed states on the SAME hue only the intensity
140
+ // changes, never the colour — so clicking never flashes.
141
+ //
142
+ // Resting must be that hue at alpha=0 (NOT Colors.transparent, which is
143
+ // transparent *black*): an AnimatedContainer lerping from black-transparent
144
+ // to a light solid passes through dark intermediates and flickers.
145
+ // Press = the hover fill blended a touch deeper, so hover→press→hover stays
146
+ // continuous instead of swapping to a different translucent overlay.
133
147
  if (widget.hoverColor != null) {
148
+ if (_pressed && widget.pressEnabled) {
149
+ return Color.alphaBlend(
150
+ base.withValues(alpha: isDark ? 0.06 : 0.08),
151
+ widget.hoverColor!,
152
+ );
153
+ }
154
+ if (_hovered && widget.hoverEnabled) return widget.hoverColor!;
134
155
  return widget.hoverColor!.withValues(alpha: 0);
135
156
  }
157
+
158
+ // Plain rows: a subtle translucent overlay that fades in on hover/press.
159
+ if (_pressed && widget.pressEnabled) {
160
+ return base.withValues(alpha: isDark ? 0.04 : 0.10);
161
+ }
162
+ if (_hovered && widget.hoverEnabled) {
163
+ return base.withValues(alpha: isDark ? 0.02 : 0.06);
164
+ }
136
165
  return Colors.transparent;
137
166
  }
138
167
 
@@ -174,13 +203,23 @@ class _KasyHoverState extends ConsumerState<KasyHover> {
174
203
  child: widget.child,
175
204
  );
176
205
 
206
+ // Keyboard focus is owned by the kit's single focus indicator so Tab +
207
+ // Enter/Space behave identically to login, signup and the chat send button.
208
+ final Widget focusContent = widget.focusable
209
+ ? KasyFocusRing(
210
+ borderRadius: widget.borderRadius,
211
+ onActivate: widget.onTap,
212
+ child: content,
213
+ )
214
+ : content;
215
+
177
216
  Widget interactive = GestureDetector(
178
217
  behavior: HitTestBehavior.opaque,
179
218
  onTap: _handleTap,
180
219
  onTapDown: _onTapDown,
181
220
  onTapUp: _onTapUp,
182
221
  onTapCancel: _onTapCancel,
183
- child: content,
222
+ child: focusContent,
184
223
  );
185
224
 
186
225
  // MouseRegion is active on all platforms:
@@ -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,