kasy-cli 1.21.8 → 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 (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 +86 -38
  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/enable-auth-via-cli.js +14 -6
  31. package/lib/scaffold/backends/firebase/generator.js +5 -5
  32. package/lib/scaffold/backends/firebase/setup-from-scratch.js +69 -45
  33. package/lib/scaffold/backends/firebase/tokens.js +4 -4
  34. package/lib/scaffold/backends/supabase/deploy.js +63 -11
  35. package/lib/scaffold/backends/supabase/edge-functions/admin-list-users/index.ts +149 -0
  36. package/lib/scaffold/backends/supabase/edge-functions/{llm-chat → ai-chat}/index.ts +19 -19
  37. package/lib/scaffold/backends/supabase/edge-functions/revenuecat-webhook/index.ts +2 -0
  38. package/lib/scaffold/backends/supabase/edge-functions/stripe-create-checkout-session/index.ts +123 -0
  39. package/lib/scaffold/backends/supabase/edge-functions/stripe-create-portal-session/index.ts +97 -0
  40. package/lib/scaffold/backends/supabase/edge-functions/stripe-list-prices/index.ts +83 -0
  41. package/lib/scaffold/backends/supabase/edge-functions/stripe-webhook/index.ts +138 -0
  42. package/lib/scaffold/backends/supabase/generator.js +17 -17
  43. package/lib/scaffold/backends/supabase/migrations/20240101000009_ai_messages.sql +50 -0
  44. package/lib/scaffold/backends/supabase/migrations/20240101000012_stripe_customers.sql +36 -0
  45. package/lib/scaffold/backends/supabase/migrations/20240101000013_admin_role.sql +62 -0
  46. package/lib/scaffold/backends/supabase/patch/lib/core/data/entities/user_entity.dart +4 -0
  47. package/lib/scaffold/backends/supabase/patch/lib/{environnements.dart → environments.dart} +3 -13
  48. package/lib/scaffold/backends/supabase/patch/lib/features/ai_chat/api/ai_chat_api.dart +95 -0
  49. package/lib/scaffold/backends/supabase/patch/lib/features/ai_chat/api/ai_chat_conversation_entity.dart +52 -0
  50. 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
  51. package/lib/scaffold/backends/supabase/patch/lib/features/{llm_chat/providers/llm_chat_notifier.dart → ai_chat/providers/ai_chat_notifier.dart} +63 -35
  52. package/lib/scaffold/backends/supabase/patch/lib/features/authentication/api/authentication_api.dart +1 -1
  53. package/lib/scaffold/backends/supabase/patch/lib/features/feedbacks/api/feature_request_api.dart +46 -0
  54. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/models/user_info.dart +16 -16
  55. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/components/onboarding_att_setup.dart +4 -3
  56. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +38 -28
  57. package/lib/scaffold/backends/supabase/patch/lib/features/settings/ui/components/admin/admin_users_api.dart +93 -0
  58. package/lib/scaffold/backends/supabase/patch/lib/features/{subscription → subscriptions}/api/entities/subscription_entity.dart +13 -0
  59. package/lib/scaffold/backends/supabase/patch/lib/features/subscriptions/api/stripe_backend_api.dart +52 -0
  60. package/lib/scaffold/backends/supabase/patch/lib/features/{subscription → subscriptions}/api/subscription_api.dart +1 -1
  61. package/lib/scaffold/backends/supabase/pubspec.yaml.tpl +4 -5
  62. package/lib/scaffold/backends/supabase/tokens.js +3 -3
  63. package/lib/scaffold/catalog.js +9 -11
  64. package/lib/scaffold/generate.js +45 -31
  65. package/lib/scaffold/shared/generator-utils.js +188 -81
  66. package/lib/scaffold/shared/sort-imports.js +191 -0
  67. package/lib/scaffold/shared/template-strings.js +3 -3
  68. package/lib/utils/checks.js +2 -2
  69. package/lib/utils/i18n/messages-en.js +50 -35
  70. package/lib/utils/i18n/messages-es.js +50 -35
  71. package/lib/utils/i18n/messages-pt.js +52 -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} +692 -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 +57 -4
  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 +41 -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 +33 -13
  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 +118 -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 +4 -5
  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
