kasy-cli 1.34.0 → 1.36.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 (141) hide show
  1. package/README.md +1 -1
  2. package/bin/kasy.js +24 -2
  3. package/docs/cli-reference.md +7 -7
  4. package/lib/commands/new.js +11 -9
  5. package/lib/commands/release-version.js +234 -0
  6. package/lib/commands/update.js +27 -0
  7. package/lib/scaffold/CHANGELOG.json +18 -0
  8. package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api.dart +24 -0
  9. package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api_interface.dart +15 -0
  10. package/lib/scaffold/backends/api/patch/lib/features/authentication/repositories/authentication_repository.dart +27 -0
  11. package/lib/scaffold/backends/api/patch/lib/features/subscriptions/api/entities/subscription_entity.dart +1 -0
  12. package/lib/scaffold/backends/firebase/setup-from-scratch.js +35 -21
  13. package/lib/scaffold/backends/patch-base-hashes.json +66 -0
  14. package/lib/scaffold/backends/supabase/edge-functions/stripe-webhook/index.ts +2 -0
  15. package/lib/scaffold/backends/supabase/migrations/20240101000007_welcome_notification.sql +36 -23
  16. package/lib/scaffold/backends/supabase/migrations/20240101000014_subscription_trial_end.sql +6 -0
  17. package/lib/scaffold/backends/supabase/patch/lib/features/authentication/api/authentication_api.dart +82 -3
  18. package/lib/scaffold/backends/supabase/patch/lib/features/authentication/repositories/authentication_repository.dart +36 -0
  19. package/lib/scaffold/backends/supabase/patch/lib/features/subscriptions/api/entities/subscription_entity.dart +1 -0
  20. package/lib/scaffold/generate.js +53 -4
  21. package/lib/utils/i18n/messages-en.js +23 -0
  22. package/lib/utils/i18n/messages-es.js +23 -0
  23. package/lib/utils/i18n/messages-pt.js +23 -0
  24. package/package.json +5 -2
  25. package/templates/firebase/.firebase/hosting.YnVpbGQvd2Vi.cache +73 -0
  26. package/templates/firebase/AGENTS.md +83 -0
  27. package/templates/firebase/DESIGN_SYSTEM.md +37 -2
  28. package/templates/firebase/docs/auth-setup.en.md +2 -0
  29. package/templates/firebase/docs/auth-setup.es.md +2 -0
  30. package/templates/firebase/docs/auth-setup.pt.md +2 -0
  31. package/templates/firebase/firebase.json +56 -1
  32. package/templates/firebase/functions/src/authentication/triggers.ts +10 -6
  33. package/templates/firebase/functions/src/core/data/entities/subscription_entity.ts +5 -0
  34. package/templates/firebase/functions/src/core/data/entities/user_entity.ts +9 -1
  35. package/templates/firebase/functions/src/core/data/repositories/user_repository.ts +27 -0
  36. package/templates/firebase/functions/src/subscriptions/models/subscriptions.ts +3 -0
  37. package/templates/firebase/functions/src/subscriptions/stripe_functions.ts +4 -0
  38. package/templates/firebase/lib/components/kasy_alert.dart +0 -1
  39. package/templates/firebase/lib/components/kasy_app_bar.dart +31 -16
  40. package/templates/firebase/lib/components/kasy_bottom_sheet.dart +0 -1
  41. package/templates/firebase/lib/components/kasy_date_picker.dart +0 -5
  42. package/templates/firebase/lib/components/kasy_dialog.dart +0 -1
  43. package/templates/firebase/lib/components/kasy_otp_verification_bottom_sheet.dart +1 -1
  44. package/templates/firebase/lib/components/kasy_sidebar.dart +215 -178
  45. package/templates/firebase/lib/components/kasy_text_area.dart +0 -1
  46. package/templates/firebase/lib/components/kasy_text_field.dart +0 -1
  47. package/templates/firebase/lib/components/kasy_text_field_otp.dart +0 -1
  48. package/templates/firebase/lib/components/kasy_toast.dart +107 -41
  49. package/templates/firebase/lib/core/app_update/app_update_repository.dart +54 -0
  50. package/templates/firebase/lib/core/app_update/app_update_status.dart +14 -0
  51. package/templates/firebase/lib/core/app_update/update_available_sheet.dart +70 -0
  52. package/templates/firebase/lib/core/bottom_menu/active_tab_notifier.dart +40 -3
  53. package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +82 -34
  54. package/templates/firebase/lib/core/bottom_menu/web_content_wrapper.dart +6 -6
  55. package/templates/firebase/lib/core/bottom_menu/web_url.dart +20 -0
  56. package/templates/firebase/lib/core/data/api/remote_config_api.dart +38 -1
  57. package/templates/firebase/lib/core/guards/guard.dart +16 -2
  58. package/templates/firebase/lib/core/icons/kasy_icons.dart +3 -0
  59. package/templates/firebase/lib/core/rating/widgets/rate_banner.dart +2 -5
  60. package/templates/firebase/lib/core/rating/widgets/review_popup.dart +5 -3
  61. package/templates/firebase/lib/core/shared_preferences/shared_preferences.dart +14 -0
  62. package/templates/firebase/lib/core/states/components/maybe_show_update_available.dart +32 -0
  63. package/templates/firebase/lib/core/states/logout_action.dart +5 -1
  64. package/templates/firebase/lib/core/states/user_state_notifier.dart +85 -14
  65. package/templates/firebase/lib/core/theme/responsive_text_theme.dart +69 -0
  66. package/templates/firebase/lib/core/theme/texts.dart +90 -57
  67. package/templates/firebase/lib/core/theme/type_scale.dart +77 -0
  68. package/templates/firebase/lib/core/theme/web_background_sync_web.dart +20 -6
  69. package/templates/firebase/lib/core/utils/image_bytes_loader.dart +12 -0
  70. package/templates/firebase/lib/core/utils/image_bytes_loader_io.dart +28 -0
  71. package/templates/firebase/lib/core/utils/image_bytes_loader_web.dart +56 -0
  72. package/templates/firebase/lib/core/web_screen_width.dart +15 -0
  73. package/templates/firebase/lib/core/web_screen_width_io.dart +3 -0
  74. package/templates/firebase/lib/core/web_screen_width_web.dart +10 -0
  75. package/templates/firebase/lib/core/web_viewport_scale.dart +61 -25
  76. package/templates/firebase/lib/core/widgets/focus_visibility.dart +89 -0
  77. package/templates/firebase/lib/features/ai_chat/ai_chat_page.dart +1 -2
  78. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_chat_conversation_view.dart +1 -2
  79. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_list.dart +2 -3
  80. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_tile.dart +1 -2
  81. package/templates/firebase/lib/features/authentication/api/auth_web_support.dart +12 -0
  82. package/templates/firebase/lib/features/authentication/api/auth_web_support_web.dart +25 -0
  83. package/templates/firebase/lib/features/authentication/api/authentication_api.dart +205 -0
  84. package/templates/firebase/lib/features/authentication/api/authentication_api_interface.dart +22 -0
  85. package/templates/firebase/lib/features/authentication/navigation/post_login_navigation.dart +32 -0
  86. package/templates/firebase/lib/features/authentication/providers/phone_auth_notifier.dart +7 -7
  87. package/templates/firebase/lib/features/authentication/providers/signin_state_provider.dart +34 -10
  88. package/templates/firebase/lib/features/authentication/providers/signup_state_provider.dart +2 -2
  89. package/templates/firebase/lib/features/authentication/repositories/authentication_repository.dart +37 -0
  90. package/templates/firebase/lib/features/authentication/repositories/exceptions/authentication_exceptions.dart +8 -0
  91. package/templates/firebase/lib/features/authentication/ui/components/otp_verification.dart +2 -2
  92. package/templates/firebase/lib/features/authentication/ui/components/phone_input.dart +2 -2
  93. package/templates/firebase/lib/features/authentication/ui/signin_page.dart +59 -0
  94. package/templates/firebase/lib/features/authentication/ui/widgets/auth_card_scaffold.dart +2 -2
  95. package/templates/firebase/lib/features/authentication/ui/widgets/recover_password_result.dart +2 -2
  96. package/templates/firebase/lib/features/feedbacks/ui/widgets/add_feature_button.dart +0 -1
  97. package/templates/firebase/lib/features/home/design_system_page.dart +134 -67
  98. package/templates/firebase/lib/features/home/home_components_page.dart +4 -3
  99. package/templates/firebase/lib/features/home/home_components_preview_page.dart +1 -3
  100. package/templates/firebase/lib/features/home/home_components_preview_registry.dart +226 -105
  101. package/templates/firebase/lib/features/home/home_page.dart +4 -0
  102. package/templates/firebase/lib/features/local_reminders/ui/reminder_page.dart +8 -3
  103. package/templates/firebase/lib/features/notifications/providers/notifications_provider.dart +10 -0
  104. package/templates/firebase/lib/features/notifications/ui/notifications_page.dart +3 -1
  105. package/templates/firebase/lib/features/notifications/ui/widgets/notification_tile.dart +8 -3
  106. package/templates/firebase/lib/features/onboarding/providers/onboarding_model.dart +11 -0
  107. package/templates/firebase/lib/features/onboarding/providers/onboarding_provider.dart +30 -3
  108. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_loader.dart +13 -4
  109. package/templates/firebase/lib/features/settings/settings_page.dart +152 -11
  110. package/templates/firebase/lib/features/settings/ui/components/admin/admin_page.dart +43 -15
  111. package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_tab.dart +12 -12
  112. package/templates/firebase/lib/features/settings/ui/components/admin/send_push_notification_page.dart +1 -4
  113. package/templates/firebase/lib/features/settings/ui/components/create_password_sheet.dart +141 -0
  114. package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +0 -1
  115. package/templates/firebase/lib/features/settings/ui/widgets/admin_card.dart +42 -38
  116. package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +17 -2
  117. package/templates/firebase/lib/features/subscriptions/api/entities/subscription_entity.dart +3 -0
  118. package/templates/firebase/lib/features/subscriptions/api/stripe_product.dart +12 -11
  119. package/templates/firebase/lib/features/subscriptions/repositories/subscription_repository.dart +25 -2
  120. package/templates/firebase/lib/features/subscriptions/ui/component/active_premium_content.dart +319 -143
  121. package/templates/firebase/lib/features/subscriptions/ui/component/paywall_minimal.dart +1 -5
  122. package/templates/firebase/lib/features/subscriptions/ui/component/paywall_row.dart +1 -5
  123. package/templates/firebase/lib/features/subscriptions/ui/component/paywall_with_switch.dart +2 -5
  124. package/templates/firebase/lib/features/subscriptions/ui/component/premium_content.dart +1 -4
  125. package/templates/firebase/lib/features/subscriptions/ui/widgets/feature_line.dart +1 -2
  126. package/templates/firebase/lib/features/subscriptions/ui/widgets/premium_banner.dart +2 -7
  127. package/templates/firebase/lib/features/subscriptions/ui/widgets/premium_feature.dart +2 -4
  128. package/templates/firebase/lib/features/subscriptions/ui/widgets/selectable_col.dart +1 -4
  129. package/templates/firebase/lib/features/subscriptions/ui/widgets/selectable_row.dart +0 -3
  130. package/templates/firebase/lib/i18n/en.i18n.json +49 -3
  131. package/templates/firebase/lib/i18n/es.i18n.json +49 -3
  132. package/templates/firebase/lib/i18n/pt.i18n.json +49 -3
  133. package/templates/firebase/lib/main.dart +11 -2
  134. package/templates/firebase/lib/router.dart +92 -13
  135. package/templates/firebase/pubspec.yaml +1 -1
  136. package/templates/firebase/test/core/data/api/fake_remote_config_api.dart +14 -0
  137. package/templates/firebase/test/core/states/user_state_notifier_test.dart +47 -3
  138. package/templates/firebase/test/core/web_viewport_scale_test.dart +68 -0
  139. package/templates/firebase/test/features/authentication/data/api/auth_api_fake.dart +15 -0
  140. package/templates/firebase/web/index.html +162 -14
  141. package/templates/firebase/lib/core/guards/user_info_guard.dart +0 -61
