kasy-cli 1.34.0 → 1.35.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.
- package/README.md +1 -1
- package/bin/kasy.js +24 -2
- package/docs/cli-reference.md +7 -7
- package/lib/commands/new.js +11 -9
- package/lib/commands/release-version.js +234 -0
- package/lib/commands/update.js +27 -0
- package/lib/scaffold/CHANGELOG.json +9 -0
- package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api.dart +24 -0
- package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api_interface.dart +15 -0
- package/lib/scaffold/backends/api/patch/lib/features/authentication/repositories/authentication_repository.dart +27 -0
- package/lib/scaffold/backends/api/patch/lib/features/subscriptions/api/entities/subscription_entity.dart +1 -0
- package/lib/scaffold/backends/firebase/setup-from-scratch.js +35 -21
- package/lib/scaffold/backends/patch-base-hashes.json +66 -0
- package/lib/scaffold/backends/supabase/edge-functions/stripe-webhook/index.ts +2 -0
- package/lib/scaffold/backends/supabase/migrations/20240101000007_welcome_notification.sql +36 -23
- package/lib/scaffold/backends/supabase/migrations/20240101000014_subscription_trial_end.sql +6 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/authentication/api/authentication_api.dart +82 -3
- package/lib/scaffold/backends/supabase/patch/lib/features/authentication/repositories/authentication_repository.dart +36 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/subscriptions/api/entities/subscription_entity.dart +1 -0
- package/lib/scaffold/generate.js +53 -4
- package/lib/utils/i18n/messages-en.js +23 -0
- package/lib/utils/i18n/messages-es.js +23 -0
- package/lib/utils/i18n/messages-pt.js +23 -0
- package/package.json +5 -2
- package/templates/firebase/.firebase/hosting.YnVpbGQvd2Vi.cache +73 -0
- package/templates/firebase/AGENTS.md +83 -0
- package/templates/firebase/DESIGN_SYSTEM.md +37 -2
- package/templates/firebase/docs/auth-setup.en.md +2 -0
- package/templates/firebase/docs/auth-setup.es.md +2 -0
- package/templates/firebase/docs/auth-setup.pt.md +2 -0
- package/templates/firebase/firebase.json +56 -1
- package/templates/firebase/functions/src/authentication/triggers.ts +10 -6
- package/templates/firebase/functions/src/core/data/entities/subscription_entity.ts +5 -0
- package/templates/firebase/functions/src/core/data/entities/user_entity.ts +9 -1
- package/templates/firebase/functions/src/core/data/repositories/user_repository.ts +27 -0
- package/templates/firebase/functions/src/subscriptions/models/subscriptions.ts +3 -0
- package/templates/firebase/functions/src/subscriptions/stripe_functions.ts +4 -0
- package/templates/firebase/lib/components/kasy_alert.dart +0 -1
- package/templates/firebase/lib/components/kasy_app_bar.dart +31 -16
- package/templates/firebase/lib/components/kasy_bottom_sheet.dart +0 -1
- package/templates/firebase/lib/components/kasy_date_picker.dart +0 -5
- package/templates/firebase/lib/components/kasy_dialog.dart +0 -1
- package/templates/firebase/lib/components/kasy_otp_verification_bottom_sheet.dart +1 -1
- package/templates/firebase/lib/components/kasy_sidebar.dart +189 -120
- package/templates/firebase/lib/components/kasy_text_area.dart +0 -1
- package/templates/firebase/lib/components/kasy_text_field.dart +0 -1
- package/templates/firebase/lib/components/kasy_text_field_otp.dart +0 -1
- package/templates/firebase/lib/components/kasy_toast.dart +107 -41
- package/templates/firebase/lib/core/app_update/app_update_repository.dart +54 -0
- package/templates/firebase/lib/core/app_update/app_update_status.dart +14 -0
- package/templates/firebase/lib/core/app_update/update_available_sheet.dart +70 -0
- package/templates/firebase/lib/core/bottom_menu/active_tab_notifier.dart +40 -3
- package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +82 -34
- package/templates/firebase/lib/core/bottom_menu/web_content_wrapper.dart +6 -6
- package/templates/firebase/lib/core/bottom_menu/web_url.dart +20 -0
- package/templates/firebase/lib/core/data/api/remote_config_api.dart +38 -1
- package/templates/firebase/lib/core/guards/guard.dart +16 -2
- package/templates/firebase/lib/core/icons/kasy_icons.dart +3 -0
- package/templates/firebase/lib/core/rating/widgets/rate_banner.dart +2 -5
- package/templates/firebase/lib/core/rating/widgets/review_popup.dart +5 -3
- package/templates/firebase/lib/core/shared_preferences/shared_preferences.dart +14 -0
- package/templates/firebase/lib/core/states/components/maybe_show_update_available.dart +32 -0
- package/templates/firebase/lib/core/states/logout_action.dart +5 -1
- package/templates/firebase/lib/core/states/user_state_notifier.dart +85 -14
- package/templates/firebase/lib/core/theme/responsive_text_theme.dart +69 -0
- package/templates/firebase/lib/core/theme/texts.dart +90 -57
- package/templates/firebase/lib/core/theme/type_scale.dart +77 -0
- package/templates/firebase/lib/core/theme/web_background_sync_web.dart +20 -6
- package/templates/firebase/lib/core/utils/image_bytes_loader.dart +12 -0
- package/templates/firebase/lib/core/utils/image_bytes_loader_io.dart +28 -0
- package/templates/firebase/lib/core/utils/image_bytes_loader_web.dart +56 -0
- package/templates/firebase/lib/core/web_screen_width.dart +15 -0
- package/templates/firebase/lib/core/web_screen_width_io.dart +3 -0
- package/templates/firebase/lib/core/web_screen_width_web.dart +10 -0
- package/templates/firebase/lib/core/web_viewport_scale.dart +61 -25
- package/templates/firebase/lib/core/widgets/focus_visibility.dart +89 -0
- package/templates/firebase/lib/features/ai_chat/ai_chat_page.dart +1 -2
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_chat_conversation_view.dart +1 -2
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_list.dart +2 -3
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_tile.dart +1 -2
- package/templates/firebase/lib/features/authentication/api/auth_web_support.dart +12 -0
- package/templates/firebase/lib/features/authentication/api/auth_web_support_web.dart +25 -0
- package/templates/firebase/lib/features/authentication/api/authentication_api.dart +205 -0
- package/templates/firebase/lib/features/authentication/api/authentication_api_interface.dart +22 -0
- package/templates/firebase/lib/features/authentication/navigation/post_login_navigation.dart +32 -0
- package/templates/firebase/lib/features/authentication/providers/phone_auth_notifier.dart +7 -7
- package/templates/firebase/lib/features/authentication/providers/signin_state_provider.dart +34 -10
- package/templates/firebase/lib/features/authentication/providers/signup_state_provider.dart +2 -2
- package/templates/firebase/lib/features/authentication/repositories/authentication_repository.dart +37 -0
- package/templates/firebase/lib/features/authentication/repositories/exceptions/authentication_exceptions.dart +8 -0
- package/templates/firebase/lib/features/authentication/ui/components/otp_verification.dart +2 -2
- package/templates/firebase/lib/features/authentication/ui/components/phone_input.dart +2 -2
- package/templates/firebase/lib/features/authentication/ui/signin_page.dart +59 -0
- package/templates/firebase/lib/features/authentication/ui/widgets/auth_card_scaffold.dart +2 -2
- package/templates/firebase/lib/features/authentication/ui/widgets/recover_password_result.dart +2 -2
- package/templates/firebase/lib/features/feedbacks/ui/widgets/add_feature_button.dart +0 -1
- package/templates/firebase/lib/features/home/design_system_page.dart +134 -67
- package/templates/firebase/lib/features/home/home_components_page.dart +4 -3
- package/templates/firebase/lib/features/home/home_components_preview_page.dart +1 -3
- package/templates/firebase/lib/features/home/home_components_preview_registry.dart +154 -56
- package/templates/firebase/lib/features/home/home_page.dart +4 -0
- package/templates/firebase/lib/features/local_reminders/ui/reminder_page.dart +8 -3
- package/templates/firebase/lib/features/notifications/providers/notifications_provider.dart +10 -0
- package/templates/firebase/lib/features/notifications/ui/notifications_page.dart +3 -1
- package/templates/firebase/lib/features/notifications/ui/widgets/notification_tile.dart +8 -3
- package/templates/firebase/lib/features/onboarding/providers/onboarding_model.dart +11 -0
- package/templates/firebase/lib/features/onboarding/providers/onboarding_provider.dart +30 -3
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_loader.dart +13 -4
- package/templates/firebase/lib/features/settings/settings_page.dart +152 -11
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_page.dart +43 -15
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_tab.dart +12 -12
- package/templates/firebase/lib/features/settings/ui/components/admin/send_push_notification_page.dart +1 -4
- package/templates/firebase/lib/features/settings/ui/components/create_password_sheet.dart +141 -0
- package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +0 -1
- package/templates/firebase/lib/features/settings/ui/widgets/admin_card.dart +42 -38
- package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +17 -2
- package/templates/firebase/lib/features/subscriptions/api/entities/subscription_entity.dart +3 -0
- package/templates/firebase/lib/features/subscriptions/api/stripe_product.dart +12 -11
- package/templates/firebase/lib/features/subscriptions/repositories/subscription_repository.dart +25 -2
- package/templates/firebase/lib/features/subscriptions/ui/component/active_premium_content.dart +319 -143
- package/templates/firebase/lib/features/subscriptions/ui/component/paywall_minimal.dart +1 -5
- package/templates/firebase/lib/features/subscriptions/ui/component/paywall_row.dart +1 -5
- package/templates/firebase/lib/features/subscriptions/ui/component/paywall_with_switch.dart +2 -5
- package/templates/firebase/lib/features/subscriptions/ui/component/premium_content.dart +1 -4
- package/templates/firebase/lib/features/subscriptions/ui/widgets/feature_line.dart +1 -2
- package/templates/firebase/lib/features/subscriptions/ui/widgets/premium_banner.dart +2 -7
- package/templates/firebase/lib/features/subscriptions/ui/widgets/premium_feature.dart +2 -4
- package/templates/firebase/lib/features/subscriptions/ui/widgets/selectable_col.dart +1 -4
- package/templates/firebase/lib/features/subscriptions/ui/widgets/selectable_row.dart +0 -3
- package/templates/firebase/lib/i18n/en.i18n.json +49 -3
- package/templates/firebase/lib/i18n/es.i18n.json +49 -3
- package/templates/firebase/lib/i18n/pt.i18n.json +49 -3
- package/templates/firebase/lib/main.dart +11 -2
- package/templates/firebase/lib/router.dart +92 -13
- package/templates/firebase/pubspec.yaml +1 -1
- package/templates/firebase/test/core/data/api/fake_remote_config_api.dart +14 -0
- package/templates/firebase/test/core/states/user_state_notifier_test.dart +47 -3
- package/templates/firebase/test/core/web_viewport_scale_test.dart +68 -0
- package/templates/firebase/test/features/authentication/data/api/auth_api_fake.dart +15 -0
- package/templates/firebase/web/index.html +162 -14
- 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,1781382132245,b028f2bf7b54dc78541295a1ddf03306ac20a052662bde5b6b1aefe80d613249
|
|
62
|
+
index.html,1781382083192,bb8142eb84e9e44049957c3edf86006e1ae5f194c397a8326ce505b68fdf58f4
|
|
63
|
+
flutter_service_worker.js,1781382132717,baeeaf9f4b8e6f40d3b0549429ceb70ca01f120db6a7ef2f60d5809233dc2205
|
|
64
|
+
assets/FontManifest.json,1781382132345,4d84ab517c27984d36f9a3c8be6f2a72788c0c3985c1d5874297fef0a53407ca
|
|
65
|
+
assets/AssetManifest.bin.json,1781382132345,50a11b51c3fc4cbed1b5ec3a4f466c6a6b75c481d08d4782c8191f99bdbdba9c
|
|
66
|
+
assets/AssetManifest.bin,1781382132345,78bccb08a36307a400711a1d7e6868bd5f89a24e63439fb5b6747660323e4475
|
|
67
|
+
assets/shaders/stretch_effect.frag,1781382132431,1a7d4ac2be40cf0a459dfb390ef08bcd740f37913ffdee8de3c2ea836a18410e
|
|
68
|
+
assets/shaders/ink_sparkle.frag,1781382132431,1c8e222328206d1e06754f76fb53947aad38d62180aafad5298a3c6f510b173d
|
|
69
|
+
flutter_bootstrap.js,1781382083178,9a838d82fafadf6910ccbc6cdf4df03972abfe7749620e382c8ced856120bd8d
|
|
70
|
+
main.dart.js_3.part.js,1781382118356,80b75e3c9ff59756ea8a58318c7886e7a74787b7ed07fad6f7f4b4347d5bdbab
|
|
71
|
+
main.dart.js_1.part.js,1781382118351,a6dfa901d3a791b48e70499f583405215d20a4fe2491acd8d670fe0eac71cdaf
|
|
72
|
+
assets/NOTICES,1781382132345,a18efc42c5ed99b56481537d0f229ddd3add671c548a893aaf8766f30c854158
|
|
73
|
+
main.dart.js,1781382118819,5e7b0cab78a6e7cd653637824e33318f75554c9b5cce4b1ab26299da255247e1
|
|
@@ -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
|
|
69
|
-
|
|
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
|
-
{
|
|
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
|
|
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
|
|
56
|
-
* Sends a localized welcome notification (saved to DB only, no
|
|
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
|
|
66
|
-
if (
|
|
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
|
-
|
|
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
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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
|
-
|
|
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
|
),
|