@@ -0,0 +1,289 @@
1
+ import 'package:flutter/material.dart';
2
+ import 'package:kasy_kit/components/kasy_card.dart';
3
+ import 'package:kasy_kit/core/haptics/kasy_haptics.dart';
4
+ import 'package:kasy_kit/core/theme/theme.dart';
5
+ import 'package:kasy_kit/features/home/home_image_grid.dart';
6
+
7
+ /// A browseable image category. Selecting one filters the feed below.
8
+ enum HomeCategory { productDesign, background, animated, icons3d, gradients }
9
+
10
+ class _CategoryData {
11
+ final String title;
12
+ final String subtitle;
13
+
14
+ /// Thumbnail shown inside the filter card.
15
+ final String thumbnailId;
16
+
17
+ /// Unsplash photo ids returned in the feed for this category.
18
+ final List<String> photoIds;
19
+
20
+ const _CategoryData({
21
+ required this.title,
22
+ required this.subtitle,
23
+ required this.thumbnailId,
24
+ required this.photoIds,
25
+ });
26
+ }
27
+
28
+ const Map<HomeCategory, _CategoryData> _categories = <HomeCategory, _CategoryData>{
29
+ HomeCategory.productDesign: _CategoryData(
30
+ title: 'Product Design',
31
+ subtitle: 'Sleek, abstract objects',
32
+ thumbnailId: '1556228720-195a672e8a03',
33
+ photoIds: <String>[
34
+ '1556228720-195a672e8a03',
35
+ '1542291026-7eec264c27ff',
36
+ '1505740420928-5e560c06d30e',
37
+ '1522338242992-e1a54906a8da',
38
+ '1523275335684-37898b6baf30',
39
+ '1572635196237-14b3f281503f',
40
+ '1526170375885-4d8ecf77b99f',
41
+ '1560769629-975ec94e6a86',
42
+ '1491553895911-0055eca6402d',
43
+ '1525966222134-fcfa99b8ae77',
44
+ '1484704849700-f032a568e944',
45
+ '1542219550-37153d387c27',
46
+ ],
47
+ ),
48
+ HomeCategory.background: _CategoryData(
49
+ title: 'Background',
50
+ subtitle: 'Dreamy, scenic vibes',
51
+ thumbnailId: '1501854140801-50d01698950b',
52
+ photoIds: <String>[
53
+ '1506905925346-21bda4d32df4',
54
+ '1469474968028-56623f02e42e',
55
+ '1441974231531-c6227db76b6e',
56
+ '1472214103451-9374bd1c798e',
57
+ '1447752875215-b2761acb3c5d',
58
+ '1426604966848-d7adac402bff',
59
+ '1433086966358-54859d0ed716',
60
+ '1418985991508-e47386d96a71',
61
+ '1505765050516-f72dcac9c60e',
62
+ '1454496522488-7a8e488e8606',
63
+ '1439066615861-d1af74d74000',
64
+ '1470071459604-3b5ec3a7fe05',
65
+ ],
66
+ ),
67
+ HomeCategory.animated: _CategoryData(
68
+ title: 'Animated',
69
+ subtitle: 'Minimalist, soft',
70
+ thumbnailId: '1502691876148-a84978e59af8',
71
+ photoIds: <String>[
72
+ '1541701494587-cb58502866ab',
73
+ '1559827260-dc66d52bef19',
74
+ '1462331940025-496dfbfc7564',
75
+ '1534796636912-3b95b3ab5986',
76
+ '1518837695005-2083093ee35b',
77
+ '1502691876148-a84978e59af8',
78
+ '1574169208507-84376144848b',
79
+ '1620641788421-7a1c342ea42e',
80
+ '1550859492-d5da9d8e45f3',
81
+ '1579546929518-9e396f3cc809',
82
+ '1614851099175-e5b30eb6f696',
83
+ '1557672172-298e090bd0f1',
84
+ ],
85
+ ),
86
+ HomeCategory.icons3d: _CategoryData(
87
+ title: '3D Icons',
88
+ subtitle: 'Clean, rounded icons',
89
+ thumbnailId: '1639762681485-074b7f938ba0',
90
+ photoIds: <String>[
91
+ '1634986666676-ec8fd927c23d',
92
+ '1639762681485-074b7f938ba0',
93
+ '1633899306328-c5e70574aaa2',
94
+ '1634017839464-5c339ebe3cb4',
95
+ '1644143379190-08a5f055de1d',
96
+ '1617791160505-6f00504e3519',
97
+ '1618005182384-a83a8bd57fbe',
98
+ '1635776062127-d379bfcba9f8',
99
+ '1618556450994-a6a128ef0d9d',
100
+ '1620207418302-439b387441b0',
101
+ '1604079628040-94301bb21b91',
102
+ '1557682250-33bd709cbe85',
103
+ ],
104
+ ),
105
+ HomeCategory.gradients: _CategoryData(
106
+ title: 'Gradients',
107
+ subtitle: 'Smooth, vivid color',
108
+ thumbnailId: '1557672172-298e090bd0f1',
109
+ photoIds: <String>[
110
+ '1557672172-298e090bd0f1',
111
+ '1557683316-973673baf926',
112
+ '1579546929518-9e396f3cc809',
113
+ '1620207418302-439b387441b0',
114
+ '1614851099175-e5b30eb6f696',
115
+ '1550859492-d5da9d8e45f3',
116
+ '1557682250-33bd709cbe85',
117
+ '1557682224-5b8590cd9ec5',
118
+ '1604079628040-94301bb21b91',
119
+ '1508614999368-9260051292e5',
120
+ '1554189097-ffe88e998a2b',
121
+ '1620641788421-7a1c342ea42e',
122
+ ],
123
+ ),
124
+ };
125
+
126
+ // Author / timestamp pools — varied per photo by index so captions feel real
127
+ // without hand-authoring metadata for every image.
128
+ const List<String> _authors = <String>[
129
+ 'Elena Park', 'Marcus Vale', 'Liang Wu', 'Sofia Reis', 'Noah Fields',
130
+ 'Aya Tanaka', 'Hugo Brandt', 'Petra Nilsson', 'Diego Sol', 'Mara Lopez',
131
+ 'Theo Klein', 'Ines Costa', 'Omar Aziz', 'Lena Fox',
132
+ ];
133
+ const List<String> _agos = <String>[
134
+ '1 day ago', '2 days ago', '3 days ago', '5 days ago',
135
+ '6 days ago', '1 week ago', '2 weeks ago', '4 days ago',
136
+ ];
137
+
138
+ List<HomePhoto> _photosFor(HomeCategory category) {
139
+ final List<String> ids = _categories[category]!.photoIds;
140
+ return <HomePhoto>[
141
+ for (int i = 0; i < ids.length; i++)
142
+ HomePhoto(
143
+ id: ids[i],
144
+ author: _authors[i % _authors.length],
145
+ ago: _agos[i % _agos.length],
146
+ ),
147
+ ];
148
+ }
149
+
150
+ String _thumbUrl(String id) =>
151
+ 'https://images.unsplash.com/photo-$id?w=160&h=160&fit=crop&q=80&auto=format';
152
+
153
+ /// Home content: a selectable category filter row over a masonry image feed.
154
+ class HomeFeed extends StatefulWidget {
155
+ const HomeFeed({super.key});
156
+
157
+ @override
158
+ State<HomeFeed> createState() => _HomeFeedState();
159
+ }
160
+
161
+ class _HomeFeedState extends State<HomeFeed> {
162
+ HomeCategory _selected = HomeCategory.productDesign;
163
+
164
+ void _select(HomeCategory category) {
165
+ if (category == _selected) return;
166
+ KasyHaptics.light(context);
167
+ setState(() => _selected = category);
168
+ }
169
+
170
+ @override
171
+ Widget build(BuildContext context) {
172
+ return SingleChildScrollView(
173
+ child: Column(
174
+ crossAxisAlignment: CrossAxisAlignment.stretch,
175
+ children: <Widget>[
176
+ const SizedBox(height: KasySpacing.belowChromeContentGap),
177
+ _FilterRow(selected: _selected, onSelect: _select),
178
+ const SizedBox(height: KasySpacing.lg),
179
+ Padding(
180
+ // Reserve the floating bar's height (injected as the bottom inset by
181
+ // the host Scaffold's extendBody) so the last row settles above it.
182
+ padding: EdgeInsets.fromLTRB(
183
+ KasySpacing.md,
184
+ 0,
185
+ KasySpacing.md,
186
+ KasySpacing.lg + MediaQuery.paddingOf(context).bottom,
187
+ ),
188
+ child: HomeImageGrid(photos: _photosFor(_selected)),
189
+ ),
190
+ ],
191
+ ),
192
+ );
193
+ }
194
+ }
195
+
196
+ class _FilterRow extends StatelessWidget {
197
+ final HomeCategory selected;
198
+ final ValueChanged<HomeCategory> onSelect;
199
+
200
+ const _FilterRow({required this.selected, required this.onSelect});
201
+
202
+ @override
203
+ Widget build(BuildContext context) {
204
+ const List<HomeCategory> all = HomeCategory.values;
205
+
206
+ return SizedBox(
207
+ height: 111,
208
+ child: ListView.separated(
209
+ scrollDirection: Axis.horizontal,
210
+ padding: const EdgeInsets.symmetric(horizontal: KasySpacing.md),
211
+ itemCount: all.length,
212
+ separatorBuilder: (_, _) => const SizedBox(width: KasySpacing.md),
213
+ itemBuilder: (BuildContext context, int i) => SizedBox(
214
+ width: 302,
215
+ child: _FilterCard(
216
+ category: all[i],
217
+ selected: all[i] == selected,
218
+ onTap: () => onSelect(all[i]),
219
+ ),
220
+ ),
221
+ ),
222
+ );
223
+ }
224
+ }
225
+
226
+ class _FilterCard extends StatelessWidget {
227
+ final HomeCategory category;
228
+ final bool selected;
229
+ final VoidCallback onTap;
230
+
231
+ const _FilterCard({
232
+ required this.category,
233
+ required this.selected,
234
+ required this.onTap,
235
+ });
236
+
237
+ @override
238
+ Widget build(BuildContext context) {
239
+ final KasyColors c = context.colors;
240
+ final _CategoryData data = _categories[category]!;
241
+
242
+ return KasyCard(
243
+ onTap: onTap,
244
+ semanticLabel: data.title,
245
+ variant: selected ? KasyCardVariant.elevated : KasyCardVariant.filled,
246
+ borderColor: selected ? c.primary : null,
247
+ padding: const EdgeInsets.all(KasySpacing.md),
248
+ child: Row(
249
+ children: <Widget>[
250
+ ClipRRect(
251
+ borderRadius: BorderRadius.circular(KasyRadius.lg),
252
+ child: SizedBox(
253
+ width: 79,
254
+ height: 79,
255
+ child: KasyNetworkImage(url: _thumbUrl(data.thumbnailId)),
256
+ ),
257
+ ),
258
+ const SizedBox(width: KasySpacing.md),
259
+ Expanded(
260
+ child: Column(
261
+ crossAxisAlignment: CrossAxisAlignment.start,
262
+ mainAxisAlignment: MainAxisAlignment.center,
263
+ mainAxisSize: MainAxisSize.min,
264
+ children: <Widget>[
265
+ Text(
266
+ data.title,
267
+ maxLines: 1,
268
+ overflow: TextOverflow.ellipsis,
269
+ style: context.textTheme.titleSmall?.copyWith(
270
+ color: selected ? c.primary : c.onSurface,
271
+ ),
272
+ ),
273
+ const SizedBox(height: KasySpacing.xs),
274
+ Text(
275
+ data.subtitle,
276
+ maxLines: 1,
277
+ overflow: TextOverflow.ellipsis,
278
+ style: context.textTheme.bodySmall?.copyWith(
279
+ color: c.muted,
280
+ ),
281
+ ),
282
+ ],
283
+ ),
284
+ ),
285
+ ],
286
+ ),
287
+ );
288
+ }
289
+ }
@@ -0,0 +1,355 @@
1
+ import 'dart:ui' as ui;
2
+
3
+ import 'package:flutter/material.dart';
4
+ import 'package:kasy_kit/components/kasy_card.dart';
5
+ import 'package:kasy_kit/components/kasy_image_viewer.dart';
6
+ import 'package:kasy_kit/components/kasy_skeleton.dart';
7
+ import 'package:kasy_kit/core/haptics/kasy_haptics.dart';
8
+ import 'package:kasy_kit/core/theme/theme.dart';
9
+
10
+ /// A single image shown in the masonry feed.
11
+ class HomePhoto {
12
+ final String id;
13
+ final String author;
14
+ final String ago;
15
+
16
+ const HomePhoto({required this.id, required this.author, required this.ago});
17
+
18
+ String get url =>
19
+ 'https://images.unsplash.com/photo-$id?w=600&q=75&fit=crop&auto=format';
20
+ }
21
+
22
+ /// Network image with a synced shimmer skeleton while loading and a graceful
23
+ /// fallback on error. Adapts to light/dark via [KasySkeleton]'s own palette.
24
+ class KasyNetworkImage extends StatelessWidget {
25
+ const KasyNetworkImage({super.key, required this.url, this.fit = BoxFit.cover});
26
+
27
+ final String url;
28
+ final BoxFit fit;
29
+
30
+ @override
31
+ Widget build(BuildContext context) {
32
+ return Image.network(
33
+ url,
34
+ fit: fit,
35
+ gaplessPlayback: true,
36
+ loadingBuilder: (
37
+ BuildContext context,
38
+ Widget child,
39
+ ImageChunkEvent? progress,
40
+ ) {
41
+ if (progress == null) return child;
42
+ return const _ImageSkeleton();
43
+ },
44
+ errorBuilder: (
45
+ BuildContext context,
46
+ Object error,
47
+ StackTrace? stackTrace,
48
+ ) {
49
+ return ColoredBox(
50
+ color: context.colors.surfaceNeutralSoft,
51
+ child: Icon(KasyIcons.gallery, color: context.colors.muted),
52
+ );
53
+ },
54
+ );
55
+ }
56
+ }
57
+
58
+ class _ImageSkeleton extends StatelessWidget {
59
+ const _ImageSkeleton();
60
+
61
+ @override
62
+ Widget build(BuildContext context) {
63
+ return const KasySkeletonGroup(
64
+ child: KasySkeleton(
65
+ width: double.infinity,
66
+ height: double.infinity,
67
+ borderRadius: BorderRadius.zero,
68
+ ),
69
+ );
70
+ }
71
+ }
72
+
73
+ /// Pinterest-style masonry feed. Tile heights vary by a repeating ratio set and
74
+ /// tiles are balanced across columns by running height. Column count is
75
+ /// responsive: 2 on phones, 3 on tablets, 4 on desktop.
76
+ class HomeImageGrid extends StatelessWidget {
77
+ const HomeImageGrid({super.key, required this.photos});
78
+
79
+ final List<HomePhoto> photos;
80
+
81
+ static const double _gap = KasySpacing.smd;
82
+ static const List<double> _ratios = <double>[
83
+ 0.72, 1.0, 0.8, 1.32, 0.75, 1.0, 0.7, 1.25, 0.85, 1.4,
84
+ ];
85
+
86
+ int _columnsFor(double width) {
87
+ if (width >= 1024) return 4;
88
+ if (width >= 600) return 3;
89
+ return 2;
90
+ }
91
+
92
+ @override
93
+ Widget build(BuildContext context) {
94
+ return LayoutBuilder(
95
+ builder: (BuildContext context, BoxConstraints constraints) {
96
+ final double width = constraints.maxWidth;
97
+ final int columns = _columnsFor(width);
98
+ final double columnWidth = (width - _gap * (columns - 1)) / columns;
99
+
100
+ final List<List<Widget>> buckets =
101
+ List<List<Widget>>.generate(columns, (_) => <Widget>[]);
102
+ final List<double> heights = List<double>.filled(columns, 0);
103
+
104
+ for (int i = 0; i < photos.length; i++) {
105
+ final double ratio = _ratios[i % _ratios.length];
106
+ int shortest = 0;
107
+ for (int c = 1; c < columns; c++) {
108
+ if (heights[c] < heights[shortest]) shortest = c;
109
+ }
110
+ if (buckets[shortest].isNotEmpty) {
111
+ buckets[shortest].add(const SizedBox(height: _gap));
112
+ }
113
+ buckets[shortest]
114
+ .add(_PhotoTile(photo: photos[i], aspectRatio: ratio));
115
+ heights[shortest] += columnWidth / ratio + _gap;
116
+ }
117
+
118
+ return Row(
119
+ crossAxisAlignment: CrossAxisAlignment.start,
120
+ children: <Widget>[
121
+ for (int col = 0; col < columns; col++) ...<Widget>[
122
+ if (col > 0) const SizedBox(width: _gap),
123
+ Expanded(child: Column(children: buckets[col])),
124
+ ],
125
+ ],
126
+ );
127
+ },
128
+ );
129
+ }
130
+ }
131
+
132
+ class _PhotoTile extends StatefulWidget {
133
+ final HomePhoto photo;
134
+ final double aspectRatio;
135
+
136
+ const _PhotoTile({required this.photo, required this.aspectRatio});
137
+
138
+ @override
139
+ State<_PhotoTile> createState() => _PhotoTileState();
140
+ }
141
+
142
+ class _PhotoTileState extends State<_PhotoTile> {
143
+ bool _liked = false;
144
+
145
+ void _toggleLike() {
146
+ KasyHaptics.light(context);
147
+ setState(() => _liked = !_liked);
148
+ }
149
+
150
+ void _openViewer() {
151
+ KasyHaptics.light(context);
152
+ showKasyImageViewer(
153
+ context,
154
+ imageUrl: widget.photo.url,
155
+ heroTag: widget.photo.id,
156
+ );
157
+ }
158
+
159
+ @override
160
+ Widget build(BuildContext context) {
161
+ final HomePhoto photo = widget.photo;
162
+
163
+ return KasyCard(
164
+ padding: EdgeInsets.zero,
165
+ child: AspectRatio(
166
+ aspectRatio: widget.aspectRatio,
167
+ child: Stack(
168
+ fit: StackFit.expand,
169
+ children: <Widget>[
170
+ // Tapping the photo opens the full-screen viewer; the Hero gives a
171
+ // smooth zoom into and out of it.
172
+ Positioned.fill(
173
+ child: GestureDetector(
174
+ onTap: _openViewer,
175
+ behavior: HitTestBehavior.opaque,
176
+ child: Hero(
177
+ tag: photo.id,
178
+ child: KasyNetworkImage(url: photo.url),
179
+ ),
180
+ ),
181
+ ),
182
+
183
+ // Smooth caption scrim — keeps text legible over any photo, in
184
+ // both light and dark themes.
185
+ const Positioned(
186
+ left: 0,
187
+ right: 0,
188
+ bottom: 0,
189
+ child: _CaptionScrim(),
190
+ ),
191
+
192
+ Positioned(
193
+ left: 0,
194
+ right: 0,
195
+ bottom: 0,
196
+ child: Padding(
197
+ padding: const EdgeInsets.all(KasySpacing.md),
198
+ child: Row(
199
+ crossAxisAlignment: CrossAxisAlignment.end,
200
+ children: <Widget>[
201
+ Expanded(
202
+ child: Column(
203
+ crossAxisAlignment: CrossAxisAlignment.start,
204
+ mainAxisSize: MainAxisSize.min,
205
+ children: <Widget>[
206
+ Text(
207
+ photo.author,
208
+ maxLines: 1,
209
+ overflow: TextOverflow.ellipsis,
210
+ style: context.textTheme.titleSmall?.copyWith(
211
+ color: context.colors.onSurface,
212
+ ),
213
+ ),
214
+ Text(
215
+ photo.ago,
216
+ maxLines: 1,
217
+ overflow: TextOverflow.ellipsis,
218
+ style: context.textTheme.bodySmall?.copyWith(
219
+ color: context.colors.muted,
220
+ ),
221
+ ),
222
+ ],
223
+ ),
224
+ ),
225
+ const SizedBox(width: KasySpacing.sm),
226
+ _LikeButton(liked: _liked, onTap: _toggleLike),
227
+ ],
228
+ ),
229
+ ),
230
+ ),
231
+ ],
232
+ ),
233
+ ),
234
+ );
235
+ }
236
+ }
237
+
238
+ /// A smooth caption scrim: one continuous [surface]-tinted gradient that fades
239
+ /// from solid at the bottom to fully transparent at the top — no banding, no
240
+ /// hard edges. Adapts to light/dark via the theme's [surface] colour.
241
+ class _CaptionScrim extends StatelessWidget {
242
+ const _CaptionScrim();
243
+
244
+ static const double _height = 88;
245
+
246
+ @override
247
+ Widget build(BuildContext context) {
248
+ final Color base = context.colors.surface;
249
+
250
+ return Container(
251
+ height: _height,
252
+ decoration: BoxDecoration(
253
+ gradient: LinearGradient(
254
+ begin: Alignment.bottomCenter,
255
+ end: Alignment.topCenter,
256
+ colors: <Color>[
257
+ base.withValues(alpha: 0.94),
258
+ base.withValues(alpha: 0.62),
259
+ base.withValues(alpha: 0.0),
260
+ ],
261
+ stops: const <double>[0.0, 0.45, 1.0],
262
+ ),
263
+ ),
264
+ );
265
+ }
266
+ }
267
+
268
+ /// Circular glass "like" button. The heart pops (scales with an overshoot) and
269
+ /// turns the theme's danger red when liked; idle state stays neutral.
270
+ class _LikeButton extends StatefulWidget {
271
+ final bool liked;
272
+ final VoidCallback onTap;
273
+
274
+ const _LikeButton({required this.liked, required this.onTap});
275
+
276
+ @override
277
+ State<_LikeButton> createState() => _LikeButtonState();
278
+ }
279
+
280
+ class _LikeButtonState extends State<_LikeButton>
281
+ with SingleTickerProviderStateMixin {
282
+ late final AnimationController _controller;
283
+ late final Animation<double> _scale;
284
+
285
+ @override
286
+ void initState() {
287
+ super.initState();
288
+ _controller = AnimationController(
289
+ vsync: this,
290
+ duration: const Duration(milliseconds: 320),
291
+ );
292
+ _scale = TweenSequence<double>(<TweenSequenceItem<double>>[
293
+ TweenSequenceItem<double>(
294
+ tween: Tween<double>(begin: 1.0, end: 1.35)
295
+ .chain(CurveTween(curve: Curves.easeOut)),
296
+ weight: 40,
297
+ ),
298
+ TweenSequenceItem<double>(
299
+ tween: Tween<double>(begin: 1.35, end: 1.0)
300
+ .chain(CurveTween(curve: Curves.elasticOut)),
301
+ weight: 60,
302
+ ),
303
+ ]).animate(_controller);
304
+ }
305
+
306
+ @override
307
+ void didUpdateWidget(_LikeButton old) {
308
+ super.didUpdateWidget(old);
309
+ // Pop only when transitioning into the liked state.
310
+ if (widget.liked && !old.liked) {
311
+ _controller.forward(from: 0);
312
+ }
313
+ }
314
+
315
+ @override
316
+ void dispose() {
317
+ _controller.dispose();
318
+ super.dispose();
319
+ }
320
+
321
+ @override
322
+ Widget build(BuildContext context) {
323
+ final bool dark = context.isDark;
324
+ final Color heart =
325
+ widget.liked ? context.colors.danger : context.colors.primary;
326
+
327
+ return GestureDetector(
328
+ onTap: widget.onTap,
329
+ behavior: HitTestBehavior.opaque,
330
+ child: ClipOval(
331
+ child: BackdropFilter(
332
+ filter: ui.ImageFilter.blur(sigmaX: 8, sigmaY: 8),
333
+ child: Container(
334
+ width: 36,
335
+ height: 36,
336
+ alignment: Alignment.center,
337
+ decoration: BoxDecoration(
338
+ color: context.colors.surface
339
+ .withValues(alpha: dark ? 0.5 : 0.7),
340
+ shape: BoxShape.circle,
341
+ ),
342
+ child: ScaleTransition(
343
+ scale: _scale,
344
+ child: Icon(
345
+ widget.liked ? KasyIcons.favoriteFilled : KasyIcons.favorite,
346
+ size: 18,
347
+ color: heart,
348
+ ),
349
+ ),
350
+ ),
351
+ ),
352
+ ),
353
+ );
354
+ }
355
+ }