@@ -0,0 +1,73 @@
1
+ stripe_success.html,1781195415353,677b2fb430839866f32a33a751aae85b4c4819123be820f3f5b2f689b8eed00e
2
+ manifest.json,1779810782774,1b929ac7267ff738d05646106a0fac3591082b6f365051928a62518af30b3fd5
3
+ local_notifications.js,1772398909034,0b44b177c4b8a705ad1b0e3d7532537aad7b230e9472f93c820742b7548797b8
4
+ flutter.js,1777393608000,b42e549a02f5c1428b0ad85dbdc663d400a6d2db10cb8aefcc0fddd592fd6ceb
5
+ firebase-messaging-sw.js,1772484605561,dba3c939f7c240e62f63d06408d5f70e45f1168d00df98726e6a7a20cbd3b0df
6
+ favicon.png,1779810782545,c967afd3c3d4dd165ef2e5cf3f203c3982dfff6a95881859ed163d9c38d75512
7
+ splash/img/light-4x.png,1780546027751,abf0e55345e532a4645fe14b01c2948b07c78df50cc6da23648837dfb6271c74
8
+ splash/img/light-3x.png,1780546027699,5f02c94032cb7053807ce89603b109f14ea2740eaa3145dc76c924cbe1d534a9
9
+ splash/img/light-2x.png,1780546027647,3c073b9da154ac8be530f8beefa3579246f223acd819e856a2db000fdabcb67e
10
+ splash/img/light-1x.png,1780546027614,928ead755ab10de4d559c3c6751839dd6adf1412271ef8a508707b0938b4baf5
11
+ splash/img/dark-4x.png,1780546027952,d5353a84fd44ec4a28127ceb9d6d04305f99252d92bacfeeebeade4f2ab59bc0
12
+ splash/img/dark-3x.png,1780546027901,c37e7f4f1e37b59291a681179c97211a9d62d60a2ee802dd192879855edf6dec
13
+ splash/img/dark-2x.png,1780546027846,6ca64e1d4aa1afd8916b3b33eaa4d3e4cbe50c79902d3b813e839d1eb0d8897e
14
+ splash/img/dark-1x.png,1780546027812,8a280f0b5c87f512ebaf60bea59396d136fdcfc1710c9ea311cd10b5f6370191
15
+ icons/Icon-maskable-512.png,1779810782770,5b32f8a7281ac65928b1076db0bc02aee0d4afc2dbb6372eecbdf37a89be33cc
16
+ icons/Icon-maskable-192.png,1779810782685,eb719472f72ffb7c1a294a8f3edb6ec952ad6df0be612e3e8148f9052617d58d
17
+ icons/Icon-512.png,1779810782665,5b32f8a7281ac65928b1076db0bc02aee0d4afc2dbb6372eecbdf37a89be33cc
18
+ icons/Icon-192.png,1779810782567,eb719472f72ffb7c1a294a8f3edb6ec952ad6df0be612e3e8148f9052617d58d
19
+ canvaskit/wimp.wasm,1777394172000,f73f248dfff66551884ce69d2a3088df2410fba13e3fafa2ef8ef98829241f14
20
+ canvaskit/wimp.js.symbols,1777394172000,d503a0521fcb00d9dec064dabaccb9ead8b10d311f9f36141fb5b7997b201846
21
+ canvaskit/wimp.js,1777394172000,63f0487b1033146fc2ed7b394420b036c99cf198b857dfd3cea92227f84a2310
22
+ canvaskit/skwasm_heavy.wasm,1777394228000,c02f715939724f1c936cef6b5d787489451b1a1a24dadf86f667d71f0e21cc09
23
+ canvaskit/skwasm_heavy.js.symbols,1777394228000,625fb4cc0f491b80d66e20251521bc90dd7ad83ad06b242e9762916aaaccada6
24
+ canvaskit/skwasm_heavy.js,1777394228000,1a694c23c937503283d38d24f75d03e432093e0cc9488c38910b482751263fad
25
+ canvaskit/skwasm.wasm,1777394126000,0e0c782588b556af14cad090d959344b705131d299fa76ebfddf97180853b1ab
26
+ canvaskit/skwasm.js.symbols,1777394126000,320b80390e4e3234f1c216c90a01028f17fd39220817c7837d8126133366f313
27
+ canvaskit/skwasm.js,1777394126000,40f0e36b14ede08584d4f4cbfbcaf55642e04f81498364e975b55cec7b95ba20
28
+ canvaskit/canvaskit.wasm,1777393746000,4b911a495801f229a19e536df7e52a9943a549bf4c213583f081ea2a3d3c12b2
29
+ canvaskit/canvaskit.js.symbols,1777393746000,52d20fad5abef4f488c2b92aec7181e65997b8a22de54cc976b4bd4555401723
30
+ canvaskit/canvaskit.js,1777393746000,df5aa63d90b0491908ad19d64fd18f29db1dc416548f6c73179d962eaae694e7
31
+ canvaskit/chromium/canvaskit.wasm,1777393830000,44d29356b9e2c903751a8aa583716275e165d1fb56eea46963c95c7c43f7ca34
32
+ canvaskit/chromium/canvaskit.js.symbols,1777393830000,60722421e56edacbeb674cc3ef0f6152969067e74b51a960c80cd36e75b75a54
33
+ canvaskit/chromium/canvaskit.js,1777393830000,10ea3badcf26e29aba851699c03e09bc35fea65cdc440a035bac263d6d02d665
34
+ assets/packages/mixpanel_flutter/assets/mixpanel.js,1779806094332,1915455df317dd1a9f42c6f3ea5bbe3ee357614f89d994f157a2e821f37956c3
35
+ assets/packages/lucide_icons_flutter/assets/lucide.ttf,1779806149220,bfca5b519d438d7194550cbc60a4e7c10f53a36ac96d48c7e17503a45b6b7b0b
36
+ assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-w600.ttf,1779806149210,cf2218879264e413687602c192e43b86000404ff85e2bca933d9a67020299ed4
37
+ assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-w500.ttf,1779806149217,587ce31612aa88e6c019210a18625531557eda111f5f8e838a298277efc9e308
38
+ assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-w400.ttf,1779806149212,644f7ccdf1b74bdb898210c4171327a25d7476690c7ade2d3082dd473bc24fe1
39
+ assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-w300.ttf,1779806149214,5dd834d2d84e5a1524e027fa21d87c6e8aaaf6421f201be2ec61e57c0b7bce72
40
+ assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-w200.ttf,1779806149208,d929819511975bf5030c0f359e8c16d21ad14b20ac24d5f2ff238462114b1d9b
41
+ assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-w100.ttf,1779806149216,1e9e6ea39a38b874c40242d02533adac9dd3d09c8119dae55458e65c6c3c16e6
42
+ assets/packages/cupertino_icons/assets/CupertinoIcons.ttf,1779806141382,010d647b6cab4e1a9b4398957353f43589903ce8dc760d05d894944b664e29bd
43
+ assets/fonts/MaterialIcons-Regular.otf,1662569016000,3a631b0761788d7cf2a71e8732a5b31288d6e7aa996c671c70d53a251bbd3848
44
+ assets/assets/images/splash_logo_light_android12.png,1779867497377,da2caee4f16230230dbb04d84913f0a9df2eae6bcb8f9d1ce83ed556cee6d094
45
+ assets/assets/images/splash_logo_light.png,1779867497286,643a1f45d6ef9458c4c0eed9f4889cc2adbdf1db616913215be66673a05a0066
46
+ assets/assets/images/splash_logo_dark_android12.png,1779867497464,b237d9f9035d419525a10b32889c30d44c706d62990b30bdc6bef911fe9352d6
47
+ assets/assets/images/splash_logo_dark.png,1779867497287,18090e40687efaa4bc7c70128bebf88e6a9952562f1fcfb255331305ff7cffa7
48
+ assets/assets/images/premium-switch-header.png,1781104464333,890c0bcac58363b6db88829af7c7e6134174c9fdbe61f39b6c72e28042e54483
49
+ assets/assets/images/premium-bg.jpg,1781104464331,4c48000d91087f9828e628e6336cb9791cb2c57bdcafdf78e5edbf9d568ab262
50
+ assets/assets/images/logo_wordmark_light.png,1780606594616,f4d559a7ae85706dfc0cafeabf1c03380fa0ce5339a471c8f50fcfd4f59e6dd2
51
+ assets/assets/images/logo_wordmark_dark.png,1780606594628,9b7ec728eb6b0f7798339065b2e311bf37cb57140ea068da8496e066773276cd
52
+ assets/assets/images/icon_foreground_empty.png,1779810762471,cdf875b39e231f303c6eff2baba9972404f1ea12757836b75f41611b58a122ec
53
+ assets/assets/images/icon_android.png,1779810762467,db0f6c9ff904d4571ab40d46110096370c41e3fa16496baa0fda71861ee47800
54
+ assets/assets/images/icon.png,1779745964920,194820ecc4eed7bac14b5605916d87093b4571a28f3ca24863ac09c783343488
55
+ assets/assets/images/favicon.png,1779745964922,194820ecc4eed7bac14b5605916d87093b4571a28f3ca24863ac09c783343488
56
+ assets/assets/images/empty_notifications.jpg,1772398653078,cbb7d57f7cf305cf33ee8cbd15544eb1020670a1a8100f1328b49ca61d76fb7e
57
+ assets/assets/icons/google_play_games.png,1772398653083,533c1d87d7be8690ab173ef42aa92ee8bac3b6e6859c0f97fe30daff2c843501
58
+ assets/assets/icons/google.png,1772398653083,f423e7e7be1e06008d45617d07f095f04da7fdcab9a56523f9e0633828e464e0
59
+ assets/assets/icons/facebook.png,1772398653083,79ac67e449c1db63319d43329ca91f682b4e0bc6a0883b0dfb2a849e13ae6eb6
60
+ assets/assets/icons/apple.png,1772398653083,2a737d8801ca81452b2978bb2e1ac72136acf1a4c338f2de2f77b65e9f6de1fa
61
+ version.json,1781383075783,1c93a56a819f36903677817066dbda0bae5785563a39d3a0a486246cf67107ac
62
+ flutter_service_worker.js,1781383076286,baeeaf9f4b8e6f40d3b0549429ceb70ca01f120db6a7ef2f60d5809233dc2205
63
+ assets/FontManifest.json,1781383075907,4d84ab517c27984d36f9a3c8be6f2a72788c0c3985c1d5874297fef0a53407ca
64
+ assets/AssetManifest.bin.json,1781383075907,50a11b51c3fc4cbed1b5ec3a4f466c6a6b75c481d08d4782c8191f99bdbdba9c
65
+ assets/AssetManifest.bin,1781383075907,78bccb08a36307a400711a1d7e6868bd5f89a24e63439fb5b6747660323e4475
66
+ assets/shaders/ink_sparkle.frag,1781383075976,1c8e222328206d1e06754f76fb53947aad38d62180aafad5298a3c6f510b173d
67
+ assets/shaders/stretch_effect.frag,1781383075975,1a7d4ac2be40cf0a459dfb390ef08bcd740f37913ffdee8de3c2ea836a18410e
68
+ index.html,1781383002256,bb8142eb84e9e44049957c3edf86006e1ae5f194c397a8326ce505b68fdf58f4
69
+ flutter_bootstrap.js,1781383002244,fb98f6d7235b26ff8bcd0abce3ccbc26619afbc1956fd2bdceeaa847c25f8a4b
70
+ main.dart.js_1.part.js,1781383058280,3d9563d7203157ebfa135f83b8cd2677443bf89b850e8a5adcffe56621b4fc16
71
+ main.dart.js_3.part.js,1781383058289,133184cfaa83a56797dcb74c0b7fce45102298ea9e74f51f4a54ef6beca152c9
72
+ assets/NOTICES,1781383075907,a18efc42c5ed99b56481537d0f229ddd3add671c548a893aaf8766f30c854158
73
+ main.dart.js,1781383059126,b4cd4c8740826a7c4257dc5f49c9a402e5c6338f31eb39de8dd82762fa47be16
@@ -31,6 +31,43 @@ everything below is backend-agnostic.
31
31
  6. **Zero analyzer issues.** Run `flutter analyze` and fix everything before
32
32
  considering a change done.
33
33
 
34
+ ## Design system, components & responsiveness
35
+
36
+ This kit is an **organized, opinionated** design system — not a blank canvas.
37
+ Before building anything, browse the live **Design System** screen
38
+ (`lib/features/home/design_system_page.dart`) and the **Components** gallery:
39
+ colours, typography, spacing and dozens of ready widgets (`KasyButton`,
40
+ `KasyCard`, `KasyTextField`, `KasyScreen`, `showKasyToast`,
41
+ `showKasyConfirmDialog`, …). Reuse one before writing a new widget. Full token
42
+ reference: **`DESIGN_SYSTEM.md`**.
43
+
44
+ **Responsiveness is deliberate, not improvised.** There are exactly three
45
+ breakpoints and the type sizes are pinned per breakpoint, so the UI never
46
+ oscillates or guesses:
47
+
48
+ | Breakpoint | Width | Notes |
49
+ | ---------- | ---------- | ------------------------------------------------ |
50
+ | Mobile | `< 768` | native iOS/Android + phone-width web |
51
+ | Tablet | `768–1024` | |
52
+ | Desktop | `>= 1024` | authored baseline |
53
+
54
+ - **Typography** (`KasyTypeScale`): each role has an **explicit size per
55
+ breakpoint** — headings are largest on desktop and step down on smaller
56
+ screens; body/labels stay constant. It is applied globally by
57
+ `ResponsiveTextTheme` — **screens just use `context.textTheme.*` /
58
+ `context.kasyTextTheme.*` and the right size comes out**. Don't hardcode
59
+ `fontSize`; don't invent your own breakpoints.
60
+ - **Native mobile** is straightforward: it's the Mobile breakpoint and never
61
+ scales the viewport.
62
+ - **Web render scale** (`WebViewportScale`) is a *separate* concern: Flutter web
63
+ renders ~10% large, so **desktop only** scales the whole UI to `0.95`. Tablet
64
+ and mobile web render at natural size, like native. This is NOT the typography
65
+ scale — don't conflate the two.
66
+
67
+ To re-tune sizes, edit `KasyTypeScale` in `lib/core/theme/type_scale.dart` —
68
+ nothing else. The live ramp (tabs per breakpoint) is under **Design System →
69
+ Typography**.
70
+
34
71
  ## Architecture (feature-first, three layers)
35
72
 
36
73
  ```
@@ -78,6 +115,11 @@ Some features only work on a real iOS / Android build and are gated with
78
115
  throws at runtime.
79
116
  - **Push / FCM token, notification permission** — native APIs; on web the admin
80
117
  actions surface the "native app only" toast.
118
+ - **App update prompts** — two distinct flows, both native-only and driven by
119
+ Firebase Remote Config (works on every backend): **"update available"** (shown
120
+ *before*, when the installed version is below `app_latest_version` / blocking
121
+ below `app_min_version`, sends the user to the store) and **"what's new"**
122
+ (shown *after* updating). See `lib/core/app_update/` and the README.
81
123
 
82
124
  ## Optional design guard-rail
83
125
 
@@ -85,3 +127,44 @@ Some features only work on a real iOS / Android build and are gated with
85
127
  (hardcoded fonts / colours / Material widgets). It is **opt-in and
86
128
  non-blocking** — relax it, wire it into CI, or delete it freely. Per-line
87
129
  escape hatch: `// design-check: ignore`.
130
+
131
+ ## Updating the kit (merging new versions with your changes)
132
+
133
+ The project is a scaffold: the kit code was copied into it. Improvements ship via
134
+ the CLI, which **overwrites** files (it does not auto-merge). The safety net is git.
135
+
136
+ Recommended flow when the user wants newer kit code:
137
+
138
+ 1. **Commit first** so the working tree is clean (the CLI also warns about this).
139
+ 2. Run the relevant command:
140
+ - `kasy update` — shows what's new for the features in use
141
+ - `kasy update core` / `kasy update components` — refreshes core files / UI components
142
+ - `kasy update <feature>` — e.g. `revenuecat`, `ai_chat`, `auth` (re-applies that feature)
143
+ 3. The command overwrites the files and prints which ones changed.
144
+ 4. **Merge:** if the user had customized any overwritten file, reconcile their version
145
+ (in the previous git commit) with the new kit version. As the in-project agent,
146
+ this is your job: read `git diff` (or `git show HEAD:<file>` for the user's prior
147
+ version) and produce a result that keeps BOTH the user's customizations and the new
148
+ kit improvements. Prefer additive merges; never silently drop the user's logic.
149
+ When unsure which side wins for a hunk, ask the user.
150
+
151
+ This is why committing before `kasy update` matters: it makes the user's prior version
152
+ recoverable so the merge is a normal 3-way reconcile, not a guess.
153
+
154
+ ## Debugging when something doesn't work
155
+
156
+ Diagnose with real signals, not guesses:
157
+
158
+ - **App logs first.** Key steps (auth, service init) are logged via `Logger` — read
159
+ the `kasy run` terminal / browser console.
160
+ - **Then the backend logs.** Supabase: Dashboard → Logs (Auth / Edge Functions /
161
+ Postgres). Firebase: Console → Functions logs / Authentication.
162
+ - **Auth / token errors:** decode the JWT (base64url-decode the middle segment) and
163
+ inspect `sub` / `aud` / `iss`. A `missing sub claim` usually means the wrong
164
+ session/token was sent (e.g. the publishable `anon` key), not a bad provider token.
165
+ - **`kasy doctor`** checks common setup gaps (Google SHA-1, authorized domains,
166
+ provider config).
167
+ - **Web vs native differ:** web runs in `authRequired` mode (no anonymous user);
168
+ native is anonymous-first. Account-linking only applies when a real session exists.
169
+
170
+ Add a temporary `print(...)` to inspect a runtime value, then remove it.
@@ -65,8 +65,11 @@ doubt, add or reuse a role rather than hardcoding.
65
65
  | `cardSubtitle` | `bodyXs` (12/w400) | Card secondary text |
66
66
  | `caption` | `bodyXs` (12/w400) | Captions, helper text |
67
67
 
68
- Access roles in widgets via `context.textTheme.*` (Material slots, colour
69
- applied) or `KasyTextTheme.*` (raw role, apply colour with `.copyWith`).
68
+ Access the semantic roles via `context.kasyTextTheme.*` (colourless apply
69
+ colour with `.copyWith`). Access Material slots via `context.textTheme.*`
70
+ (colour already applied). The raw scale roles are also exposed as
71
+ `KasyTextTheme.*` statics, but those are **fixed** (the desktop baseline, for
72
+ specs/docs) — use the `context.*` accessors in live UI so text scales.
70
73
 
71
74
  Material → role mapping: `headlineMedium = heading2`, `headlineSmall =
72
75
  titleLarge = heading3`, `titleMedium = heading4`.
@@ -74,6 +77,38 @@ titleLarge = heading3`, `titleMedium = heading4`.
74
77
  The chrome/app-bar title is **w700** (not w900). Black weights read as heavy and
75
78
  fight the rest of the UI.
76
79
 
80
+ ### Responsive type — `KasyTypeScale` (`lib/core/theme/type_scale.dart`)
81
+
82
+ Not a blind multiplier: **every role declares an explicit size per breakpoint**
83
+ (the way Figma variables / modes do). The professional pattern — **headings are
84
+ largest on desktop and step down on smaller viewports** (a 36px title would feel
85
+ huge and wrap badly on a phone), while **body and labels stay constant** for
86
+ stable reading everywhere. Desktop is the authored reference.
87
+
88
+ | Role (example) | Mobile `< 768` | Tablet `768–1024` | Desktop `>= 1024` |
89
+ | -------------- | -------------- | ----------------- | ----------------- |
90
+ | `heading1` | 28 | 32 | 36 |
91
+ | `heading2` | 22 | 23 | 24 |
92
+ | `heading3` | 18 | 19 | 20 |
93
+ | `bodyBase` | 16 | 16 | 16 |
94
+ | `bodySm` | 14 | 14 | 14 |
95
+
96
+ `ResponsiveTextTheme` (innermost wrapper in `main.dart`) reads the viewport
97
+ width, picks the breakpoint and republishes a `Theme` whose Material `textTheme`
98
+ and `KasyTextTheme` extension use that breakpoint's sizes. Every
99
+ `context.textTheme.*` and `context.kasyTextTheme.*` inherits it — **no screen
100
+ does anything**; desktop is a pass-through (the authored baseline). Component
101
+ themes (buttons, inputs) keep their calibrated sizes on purpose. To re-tune the
102
+ whole app, edit the numbers in `KasyTypeScale` — nothing else. The live ramp is
103
+ visible under **Design System → Typography** (tabs per breakpoint).
104
+
105
+ **Web render scale (`WebViewportScale`, `lib/core/web_viewport_scale.dart`).** A
106
+ separate concern from type sizes: Flutter web renders ~10% larger than an
107
+ equivalent HTML app, so on **desktop only** (`>= 1024`) the whole UI is scaled to
108
+ `0.95` — a true viewport scale, so layout, scrolling and hit-testing stay
109
+ correct. Tablet and mobile web render at natural size, exactly like the native
110
+ build, which never scales.
111
+
77
112
  ---
78
113
 
79
114
  ## Icon sizes — `KasyIconSize` (`lib/core/theme/icon_sizes.dart`)
@@ -83,6 +83,8 @@ Requires an [Apple Developer](https://developer.apple.com) account (paid).
83
83
 
84
84
  Requires a [Meta for Developers](https://developers.facebook.com) account.
85
85
 
86
+ > **Shortcut:** the `kasy facebook` command automates writing the credentials (Info.plist, strings.xml, and the Firebase/Supabase provider) and opens the Meta dashboard. The steps below are what you do on Meta (manual, no API).
87
+
86
88
  ### Step 1 — Create an app on Meta
87
89
 
88
90
  1. Open [Meta for Developers → My Apps](https://developers.facebook.com/apps)
@@ -83,6 +83,8 @@ Requiere cuenta de [Apple Developer](https://developer.apple.com) (de pago).
83
83
 
84
84
  Requiere cuenta en [Meta for Developers](https://developers.facebook.com).
85
85
 
86
+ > **Atajo:** el comando `kasy facebook` automatiza escribir las credenciales (Info.plist, strings.xml y el proveedor en Firebase/Supabase) y abre el panel de Meta. Los pasos de abajo son lo que haces en Meta (manual, sin API).
87
+
86
88
  ### Paso 1 — Crear una app en Meta
87
89
 
88
90
  1. Abre [Meta for Developers → My Apps](https://developers.facebook.com/apps)
@@ -83,6 +83,8 @@ Requer conta [Apple Developer](https://developer.apple.com) (paga).
83
83
 
84
84
  Requer conta no [Meta for Developers](https://developers.facebook.com).
85
85
 
86
+ > **Atalho:** o comando `kasy facebook` automatiza a parte de gravar as credenciais (Info.plist, strings.xml e o provedor no Firebase/Supabase) e abre o painel da Meta. Os passos abaixo são o que você faz na Meta (manual, sem API).
87
+
86
88
  ### Passo 1 — Criar app no Meta
87
89
 
88
90
  1. Abra [Meta for Developers → My Apps](https://developers.facebook.com/apps)
@@ -1 +1,56 @@
1
- {"firestore":{"rules":"firestore.rules","indexes":"firestore.indexes.json"},"storage":{"rules":"storage.rules"},"functions":[{"source":"functions","codebase":"default","ignore":["node_modules",".git","firebase-debug.log","firebase-debug.*.log"],"predeploy":["npm --prefix \"$RESOURCE_DIR\" run build"]}],"flutter":{"platforms":{"android":{"default":{"projectId":"fir-kit-8e56b","appId":"1:613770689965:android:f800aacb0091708b0f7524","fileOutput":"android/app/google-services.json"}},"ios":{"default":{"projectId":"fir-kit-8e56b","appId":"1:613770689965:ios:7cc1f4579cf759fd0f7524","uploadDebugSymbols":false,"fileOutput":"ios/Runner/GoogleService-Info.plist"}},"dart":{"lib/firebase_options.dart":{"projectId":"fir-kit-8e56b","configurations":{"android":"1:613770689965:android:f800aacb0091708b0f7524","ios":"1:613770689965:ios:7cc1f4579cf759fd0f7524","web":"1:613770689965:web:e9347bac3b3eae5d0f7524"}}}}}}
1
+ {
2
+ "hosting": {
3
+ "public": "build/web",
4
+ "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
5
+ "rewrites": [
6
+ { "source": "**", "destination": "/index.html" }
7
+ ],
8
+ "headers": [
9
+ {
10
+ "source": "**/@(index.html|flutter_bootstrap.js|flutter_service_worker.js|main.dart.js|main.dart.wasm|main.dart.mjs|manifest.json|version.json)",
11
+ "headers": [
12
+ { "key": "Cache-Control", "value": "no-cache, no-store, must-revalidate" }
13
+ ]
14
+ }
15
+ ]
16
+ },
17
+ "firestore": { "rules": "firestore.rules", "indexes": "firestore.indexes.json" },
18
+ "storage": { "rules": "storage.rules" },
19
+ "functions": [
20
+ {
21
+ "source": "functions",
22
+ "codebase": "default",
23
+ "ignore": ["node_modules", ".git", "firebase-debug.log", "firebase-debug.*.log"],
24
+ "predeploy": ["npm --prefix \"$RESOURCE_DIR\" run build"]
25
+ }
26
+ ],
27
+ "flutter": {
28
+ "platforms": {
29
+ "android": {
30
+ "default": {
31
+ "projectId": "fir-kit-8e56b",
32
+ "appId": "1:613770689965:android:f800aacb0091708b0f7524",
33
+ "fileOutput": "android/app/google-services.json"
34
+ }
35
+ },
36
+ "ios": {
37
+ "default": {
38
+ "projectId": "fir-kit-8e56b",
39
+ "appId": "1:613770689965:ios:7cc1f4579cf759fd0f7524",
40
+ "uploadDebugSymbols": false,
41
+ "fileOutput": "ios/Runner/GoogleService-Info.plist"
42
+ }
43
+ },
44
+ "dart": {
45
+ "lib/firebase_options.dart": {
46
+ "projectId": "fir-kit-8e56b",
47
+ "configurations": {
48
+ "android": "1:613770689965:android:f800aacb0091708b0f7524",
49
+ "ios": "1:613770689965:ios:7cc1f4579cf759fd0f7524",
50
+ "web": "1:613770689965:web:e9347bac3b3eae5d0f7524"
51
+ }
52
+ }
53
+ }
54
+ }
55
+ }
56
+ }
@@ -5,7 +5,7 @@ import {notificationsApi} from "../notifications/notifications_api";
5
5
  import {NotificationPayload, SystemNotificationParams} from "../notifications/models/notification";
6
6
  import {NotificationTypes} from "../core/data/entities/notification_entity";
7
7
  import {Timestamp} from "firebase-admin/firestore";
8
- import {usersRepository, userDevicesRepository} from "../core/data/repositories/repositories";
8
+ import {usersRepository} from "../core/data/repositories/repositories";
9
9
  import * as functions from "firebase-functions/v1";
10
10
  import {onDocumentCreated} from "firebase-functions/v2/firestore";
11
11
  import {UserDeviceEntityData} from "../core/data/entities/user_device_entity";
@@ -52,9 +52,13 @@ export const onUserRegistration = functions
52
52
  });
53
53
 
54
54
  /**
55
- * Triggered when the user registers their first device.
56
- * Sends a localized welcome notification (saved to DB only, no push).
57
- * Fires on device creation so the device locale is available.
55
+ * Triggered when a device is registered.
56
+ * Sends a localized welcome notification ONCE per account (saved to DB only, no
57
+ * push). Fires on device creation so the device locale is available.
58
+ *
59
+ * The "once per account" guard is [UserRepository.claimWelcome], not a device
60
+ * count: a device re-registered after a logout/login cycle looks like a fresh
61
+ * first device, so counting devices re-sent the welcome on every re-login.
58
62
  */
59
63
  export const onFirstDeviceRegistered = onDocumentCreated(
60
64
  "users/{userId}/devices/{deviceId}",
@@ -62,8 +66,8 @@ export const onFirstDeviceRegistered = onDocumentCreated(
62
66
  const logger = new Logger("onFirstDeviceRegistered");
63
67
  const userId = event.params.userId;
64
68
  try {
65
- const allDevices = await userDevicesRepository.getDevices([userId]);
66
- if (allDevices.length > 1) return;
69
+ const shouldWelcome = await usersRepository.claimWelcome(userId);
70
+ if (!shouldWelcome) return;
67
71
 
68
72
  const deviceData = event.data?.data() as UserDeviceEntityData | undefined;
69
73
  // Flutter serializes as "extraData" (camelCase); Firestore schema uses "extra_data".
@@ -13,6 +13,7 @@ export interface SubscriptionEntityData {
13
13
  creation_date: Timestamp;
14
14
  last_activity: Timestamp;
15
15
  expiration_date?: Timestamp;
16
+ trial_end?: Timestamp;
16
17
  status: SubscriptionStatus;
17
18
  store: Stores,
18
19
  product_id: string;
@@ -30,6 +31,7 @@ export class SubscriptionEntity {
30
31
  creation_date,
31
32
  last_activity,
32
33
  expiration_date,
34
+ trial_end,
33
35
  status,
34
36
  store,
35
37
  product_id,
@@ -41,6 +43,7 @@ export class SubscriptionEntity {
41
43
  this.creation_date = creation_date;
42
44
  this.last_activity = last_activity;
43
45
  this.expiration_date = expiration_date;
46
+ this.trial_end = trial_end;
44
47
  this.status = status;
45
48
  this.store = store;
46
49
  this.product_id = product_id;
@@ -66,6 +69,7 @@ export class SubscriptionEntity {
66
69
  creation_date: subscription.creationDate,
67
70
  last_activity: subscription.lastUpdate,
68
71
  expiration_date: subscription.expirationDate,
72
+ trial_end: subscription.trialEnd,
69
73
  status: subscription.status,
70
74
  store: subscription.store,
71
75
  product_id: subscription.productId,
@@ -78,6 +82,7 @@ export class SubscriptionEntity {
78
82
  creationDate: this.creation_date,
79
83
  lastUpdate: this.last_activity,
80
84
  expirationDate: this.expiration_date,
85
+ trialEnd: this.trial_end,
81
86
  status: this.status,
82
87
  store: this.store,
83
88
  productId: this.product_id,
@@ -7,6 +7,12 @@ export interface UserEntityData {
7
7
  creation_date: Timestamp;
8
8
  last_update_date: Timestamp;
9
9
  locale?: string;
10
+ /**
11
+ * Set to true once the one-time welcome notification has been sent for this
12
+ * account. Guards against re-sending it when a device is re-registered after
13
+ * a logout/login cycle (which otherwise looks like a brand-new first device).
14
+ */
15
+ welcome_sent?: boolean;
10
16
  }
11
17
 
12
18
  export type NewUserEntityData = Omit<UserEntityData, "id">;
@@ -20,7 +26,8 @@ export class UserEntity {
20
26
  email,
21
27
  creation_date,
22
28
  last_update_date,
23
- locale
29
+ locale,
30
+ welcome_sent
24
31
  }: UserEntityData
25
32
  ) {
26
33
  this.id = id;
@@ -29,6 +36,7 @@ export class UserEntity {
29
36
  this.creation_date = creation_date;
30
37
  this.last_update_date = last_update_date;
31
38
  this.locale = locale;
39
+ this.welcome_sent = welcome_sent;
32
40
  }
33
41
 
34
42
  static fromData(data: UserEntityData): UserEntity {
@@ -46,6 +46,33 @@ export class UserRepository {
46
46
  });
47
47
  }
48
48
 
49
+ /**
50
+ * Atomically claims the one-time welcome for a user.
51
+ *
52
+ * Returns true for exactly ONE caller per account — the one that should send
53
+ * the welcome notification — and false for every later call (e.g. a device
54
+ * re-registered after a logout/login cycle, or a second device registering at
55
+ * the same time). The flag flip and the read happen inside a transaction, so
56
+ * two concurrent device registrations can never both send the welcome.
57
+ */
58
+ async claimWelcome(userId: string): Promise<boolean> {
59
+ const ref = this.db.collection("users").doc(userId);
60
+ return this.db.runTransaction(async (tx) => {
61
+ const snapshot = await tx.get(ref);
62
+ if (!snapshot.exists) {
63
+ return false;
64
+ }
65
+ if (snapshot.get("welcome_sent") === true) {
66
+ return false;
67
+ }
68
+ tx.update(ref, {
69
+ "welcome_sent": true,
70
+ "last_update_date": Timestamp.now(),
71
+ });
72
+ return true;
73
+ });
74
+ }
75
+
49
76
  async list({
50
77
  limit = 100,
51
78
  startAfter,
@@ -22,6 +22,7 @@ export interface SubscriptionData {
22
22
  creationDate: Timestamp;
23
23
  lastUpdate: Timestamp;
24
24
  expirationDate?: Timestamp;
25
+ trialEnd?: Timestamp;
25
26
  store: Stores;
26
27
  productId: string;
27
28
  // Denormalized copy of the subscriber's email. Firestore is a non-relational
@@ -41,6 +42,7 @@ export class Subscription {
41
42
  creationDate,
42
43
  lastUpdate,
43
44
  expirationDate,
45
+ trialEnd,
44
46
  store,
45
47
  productId,
46
48
  email,
@@ -52,6 +54,7 @@ export class Subscription {
52
54
  this.creationDate = creationDate;
53
55
  this.lastUpdate = lastUpdate;
54
56
  this.expirationDate = expirationDate;
57
+ this.trialEnd = trialEnd;
55
58
  this.store = store;
56
59
  this.productId = productId;
57
60
  this.email = email;
@@ -275,6 +275,9 @@ async function upsertFromStripeSubscription(sub: Stripe.Subscription): Promise<v
275
275
  const expiration = periodEnd
276
276
  ? Timestamp.fromMillis(periodEnd * 1000)
277
277
  : undefined;
278
+ const trialEnd = sub.trial_end
279
+ ? Timestamp.fromMillis(sub.trial_end * 1000)
280
+ : undefined;
278
281
 
279
282
  const subscription = new Subscription(
280
283
  {
@@ -283,6 +286,7 @@ async function upsertFromStripeSubscription(sub: Stripe.Subscription): Promise<v
283
286
  creationDate: existing?.creationDate ?? now,
284
287
  lastUpdate: now,
285
288
  expirationDate: expiration,
289
+ trialEnd,
286
290
  store: Stores.STRIPE,
287
291
  productId: priceId,
288
292
  email: user.email,
@@ -53,7 +53,6 @@ class KasyAlert extends StatelessWidget {
53
53
  final bool hasMessage = message != null && message!.trim().isNotEmpty;
54
54
  final TextStyle titleStyle =
55
55
  context.textTheme.titleMedium?.copyWith(
56
- fontWeight: FontWeight.w600,
57
56
  color: emphasizeTitleWithTone
58
57
  ? palette.accent
59
58
  : context.colors.onSurface,
@@ -186,7 +186,10 @@ class KasyTopScrollFade extends StatelessWidget {
186
186
  // On web topInset = 0; enforce a 6 px minimum so the strip still exists.
187
187
  final double solidHeight = topInset < 6 ? 6.0 : topInset;
188
188
  final double totalHeight = solidHeight + _contentFade;
189
- final Color base = context.colors.surface;
189
+ // The page background (NOT the bar's `surface`): when the bar hides on
190
+ // scroll, content must melt into the page color, not into a floating sliver
191
+ // of the bar's tint at the top.
192
+ final Color base = context.colors.background;
190
193
 
191
194
  // Same gradient the grid cards use for their caption scrim — strong at the
192
195
  // top, melting gradually to fully transparent at the bottom.
@@ -238,6 +241,12 @@ class KasyAppBar extends StatelessWidget {
238
241
  /// or wrap a custom 44×44 chrome control with [KasyAppBarChromeTap] for the
239
242
  /// same tap target semantics as the built-in orbs.
240
243
  final Widget? trailing;
244
+
245
+ /// Replaces the default leading control (back button / spacer) — e.g. a menu
246
+ /// orb that opens a [KasySidebar] drawer. Use [KasyChromeOrbIconButton] so it
247
+ /// matches the trailing orbs' size and tap target.
248
+ final Widget? leading;
249
+
241
250
  final bool useSafeArea;
242
251
 
243
252
  /// When set, called instead of [ThemeProvider.toggle] for the theme orb
@@ -266,6 +275,7 @@ class KasyAppBar extends StatelessWidget {
266
275
  this.style = KasyAppBarStyle.subpage,
267
276
  this.onBack,
268
277
  this.trailing,
278
+ this.leading,
269
279
  this.useSafeArea = true,
270
280
  this.onThemeToggle,
271
281
  this.toolbarHeight,
@@ -285,20 +295,21 @@ class KasyAppBar extends StatelessWidget {
285
295
 
286
296
  final double rowHeight =
287
297
  toolbarHeight ?? kasyAppBarToolbarRowHeightOf(context);
288
- final Widget leading = switch (style) {
289
- KasyAppBarStyle.rootTab => SizedBox(
290
- width: 44,
291
- height: rowHeight,
292
- ),
293
- _ => KasyChromeOrbIconButton(
294
- icon: KasyIcons.arrowBackIos,
295
- iconSize: KasyIconSize.md,
296
- foregroundColor: orbFg,
297
- fillColor: orbFill,
298
- onPressed: handleBack,
299
- tooltip: MaterialLocalizations.of(context).backButtonTooltip,
300
- ),
301
- };
298
+ final Widget leadingWidget = leading ??
299
+ switch (style) {
300
+ KasyAppBarStyle.rootTab => SizedBox(
301
+ width: 44,
302
+ height: rowHeight,
303
+ ),
304
+ _ => KasyChromeOrbIconButton(
305
+ icon: KasyIcons.arrowBackIos,
306
+ iconSize: KasyIconSize.md,
307
+ foregroundColor: orbFg,
308
+ fillColor: orbFill,
309
+ onPressed: handleBack,
310
+ tooltip: MaterialLocalizations.of(context).backButtonTooltip,
311
+ ),
312
+ };
302
313
 
303
314
  final TextStyle? titleRef =
304
315
  context.textTheme.headlineSmall ?? context.textTheme.titleLarge;
@@ -326,7 +337,7 @@ class KasyAppBar extends StatelessWidget {
326
337
  children: [
327
338
  Row(
328
339
  children: [
329
- leading,
340
+ leadingWidget,
330
341
  const Spacer(),
331
342
  _buildTrailing(context, orbFg, orbFill),
332
343
  ],
@@ -486,6 +497,10 @@ class KasyOverlayScaffold extends StatelessWidget {
486
497
  Widget scrollView = CustomScrollView(
487
498
  controller: scrollController,
488
499
  physics: effectivePhysics,
500
+ // Dragging the form means the user is done with the focused field and
501
+ // wants to see what's below, so dismiss the keyboard. Chat views opt out
502
+ // of this wrapper on purpose (there scrolling means reading, not typing).
503
+ keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
489
504
  slivers: kasyOverlayPaddedSlivers(
490
505
  context,
491
506
  contentPadding: contentPadding,
@@ -105,7 +105,6 @@ class KasyBottomSheet extends StatelessWidget {
105
105
  title!,
106
106
  textAlign: centered ? TextAlign.center : TextAlign.start,
107
107
  style: context.textTheme.titleLarge?.copyWith(
108
- fontWeight: FontWeight.w600,
109
108
  color: context.colors.onSurface,
110
109
  ),
111
110
  ),
@@ -1457,7 +1457,6 @@ class _CalendarPanelState extends State<_CalendarPanel> {
1457
1457
  context.textTheme.labelLarge?.copyWith(
1458
1458
  color: context.colors.onSurface,
1459
1459
  fontWeight: FontWeight.w700,
1460
- fontSize: 14,
1461
1460
  );
1462
1461
 
1463
1462
  return Column(
@@ -1623,7 +1622,6 @@ class _MonthGrid extends StatelessWidget {
1623
1622
  style: context.textTheme.labelLarge?.copyWith(
1624
1623
  color: context.colors.onSurface,
1625
1624
  fontWeight: FontWeight.w700,
1626
- fontSize: 14,
1627
1625
  ),
1628
1626
  ),
1629
1627
  ),
@@ -1688,7 +1686,6 @@ class _CalendarNavRow extends StatelessWidget {
1688
1686
  style: context.textTheme.labelLarge?.copyWith(
1689
1687
  color: c.onSurface,
1690
1688
  fontWeight: FontWeight.w700,
1691
- fontSize: 14,
1692
1689
  ),
1693
1690
  ),
1694
1691
  const SizedBox(width: 4),
@@ -1903,7 +1900,6 @@ class _YearPickerContentState extends State<_YearPickerContent> {
1903
1900
  ? const Color(0xFFFCFCFC)
1904
1901
  : c.onSurface,
1905
1902
  fontWeight: FontWeight.w600,
1906
- fontSize: 14,
1907
1903
  ),
1908
1904
  ),
1909
1905
  ),
@@ -2125,7 +2121,6 @@ class _DayCell extends StatelessWidget {
2125
2121
  style: context.textTheme.labelLarge?.copyWith(
2126
2122
  color: textColor,
2127
2123
  fontWeight: FontWeight.w600,
2128
- fontSize: 14,
2129
2124
  height: 1.43,
2130
2125
  ),
2131
2126
  ),