kasy-cli 1.21.8 → 1.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commands/add.js +93 -80
- package/lib/commands/configure.js +100 -32
- package/lib/commands/doctor.js +28 -2
- package/lib/commands/new.js +86 -38
- package/lib/commands/notifications.js +1 -1
- package/lib/commands/remove.js +43 -15
- package/lib/commands/run.js +2 -2
- package/lib/commands/update.js +2 -2
- package/lib/scaffold/CHANGELOG.json +14 -0
- package/lib/scaffold/backends/api/generator.js +14 -14
- package/lib/scaffold/backends/api/patch/README.md +83 -0
- package/lib/scaffold/backends/api/patch/lib/core/data/api/storage_api.dart +1 -1
- package/lib/scaffold/backends/api/patch/lib/core/data/entities/user_entity.dart +5 -0
- package/lib/scaffold/backends/api/patch/lib/{environnements.dart → environments.dart} +3 -11
- package/lib/scaffold/backends/api/patch/lib/features/ai_chat/api/ai_chat_api.dart +108 -0
- package/lib/scaffold/backends/api/patch/lib/features/ai_chat/api/ai_chat_conversation_entity.dart +51 -0
- package/lib/scaffold/backends/api/patch/lib/features/{llm_chat/api/llm_chat_message_entity.dart → ai_chat/api/ai_chat_message_entity.dart} +5 -5
- package/lib/scaffold/backends/api/patch/lib/features/{llm_chat/providers/llm_chat_notifier.dart → ai_chat/providers/ai_chat_notifier.dart} +71 -38
- package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api.dart +2 -2
- package/lib/scaffold/backends/api/patch/lib/features/feedbacks/api/feature_request_api.dart +54 -0
- package/lib/scaffold/backends/api/patch/lib/features/onboarding/models/user_info.dart +16 -16
- package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/components/onboarding_att_setup.dart +4 -3
- package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +38 -28
- package/lib/scaffold/backends/api/patch/lib/features/settings/ui/components/admin/admin_users_api.dart +100 -0
- package/lib/scaffold/backends/api/patch/lib/features/{subscription → subscriptions}/api/entities/subscription_entity.dart +13 -0
- package/lib/scaffold/backends/api/patch/lib/features/subscriptions/api/stripe_backend_api.dart +60 -0
- package/lib/scaffold/backends/api/patch/lib/features/{subscription → subscriptions}/api/subscription_api.dart +1 -1
- package/lib/scaffold/backends/api/pubspec.yaml.tpl +4 -5
- package/lib/scaffold/backends/firebase/deploy.js +87 -13
- package/lib/scaffold/backends/firebase/enable-auth-via-cli.js +14 -6
- package/lib/scaffold/backends/firebase/generator.js +5 -5
- package/lib/scaffold/backends/firebase/setup-from-scratch.js +69 -45
- package/lib/scaffold/backends/firebase/tokens.js +4 -4
- package/lib/scaffold/backends/supabase/deploy.js +63 -11
- package/lib/scaffold/backends/supabase/edge-functions/admin-list-users/index.ts +149 -0
- package/lib/scaffold/backends/supabase/edge-functions/{llm-chat → ai-chat}/index.ts +19 -19
- package/lib/scaffold/backends/supabase/edge-functions/revenuecat-webhook/index.ts +2 -0
- package/lib/scaffold/backends/supabase/edge-functions/stripe-create-checkout-session/index.ts +123 -0
- package/lib/scaffold/backends/supabase/edge-functions/stripe-create-portal-session/index.ts +97 -0
- package/lib/scaffold/backends/supabase/edge-functions/stripe-list-prices/index.ts +83 -0
- package/lib/scaffold/backends/supabase/edge-functions/stripe-webhook/index.ts +138 -0
- package/lib/scaffold/backends/supabase/generator.js +17 -17
- package/lib/scaffold/backends/supabase/migrations/20240101000009_ai_messages.sql +50 -0
- package/lib/scaffold/backends/supabase/migrations/20240101000012_stripe_customers.sql +36 -0
- package/lib/scaffold/backends/supabase/migrations/20240101000013_admin_role.sql +62 -0
- package/lib/scaffold/backends/supabase/patch/lib/core/data/entities/user_entity.dart +4 -0
- package/lib/scaffold/backends/supabase/patch/lib/{environnements.dart → environments.dart} +3 -13
- package/lib/scaffold/backends/supabase/patch/lib/features/ai_chat/api/ai_chat_api.dart +95 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/ai_chat/api/ai_chat_conversation_entity.dart +52 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/{llm_chat/api/llm_chat_message_entity.dart → ai_chat/api/ai_chat_message_entity.dart} +6 -6
- package/lib/scaffold/backends/supabase/patch/lib/features/{llm_chat/providers/llm_chat_notifier.dart → ai_chat/providers/ai_chat_notifier.dart} +63 -35
- package/lib/scaffold/backends/supabase/patch/lib/features/authentication/api/authentication_api.dart +1 -1
- package/lib/scaffold/backends/supabase/patch/lib/features/feedbacks/api/feature_request_api.dart +46 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/models/user_info.dart +16 -16
- package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/components/onboarding_att_setup.dart +4 -3
- package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +38 -28
- package/lib/scaffold/backends/supabase/patch/lib/features/settings/ui/components/admin/admin_users_api.dart +93 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/{subscription → subscriptions}/api/entities/subscription_entity.dart +13 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/subscriptions/api/stripe_backend_api.dart +52 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/{subscription → subscriptions}/api/subscription_api.dart +1 -1
- package/lib/scaffold/backends/supabase/pubspec.yaml.tpl +4 -5
- package/lib/scaffold/backends/supabase/tokens.js +3 -3
- package/lib/scaffold/catalog.js +9 -11
- package/lib/scaffold/generate.js +45 -31
- package/lib/scaffold/shared/generator-utils.js +188 -81
- package/lib/scaffold/shared/sort-imports.js +191 -0
- package/lib/scaffold/shared/template-strings.js +3 -3
- package/lib/utils/checks.js +2 -2
- package/lib/utils/i18n/messages-en.js +50 -35
- package/lib/utils/i18n/messages-es.js +50 -35
- package/lib/utils/i18n/messages-pt.js +52 -37
- package/lib/utils/updates.js +15 -15
- package/package.json +1 -1
- package/templates/firebase/.env.example +2 -2
- package/templates/firebase/android/app/src/main/res/drawable/background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night/background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-v21/background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-v21/background.png +0 -0
- package/templates/firebase/android/app/src/main/res/values-night-v31/styles.xml +1 -1
- package/templates/firebase/android/app/src/main/res/values-v31/styles.xml +1 -1
- package/templates/firebase/assets/images/logo_wordmark_dark.png +0 -0
- package/templates/firebase/assets/images/logo_wordmark_light.png +0 -0
- package/templates/firebase/docs/revenuecat-setup.es.md +1 -1
- package/templates/firebase/docs/revenuecat-setup.pt.md +1 -1
- package/templates/firebase/firestore.rules +24 -5
- package/templates/firebase/functions/package-lock.json +22 -1
- package/templates/firebase/functions/package.json +2 -1
- package/templates/firebase/functions/src/admin/functions.ts +113 -0
- package/templates/firebase/functions/src/{llm_chat → ai_chat}/index.ts +16 -16
- package/templates/firebase/functions/src/index.ts +8 -2
- package/templates/firebase/functions/src/notifications/device_triggers.ts +2 -2
- package/templates/firebase/functions/src/notifications/triggers.ts +3 -3
- package/templates/firebase/functions/src/subscriptions/models/subscription_status.ts +2 -0
- package/templates/firebase/functions/src/subscriptions/stripe_functions.ts +222 -0
- package/templates/firebase/functions/src/subscriptions/subscriptions_functions.ts +2 -2
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png +0 -0
- package/templates/firebase/lib/components/components.dart +4 -1
- package/templates/firebase/lib/components/kasy_app_bar.dart +22 -7
- package/templates/firebase/lib/components/kasy_avatar.dart +7 -6
- package/templates/firebase/lib/components/kasy_button.dart +23 -99
- package/templates/firebase/lib/components/kasy_dialog.dart +11 -11
- package/templates/firebase/lib/components/kasy_image_viewer.dart +84 -0
- package/templates/firebase/lib/components/{kasy_sidebar_pro.dart → kasy_sidebar.dart} +692 -425
- package/templates/firebase/lib/components/kasy_status_tag.dart +91 -0
- package/templates/firebase/lib/components/kasy_text_area.dart +1 -3
- package/templates/firebase/lib/components/kasy_text_field.dart +5 -6
- package/templates/firebase/lib/components/kasy_text_field_otp.dart +1 -3
- package/templates/firebase/lib/components/kasy_toast.dart +2 -2
- package/templates/firebase/lib/components/kasy_web_header.dart +209 -0
- package/templates/firebase/lib/core/ads/ads_provider.dart +1 -1
- package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +57 -4
- package/templates/firebase/lib/core/bottom_menu/bottom_router.dart +19 -91
- package/templates/firebase/lib/core/bottom_menu/kasy_bottom_bar_factory.dart +196 -96
- package/templates/firebase/lib/core/bottom_menu/web_content_wrapper.dart +41 -0
- package/templates/firebase/lib/core/config/app_env.dart +5 -11
- package/templates/firebase/lib/core/config/features.dart +5 -4
- package/templates/firebase/lib/core/data/api/analytics_api.dart +1 -1
- package/templates/firebase/lib/core/data/api/http_client.dart +1 -1
- package/templates/firebase/lib/core/data/entities/user_entity.dart +3 -0
- package/templates/firebase/lib/core/data/models/entitlement.dart +35 -0
- package/templates/firebase/lib/core/data/models/subscription.dart +13 -186
- package/templates/firebase/lib/core/data/models/user.dart +11 -0
- package/templates/firebase/lib/core/data/repositories/user_repository.dart +1 -1
- package/templates/firebase/lib/core/home_widgets/home_widget_background_task.dart +1 -1
- package/templates/firebase/lib/core/home_widgets/home_widget_mywidget_service.dart +1 -1
- package/templates/firebase/lib/core/icons/kasy_icons.dart +31 -1
- package/templates/firebase/lib/core/rating/api/rating_api.dart +2 -2
- package/templates/firebase/lib/core/states/logout_action.dart +25 -0
- package/templates/firebase/lib/core/states/user_state_notifier.dart +3 -3
- package/templates/firebase/lib/core/theme/colors.dart +488 -188
- package/templates/firebase/lib/core/theme/radius.dart +22 -11
- package/templates/firebase/lib/core/theme/shadows.dart +66 -0
- package/templates/firebase/lib/core/theme/texts.dart +75 -41
- package/templates/firebase/lib/core/theme/universal_theme.dart +9 -4
- package/templates/firebase/lib/core/web_device_preview/web_device_preview.dart +5 -4
- package/templates/firebase/lib/core/web_viewport_scale.dart +52 -0
- package/templates/firebase/lib/core/widgets/kasy_brand_badge.dart +118 -0
- package/templates/firebase/lib/core/widgets/kasy_brand_logo.dart +40 -0
- package/templates/firebase/lib/core/widgets/kasy_focus_ring.dart +125 -0
- package/templates/firebase/lib/core/widgets/kasy_hover.dart +33 -13
- package/templates/firebase/lib/core/widgets/kasy_pressable_depth.dart +18 -13
- package/templates/firebase/lib/{environnements.dart → environments.dart} +3 -14
- package/templates/firebase/lib/features/ai_chat/ai_chat_page.dart +159 -0
- package/templates/firebase/lib/features/ai_chat/api/ai_chat_api.dart +107 -0
- package/templates/firebase/lib/features/ai_chat/api/ai_chat_conversation_entity.dart +64 -0
- package/templates/firebase/lib/features/{llm_chat/api/llm_chat_message_entity.dart → ai_chat/api/ai_chat_message_entity.dart} +6 -6
- package/templates/firebase/lib/features/{llm_chat/providers/llm_chat_notifier.dart → ai_chat/providers/ai_chat_notifier.dart} +73 -38
- package/templates/firebase/lib/features/ai_chat/providers/ai_conversations_notifier.dart +103 -0
- package/templates/firebase/lib/features/{llm_chat/ui/widgets/llm_chat_avatars.dart → ai_chat/ui/widgets/ai_chat_avatars.dart} +13 -13
- package/templates/firebase/lib/features/{llm_chat/ui/widgets/llm_chat_composer.dart → ai_chat/ui/widgets/ai_chat_composer.dart} +10 -10
- package/templates/firebase/lib/features/{llm_chat/llm_chat_page.dart → ai_chat/ui/widgets/ai_chat_conversation_view.dart} +80 -67
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_list.dart +285 -0
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_tile.dart +163 -0
- package/templates/firebase/lib/features/authentication/api/authentication_api.dart +52 -13
- package/templates/firebase/lib/features/authentication/api/popup_dismiss_watcher.dart +12 -0
- package/templates/firebase/lib/features/authentication/api/popup_dismiss_watcher_web.dart +35 -0
- package/templates/firebase/lib/features/authentication/ui/recover_password_page.dart +108 -68
- package/templates/firebase/lib/features/authentication/ui/signin_page.dart +38 -51
- package/templates/firebase/lib/features/authentication/ui/signup_page.dart +38 -51
- package/templates/firebase/lib/features/authentication/ui/widgets/auth_card_scaffold.dart +118 -0
- package/templates/firebase/lib/features/authentication/ui/widgets/recover_password_result.dart +61 -44
- package/templates/firebase/lib/features/feedbacks/api/feature_request_api.dart +32 -0
- package/templates/firebase/lib/features/feedbacks/models/feedback_state.dart +5 -5
- package/templates/firebase/lib/features/feedbacks/providers/feedback_page_notifier.dart +2 -2
- package/templates/firebase/lib/features/home/design_system_page.dart +808 -170
- package/templates/firebase/lib/features/home/home_components_page.dart +6 -3
- package/templates/firebase/lib/features/home/home_components_preview_page.dart +6 -6
- package/templates/firebase/lib/features/home/home_components_preview_registry.dart +325 -186
- package/templates/firebase/lib/features/home/home_feed.dart +289 -0
- package/templates/firebase/lib/features/home/home_image_grid.dart +355 -0
- package/templates/firebase/lib/features/home/home_page.dart +11 -250
- package/templates/firebase/lib/features/{local_reminder → local_reminders}/providers/reminder_notifier.dart +1 -1
- package/templates/firebase/lib/features/{local_reminder → local_reminders}/ui/reminder_page.dart +2 -2
- package/templates/firebase/lib/features/notifications/shared/att_permission.dart +1 -1
- package/templates/firebase/lib/features/notifications/ui/request_notification_permission.dart +25 -61
- package/templates/firebase/lib/features/notifications/ui/widgets/permission_request_view.dart +117 -0
- package/templates/firebase/lib/features/onboarding/models/user_info.dart +16 -16
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_att_setup.dart +4 -3
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_features.dart +7 -9
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_loader.dart +71 -48
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +3 -2
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_questions.dart +5 -5
- package/templates/firebase/lib/features/onboarding/ui/onboarding_page.dart +4 -4
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_background.dart +4 -2
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_feature.dart +39 -121
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_illustration_scaffold.dart +105 -70
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_module_mockups.dart +639 -0
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_progress.dart +62 -50
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +38 -28
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_step_header.dart +75 -0
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_sticky_footer.dart +16 -5
- package/templates/firebase/lib/features/onboarding/ui/widgets/selectable_row_tile.dart +26 -22
- package/templates/firebase/lib/features/settings/settings_page.dart +601 -90
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_page.dart +1193 -0
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_paywalls.dart +1 -1
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_routes.dart +2 -3
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_api.dart +86 -0
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_tab.dart +1215 -0
- package/templates/firebase/lib/features/settings/ui/components/admin/send_push_notification_page.dart +236 -0
- package/templates/firebase/lib/features/settings/ui/components/avatar_component.dart +3 -3
- package/templates/firebase/lib/features/settings/ui/widgets/kasy_user_avatar.dart +77 -0
- package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +1 -0
- package/templates/firebase/lib/features/{subscription → subscriptions}/api/entities/subscription_entity.dart +17 -0
- package/templates/firebase/lib/features/{subscription → subscriptions}/api/inapp_subscription_api.dart +67 -46
- package/templates/firebase/lib/features/subscriptions/api/revenuecat_product.dart +189 -0
- package/templates/firebase/lib/features/subscriptions/api/stripe_backend_api.dart +55 -0
- package/templates/firebase/lib/features/subscriptions/api/stripe_payment_api.dart +82 -0
- package/templates/firebase/lib/features/subscriptions/api/stripe_product.dart +178 -0
- package/templates/firebase/lib/features/{subscription → subscriptions}/api/subscription_api.dart +1 -1
- package/templates/firebase/lib/features/subscriptions/api/subscription_payment_api.dart +53 -0
- package/templates/firebase/lib/features/subscriptions/api/subscription_payment_api_provider.dart +21 -0
- package/templates/firebase/lib/features/{subscription → subscriptions}/providers/premium_page_provider.dart +9 -6
- package/templates/firebase/lib/features/{subscription → subscriptions}/repositories/subscription_repository.dart +26 -30
- package/{lib/scaffold/backends/supabase/patch/lib/features/subscription → templates/firebase/lib/features/subscriptions}/shared/maybeshow_premium.dart +0 -2
- package/templates/firebase/lib/features/subscriptions/shared/subscription_management.dart +45 -0
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/active_premium_content.dart +28 -4
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_minimal.dart +7 -7
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_row.dart +8 -8
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_with_switch.dart +7 -7
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/premium_content.dart +7 -7
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/premium_page_factory.dart +5 -5
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/premium_page.dart +5 -5
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/comparison_table.dart +1 -1
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/paywall_empty_state.dart +4 -4
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_bottom_menu.dart +3 -4
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/selectable_col.dart +1 -1
- package/templates/firebase/lib/i18n/en.i18n.json +171 -46
- package/templates/firebase/lib/i18n/es.i18n.json +175 -50
- package/templates/firebase/lib/i18n/pt.i18n.json +166 -41
- package/templates/firebase/lib/main.dart +6 -3
- package/templates/firebase/lib/router.dart +15 -23
- package/templates/firebase/pubspec.yaml +4 -5
- package/templates/firebase/test/core/data/repositories/user_repository_test.dart +3 -3
- package/templates/firebase/test/core/states/user_state_notifier_test.dart +4 -4
- package/templates/firebase/test/core/widgets/focus_ring_shape_test.dart +55 -0
- package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_inapp_subscription_api.dart +11 -4
- package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_revenuecat_product.dart +1 -0
- package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_subscription_api.dart +2 -2
- package/templates/firebase/test/features/{subscription → subscriptions}/subscription_page_test.dart +4 -4
- package/templates/firebase/test/test_utils.dart +6 -6
- package/templates/firebase/web/index.html +5 -2
- package/lib/scaffold/backends/api/patch/lib/features/llm_chat/api/llm_chat_api.dart +0 -58
- package/lib/scaffold/backends/api/patch/lib/features/subscription/shared/maybeshow_premium.dart +0 -86
- package/lib/scaffold/backends/supabase/migrations/20240101000009_llm_messages.sql +0 -22
- package/lib/scaffold/backends/supabase/patch/lib/features/llm_chat/api/llm_chat_api.dart +0 -47
- package/templates/firebase/assets/images/onboarding/authentication-login-template.jpg +0 -0
- package/templates/firebase/assets/images/onboarding/img2.jpg +0 -0
- package/templates/firebase/assets/images/onboarding/img3.jpg +0 -0
- package/templates/firebase/assets/images/onboarding/notifications.png +0 -0
- package/templates/firebase/assets/images/onboarding/purchase.png +0 -0
- package/templates/firebase/lib/core/sidebar/kasy_sidebar.dart +0 -2021
- package/templates/firebase/lib/features/authentication/ui/widgets/auth_brand.dart +0 -35
- package/templates/firebase/lib/features/home/home_features_page.dart +0 -207
- package/templates/firebase/lib/features/llm_chat/api/llm_chat_api.dart +0 -50
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_bottom_sheet.dart +0 -316
- package/templates/firebase/lib/features/subscription/shared/maybeshow_premium.dart +0 -85
- /package/templates/firebase/lib/features/{local_reminder → local_reminders}/repositories/reminder_preferences.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/providers/models/premium_state.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/feature_line.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_background.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_background_gradient.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_banner.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_card.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_close_button.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_feature.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/selectable_row.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/trial_switcher.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/unsubscribe_feedback_popup.dart +0 -0
|
@@ -1,21 +1,32 @@
|
|
|
1
1
|
import 'package:flutter/material.dart';
|
|
2
2
|
|
|
3
|
-
/// Kasy Design System — Border Radius Tokens
|
|
3
|
+
/// Kasy Design System — Border Radius Tokens (HeroUI / Tailwind scale)
|
|
4
4
|
///
|
|
5
|
-
///
|
|
6
|
-
///
|
|
7
|
-
///
|
|
5
|
+
/// Radius is a system-level variable based on Tailwind's base radius values.
|
|
6
|
+
/// The full HeroUI scale is exposed as `rounded*` tokens; the existing semantic
|
|
7
|
+
/// names (xs/sm/md/lg/xl/full) are kept for backward compatibility and already
|
|
8
|
+
/// land on points of this scale:
|
|
9
|
+
/// xs (4) = roundedSm · sm (8) = roundedLg · md (12) = roundedXl
|
|
10
|
+
/// lg (16) = rounded2xl · xl (24) = rounded3xl
|
|
8
11
|
///
|
|
9
|
-
///
|
|
10
|
-
/// xs → 4
|
|
11
|
-
/// sm → 8
|
|
12
|
-
/// md → 12
|
|
13
|
-
/// lg → 16
|
|
14
|
-
/// xl → 24
|
|
15
|
-
/// full → 999 (pill shape / fully rounded)
|
|
12
|
+
/// Usage: KasyRadius.md → 12.0 · BorderRadius.circular(KasyRadius.md)
|
|
16
13
|
class KasyRadius {
|
|
17
14
|
KasyRadius._();
|
|
18
15
|
|
|
16
|
+
// --- HeroUI / Tailwind scale (canonical) ---
|
|
17
|
+
static const double roundedNone = 0;
|
|
18
|
+
static const double roundedXs = 2;
|
|
19
|
+
static const double roundedSm = 4;
|
|
20
|
+
static const double roundedMd = 6;
|
|
21
|
+
static const double roundedLg = 8;
|
|
22
|
+
static const double roundedXl = 12;
|
|
23
|
+
static const double rounded2xl = 16;
|
|
24
|
+
static const double rounded2_5xl = 20;
|
|
25
|
+
static const double rounded3xl = 24;
|
|
26
|
+
static const double rounded4xl = 32;
|
|
27
|
+
static const double roundedFull = 9999;
|
|
28
|
+
|
|
29
|
+
// --- Semantic names (backward compatible — map onto the scale above) ---
|
|
19
30
|
static const double xs = 4;
|
|
20
31
|
static const double sm = 8;
|
|
21
32
|
static const double md = 12;
|
|
@@ -61,4 +61,70 @@ class KasyShadows {
|
|
|
61
61
|
blurRadius: base.blurRadius - 0.3,
|
|
62
62
|
);
|
|
63
63
|
}
|
|
64
|
+
|
|
65
|
+
// -------------------------------------------------------------------------
|
|
66
|
+
// HeroUI Effect Styles — exact Figma specs (drop-shadow layers).
|
|
67
|
+
//
|
|
68
|
+
// The Figma effects also include inner-shadow and background-blur layers.
|
|
69
|
+
// Flutter has no native inner shadow and blur needs a [BackdropFilter], so
|
|
70
|
+
// these lists model the drop-shadow stack — the part expressible as
|
|
71
|
+
// [BoxShadow]. Blur radii and the inner-shadow tint are exposed separately.
|
|
72
|
+
// -------------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
/// Surface elevation (cards, panels, modals).
|
|
75
|
+
static const List<BoxShadow> surface = [
|
|
76
|
+
BoxShadow(color: Color(0x0F000000), blurRadius: 1),
|
|
77
|
+
BoxShadow(color: Color(0x0F000000), offset: Offset(0, 1), blurRadius: 2),
|
|
78
|
+
BoxShadow(color: Color(0x0A000000), offset: Offset(0, 2), blurRadius: 4),
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
/// Form field resting elevation (same drop stack as [surface]).
|
|
82
|
+
static const List<BoxShadow> field = surface;
|
|
83
|
+
|
|
84
|
+
/// Switch thumb.
|
|
85
|
+
static const List<BoxShadow> switchControl = [
|
|
86
|
+
BoxShadow(color: Color(0x05000000), blurRadius: 5),
|
|
87
|
+
BoxShadow(color: Color(0x0F000000), offset: Offset(0, 2), blurRadius: 10),
|
|
88
|
+
BoxShadow(color: Color(0x4D000000), blurRadius: 1),
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
/// Segmented control / tab selected thumb.
|
|
92
|
+
static const List<BoxShadow> tab = [
|
|
93
|
+
BoxShadow(color: Color(0x0F000000), offset: Offset(0, 2), blurRadius: 8),
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
/// Floating overlays (menus, popovers, dropdowns).
|
|
97
|
+
static const List<BoxShadow> overlay = [
|
|
98
|
+
BoxShadow(color: Color(0x14000000), offset: Offset(0, 14), blurRadius: 28),
|
|
99
|
+
BoxShadow(color: Color(0x08000000), offset: Offset(0, -6), blurRadius: 12),
|
|
100
|
+
BoxShadow(color: Color(0x0F000000), offset: Offset(0, 2), blurRadius: 8),
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
/// Inner-shadow tint used on insets (apply via custom painter / overlay).
|
|
104
|
+
static const Color inner = Color(0x4D000000);
|
|
105
|
+
|
|
106
|
+
/// Background-blur radius for translucent surfaces (Figma `blur`), logical px.
|
|
107
|
+
static const double blurRadius = 64;
|
|
108
|
+
|
|
109
|
+
/// Background-blur radius for backdrops (Figma `blur-backdrop`), logical px.
|
|
110
|
+
static const double backdropBlurRadius = 12;
|
|
111
|
+
|
|
112
|
+
/// Focus ring thickness (HeroUI `ring-focus-width`).
|
|
113
|
+
static const double ringFocusWidth = 4;
|
|
114
|
+
|
|
115
|
+
/// Gap between the element and its focus ring (HeroUI `ring-offset-width`).
|
|
116
|
+
static const double ringOffsetWidth = 2;
|
|
117
|
+
|
|
118
|
+
/// HeroUI focus ring as box-shadows: an accent ring with a background-colored
|
|
119
|
+
/// offset gap. Pass `ring` = accent and `gap` = the surrounding background.
|
|
120
|
+
static List<BoxShadow> focusRing({required Color ring, required Color gap}) =>
|
|
121
|
+
[
|
|
122
|
+
BoxShadow(color: ring, spreadRadius: ringFocusWidth),
|
|
123
|
+
BoxShadow(color: gap, spreadRadius: ringOffsetWidth),
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
/// Focus ring for form fields: accent ring with no offset gap.
|
|
127
|
+
static List<BoxShadow> focusRingField({required Color ring}) => [
|
|
128
|
+
BoxShadow(color: ring, spreadRadius: ringOffsetWidth),
|
|
129
|
+
];
|
|
64
130
|
}
|
|
@@ -1,32 +1,68 @@
|
|
|
1
1
|
import 'package:flutter/material.dart';
|
|
2
2
|
import 'package:google_fonts/google_fonts.dart';
|
|
3
3
|
|
|
4
|
-
/// Kasy Design System — Typography Tokens
|
|
4
|
+
/// Kasy Design System — Typography Tokens (HeroUI type scale)
|
|
5
5
|
///
|
|
6
|
-
/// Single source of truth for all text styles in the app.
|
|
7
|
-
///
|
|
6
|
+
/// Single source of truth for all text styles in the app. Mirrors the HeroUI
|
|
7
|
+
/// typography, which is based on Tailwind's default scale: Inter, neutral
|
|
8
|
+
/// tracking (letter-spacing 0), predictable sizes and line-heights.
|
|
8
9
|
///
|
|
9
|
-
///
|
|
10
|
-
///
|
|
11
|
-
///
|
|
10
|
+
/// HeroUI roles (heading1…buttonSm) are exposed as static getters and are the
|
|
11
|
+
/// canonical styles. The Material slots (displayLarge…labelSmall) map onto
|
|
12
|
+
/// those roles so existing `context.textTheme.*` call sites keep working.
|
|
12
13
|
///
|
|
13
|
-
///
|
|
14
|
-
///
|
|
14
|
+
/// Usage:
|
|
15
|
+
/// context.textTheme.bodyMedium → Body sm (Material slot)
|
|
16
|
+
/// KasyTextTheme.heading1 → HeroUI Heading 1
|
|
15
17
|
class KasyTextTheme extends ThemeExtension<KasyTextTheme> {
|
|
16
18
|
// -----------------------------------------------------------------------
|
|
17
|
-
// Font family —
|
|
19
|
+
// Font family — Inter. Change here to update the entire app.
|
|
18
20
|
// -----------------------------------------------------------------------
|
|
19
|
-
static TextStyle
|
|
21
|
+
static TextStyle _inter(
|
|
22
|
+
FontWeight weight,
|
|
23
|
+
double size,
|
|
24
|
+
double lineHeight, {
|
|
25
|
+
TextDecoration? decoration,
|
|
26
|
+
}) =>
|
|
27
|
+
GoogleFonts.inter(
|
|
28
|
+
fontSize: size,
|
|
29
|
+
fontWeight: weight,
|
|
30
|
+
height: lineHeight / size,
|
|
31
|
+
letterSpacing: 0,
|
|
32
|
+
decoration: decoration,
|
|
33
|
+
);
|
|
20
34
|
|
|
21
35
|
// -----------------------------------------------------------------------
|
|
22
|
-
//
|
|
36
|
+
// HeroUI roles — canonical styles (color applied per-theme downstream)
|
|
37
|
+
// -----------------------------------------------------------------------
|
|
38
|
+
static TextStyle get heading1 => _inter(FontWeight.w800, 36, 40);
|
|
39
|
+
static TextStyle get heading2 => _inter(FontWeight.w700, 24, 32);
|
|
40
|
+
static TextStyle get heading3 => _inter(FontWeight.w600, 20, 28);
|
|
41
|
+
static TextStyle get heading4 => _inter(FontWeight.w600, 16, 24);
|
|
42
|
+
static TextStyle get bodyBase => _inter(FontWeight.w400, 16, 24);
|
|
43
|
+
static TextStyle get bodyBaseMedium => _inter(FontWeight.w500, 16, 24);
|
|
44
|
+
static TextStyle get bodySm => _inter(FontWeight.w400, 14, 20);
|
|
45
|
+
static TextStyle get bodySmMedium => _inter(FontWeight.w500, 14, 20);
|
|
46
|
+
static TextStyle get bodyXs => _inter(FontWeight.w400, 12, 16);
|
|
47
|
+
static TextStyle get bodyXsMedium => _inter(FontWeight.w500, 12, 16);
|
|
48
|
+
static TextStyle get linkBase =>
|
|
49
|
+
_inter(FontWeight.w500, 16, 24, decoration: TextDecoration.underline);
|
|
50
|
+
static TextStyle get linkSm =>
|
|
51
|
+
_inter(FontWeight.w500, 14, 20, decoration: TextDecoration.underline);
|
|
52
|
+
static TextStyle get textFieldBase => _inter(FontWeight.w400, 16, 24);
|
|
53
|
+
static TextStyle get textFieldSm => _inter(FontWeight.w400, 14, 20);
|
|
54
|
+
static TextStyle get buttonBase => _inter(FontWeight.w500, 16, 24);
|
|
55
|
+
static TextStyle get buttonSm => _inter(FontWeight.w500, 14, 20);
|
|
56
|
+
|
|
57
|
+
// -----------------------------------------------------------------------
|
|
58
|
+
// Display — large hero text (extends the HeroUI scale upward in Inter)
|
|
23
59
|
// -----------------------------------------------------------------------
|
|
24
60
|
final TextStyle displayLarge;
|
|
25
61
|
final TextStyle displayMedium;
|
|
26
62
|
final TextStyle displaySmall;
|
|
27
63
|
|
|
28
64
|
// -----------------------------------------------------------------------
|
|
29
|
-
// Headline — section headings
|
|
65
|
+
// Headline — section headings (Heading 1–3)
|
|
30
66
|
// -----------------------------------------------------------------------
|
|
31
67
|
final TextStyle headlineLarge;
|
|
32
68
|
final TextStyle headlineMedium;
|
|
@@ -76,36 +112,34 @@ class KasyTextTheme extends ThemeExtension<KasyTextTheme> {
|
|
|
76
112
|
});
|
|
77
113
|
|
|
78
114
|
factory KasyTextTheme.build() {
|
|
79
|
-
final base = _base;
|
|
80
|
-
|
|
81
115
|
return KasyTextTheme(
|
|
82
|
-
// Display
|
|
83
|
-
displayLarge:
|
|
84
|
-
displayMedium:
|
|
85
|
-
displaySmall:
|
|
86
|
-
|
|
87
|
-
// Headline
|
|
88
|
-
headlineLarge:
|
|
89
|
-
headlineMedium:
|
|
90
|
-
headlineSmall:
|
|
91
|
-
|
|
92
|
-
// Title
|
|
93
|
-
titleLarge:
|
|
94
|
-
titleMedium:
|
|
95
|
-
titleSmall:
|
|
96
|
-
|
|
97
|
-
// Body
|
|
98
|
-
bodyLarge:
|
|
99
|
-
bodyMedium:
|
|
100
|
-
bodySmall:
|
|
101
|
-
|
|
102
|
-
// Label
|
|
103
|
-
labelLarge:
|
|
104
|
-
labelMedium:
|
|
105
|
-
labelSmall:
|
|
106
|
-
|
|
107
|
-
// Legacy primary
|
|
108
|
-
primary:
|
|
116
|
+
// Display — hero text in Inter, neutral tracking.
|
|
117
|
+
displayLarge: _inter(FontWeight.w800, 57, 64),
|
|
118
|
+
displayMedium: _inter(FontWeight.w800, 45, 52),
|
|
119
|
+
displaySmall: heading1, // 36/40
|
|
120
|
+
|
|
121
|
+
// Headline → HeroUI Heading 1–3.
|
|
122
|
+
headlineLarge: heading1,
|
|
123
|
+
headlineMedium: heading2,
|
|
124
|
+
headlineSmall: heading3,
|
|
125
|
+
|
|
126
|
+
// Title → HeroUI Heading 3 / Heading 4 / Body sm medium.
|
|
127
|
+
titleLarge: heading3,
|
|
128
|
+
titleMedium: heading4,
|
|
129
|
+
titleSmall: bodySmMedium,
|
|
130
|
+
|
|
131
|
+
// Body → HeroUI Body base / Body sm / Body xs.
|
|
132
|
+
bodyLarge: bodyBase,
|
|
133
|
+
bodyMedium: bodySm,
|
|
134
|
+
bodySmall: bodyXs,
|
|
135
|
+
|
|
136
|
+
// Label → HeroUI Button sm / Body xs medium.
|
|
137
|
+
labelLarge: buttonSm,
|
|
138
|
+
labelMedium: bodyXsMedium,
|
|
139
|
+
labelSmall: bodyXsMedium,
|
|
140
|
+
|
|
141
|
+
// Legacy primary.
|
|
142
|
+
primary: bodyBase,
|
|
109
143
|
);
|
|
110
144
|
}
|
|
111
145
|
|
|
@@ -64,15 +64,20 @@ class UniversalThemeFactory extends KasyThemeDataFactory {
|
|
|
64
64
|
textTheme: defaultTextStyle,
|
|
65
65
|
),
|
|
66
66
|
appBarTheme: AppBarTheme(
|
|
67
|
-
backgroundColor: colors.
|
|
68
|
-
|
|
69
|
-
: const Color(0xFFF7F7F7),
|
|
70
|
-
foregroundColor: colors.onSurface,
|
|
67
|
+
backgroundColor: colors.surface,
|
|
68
|
+
foregroundColor: colors.foreground,
|
|
71
69
|
elevation: colors.background.computeLuminance() < 0.5 ? 0 : 1.4,
|
|
72
70
|
shadowColor: colors.background.computeLuminance() < 0.5
|
|
73
71
|
? Colors.transparent
|
|
74
72
|
: Colors.black.withValues(alpha: 0.05),
|
|
75
73
|
),
|
|
74
|
+
// Modal bottom sheets span the full available width on every breakpoint.
|
|
75
|
+
// Material 3 otherwise caps them at 640 logical px, so on web/desktop the
|
|
76
|
+
// sheet floats at tablet width with empty gutters on each side. An empty
|
|
77
|
+
// BoxConstraints() removes that cap; phones are unaffected (already < 640).
|
|
78
|
+
bottomSheetTheme: const BottomSheetThemeData(
|
|
79
|
+
constraints: BoxConstraints(),
|
|
80
|
+
),
|
|
76
81
|
),
|
|
77
82
|
);
|
|
78
83
|
}
|
|
@@ -203,10 +203,11 @@ class _WebDevicePreviewState extends State<WebDevicePreview>
|
|
|
203
203
|
Future<void> _bootstrap() async {
|
|
204
204
|
final prefs = await SharedPreferences.getInstance();
|
|
205
205
|
|
|
206
|
-
// Default
|
|
207
|
-
//
|
|
208
|
-
//
|
|
209
|
-
|
|
206
|
+
// Default OFF: the web app renders at its real desktop proportions instead
|
|
207
|
+
// of inside a simulated device frame (which distorts scale/proportion).
|
|
208
|
+
// Devs who want to preview the mobile app in a device frame toggle it on
|
|
209
|
+
// (shortcut), and that choice persists for subsequent launches.
|
|
210
|
+
final savedEnabled = prefs.getBool(webDevicePreviewEnabledPrefKey) ?? false;
|
|
210
211
|
final savedPlatform = prefs.getInt(_platformPrefKey);
|
|
211
212
|
final savedIosIndex = prefs.getInt(_iosIndexPrefKey);
|
|
212
213
|
final savedAndroidIndex = prefs.getInt(_androidIndexPrefKey);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import 'package:flutter/foundation.dart' show kIsWeb;
|
|
2
|
+
import 'package:flutter/widgets.dart';
|
|
3
|
+
|
|
4
|
+
/// Global render scale applied to the app on web.
|
|
5
|
+
///
|
|
6
|
+
/// Flutter web tends to render ~10% larger than equivalent HTML apps at the
|
|
7
|
+
/// browser's 100% zoom, so the whole UI feels oversized on desktop. `0.9` brings
|
|
8
|
+
/// it to the proportion the design targets (i.e. what 90% zoom looked like)
|
|
9
|
+
/// without the user having to touch the browser zoom.
|
|
10
|
+
const double kWebViewportScale = 0.9;
|
|
11
|
+
|
|
12
|
+
/// Renders [child] uniformly scaled by [scale] on web (no-op elsewhere).
|
|
13
|
+
///
|
|
14
|
+
/// This is a *true* viewport scale, not a visual-only [Transform]: it enlarges
|
|
15
|
+
/// the logical [MediaQuery] size by `1/scale` and fits it back into the real
|
|
16
|
+
/// viewport with a [FittedBox], so layout, scrolling and hit-testing all stay
|
|
17
|
+
/// correct — exactly as if the browser were zoomed out, but baked into the app.
|
|
18
|
+
class WebViewportScale extends StatelessWidget {
|
|
19
|
+
final Widget child;
|
|
20
|
+
final double scale;
|
|
21
|
+
|
|
22
|
+
const WebViewportScale({
|
|
23
|
+
super.key,
|
|
24
|
+
required this.child,
|
|
25
|
+
this.scale = kWebViewportScale,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
/// Wraps [child] on web; returns it untouched on every other platform.
|
|
29
|
+
static Widget wrap(Widget child) =>
|
|
30
|
+
kIsWeb ? WebViewportScale(child: child) : child;
|
|
31
|
+
|
|
32
|
+
@override
|
|
33
|
+
Widget build(BuildContext context) {
|
|
34
|
+
if (!kIsWeb || scale == 1.0) return child;
|
|
35
|
+
final MediaQueryData mq = MediaQuery.of(context);
|
|
36
|
+
final Size logicalSize = Size(
|
|
37
|
+
mq.size.width / scale,
|
|
38
|
+
mq.size.height / scale,
|
|
39
|
+
);
|
|
40
|
+
return MediaQuery(
|
|
41
|
+
data: mq.copyWith(size: logicalSize),
|
|
42
|
+
child: FittedBox(
|
|
43
|
+
fit: BoxFit.fill,
|
|
44
|
+
child: SizedBox(
|
|
45
|
+
width: logicalSize.width,
|
|
46
|
+
height: logicalSize.height,
|
|
47
|
+
child: child,
|
|
48
|
+
),
|
|
49
|
+
),
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import 'package:flutter/material.dart';
|
|
2
|
+
import 'package:kasy_kit/core/theme/theme.dart';
|
|
3
|
+
|
|
4
|
+
/// Premium brand mark: a brand‑gradient rounded squircle holding an icon (or any
|
|
5
|
+
/// child), optionally floating on a soft radial brand glow.
|
|
6
|
+
///
|
|
7
|
+
/// This is the kit's single "hero badge" primitive. Auth screens and permission
|
|
8
|
+
/// prompts all build their top mark from it, so rebranding is a one‑file change:
|
|
9
|
+
/// swap the [icon] (or pass a logo as [child]), or retint via [gradient] /
|
|
10
|
+
/// [foregroundColor]. Colors default to the active theme's brand color, so it
|
|
11
|
+
/// adapts to light/dark automatically.
|
|
12
|
+
class KasyBrandBadge extends StatelessWidget {
|
|
13
|
+
const KasyBrandBadge({
|
|
14
|
+
super.key,
|
|
15
|
+
this.icon,
|
|
16
|
+
this.child,
|
|
17
|
+
this.size = 64,
|
|
18
|
+
this.glyphSize = 30,
|
|
19
|
+
this.glow = true,
|
|
20
|
+
this.gradient,
|
|
21
|
+
this.foregroundColor,
|
|
22
|
+
}) : assert(
|
|
23
|
+
icon != null || child != null,
|
|
24
|
+
'KasyBrandBadge needs an icon or a child',
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
/// Icon drawn inside the badge. Ignored when [child] is provided.
|
|
28
|
+
final IconData? icon;
|
|
29
|
+
|
|
30
|
+
/// Custom content (e.g. an `Image` logo). Overrides [icon] when set.
|
|
31
|
+
final Widget? child;
|
|
32
|
+
|
|
33
|
+
/// Badge edge length.
|
|
34
|
+
final double size;
|
|
35
|
+
|
|
36
|
+
/// Icon size inside the badge.
|
|
37
|
+
final double glyphSize;
|
|
38
|
+
|
|
39
|
+
/// Soft radial brand glow behind the badge.
|
|
40
|
+
final bool glow;
|
|
41
|
+
|
|
42
|
+
/// Overrides the default subtle brand gradient fill.
|
|
43
|
+
final Gradient? gradient;
|
|
44
|
+
|
|
45
|
+
/// Icon color. Defaults to [KasyColors.onPrimary].
|
|
46
|
+
final Color? foregroundColor;
|
|
47
|
+
|
|
48
|
+
@override
|
|
49
|
+
Widget build(BuildContext context) {
|
|
50
|
+
final KasyColors colors = context.colors;
|
|
51
|
+
|
|
52
|
+
final Widget badge = Container(
|
|
53
|
+
width: size,
|
|
54
|
+
height: size,
|
|
55
|
+
alignment: Alignment.center,
|
|
56
|
+
decoration: BoxDecoration(
|
|
57
|
+
gradient:
|
|
58
|
+
gradient ??
|
|
59
|
+
LinearGradient(
|
|
60
|
+
begin: Alignment.topLeft,
|
|
61
|
+
end: Alignment.bottomRight,
|
|
62
|
+
colors: [
|
|
63
|
+
colors.primary,
|
|
64
|
+
Color.lerp(colors.primary, Colors.black, 0.18)!,
|
|
65
|
+
],
|
|
66
|
+
),
|
|
67
|
+
borderRadius: BorderRadius.circular(size * 0.3),
|
|
68
|
+
boxShadow: [
|
|
69
|
+
BoxShadow(
|
|
70
|
+
color: colors.primary.withValues(
|
|
71
|
+
alpha: context.isDark ? 0.34 : 0.26,
|
|
72
|
+
),
|
|
73
|
+
blurRadius: 22,
|
|
74
|
+
spreadRadius: -6,
|
|
75
|
+
offset: const Offset(0, 10),
|
|
76
|
+
),
|
|
77
|
+
],
|
|
78
|
+
),
|
|
79
|
+
child:
|
|
80
|
+
child ??
|
|
81
|
+
Icon(
|
|
82
|
+
icon,
|
|
83
|
+
color: foregroundColor ?? colors.onPrimary,
|
|
84
|
+
size: glyphSize,
|
|
85
|
+
),
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
if (!glow) return badge;
|
|
89
|
+
|
|
90
|
+
final double glowSize = size * 2.2;
|
|
91
|
+
return SizedBox(
|
|
92
|
+
width: glowSize,
|
|
93
|
+
height: glowSize,
|
|
94
|
+
child: Stack(
|
|
95
|
+
alignment: Alignment.center,
|
|
96
|
+
clipBehavior: Clip.none,
|
|
97
|
+
children: [
|
|
98
|
+
IgnorePointer(
|
|
99
|
+
child: Container(
|
|
100
|
+
decoration: BoxDecoration(
|
|
101
|
+
shape: BoxShape.circle,
|
|
102
|
+
gradient: RadialGradient(
|
|
103
|
+
colors: [
|
|
104
|
+
colors.primary.withValues(
|
|
105
|
+
alpha: context.isDark ? 0.20 : 0.14,
|
|
106
|
+
),
|
|
107
|
+
colors.primary.withValues(alpha: 0),
|
|
108
|
+
],
|
|
109
|
+
),
|
|
110
|
+
),
|
|
111
|
+
),
|
|
112
|
+
),
|
|
113
|
+
badge,
|
|
114
|
+
],
|
|
115
|
+
),
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import 'package:flutter/material.dart';
|
|
2
|
+
import 'package:kasy_kit/core/theme/theme.dart';
|
|
3
|
+
|
|
4
|
+
/// The app's brand logo — the SAME artwork shown on the native splash screen,
|
|
5
|
+
/// so the launch → app handoff feels seamless.
|
|
6
|
+
///
|
|
7
|
+
/// It auto‑swaps between the light and dark splash logos based on the active
|
|
8
|
+
/// theme. To rebrand, replace the two PNGs (or point [lightAsset] / [darkAsset]
|
|
9
|
+
/// at your own) — keep them in sync with `flutter_native_splash` in
|
|
10
|
+
/// `pubspec.yaml` so the splash and the in‑app logo always match.
|
|
11
|
+
class KasyBrandLogo extends StatelessWidget {
|
|
12
|
+
const KasyBrandLogo({
|
|
13
|
+
super.key,
|
|
14
|
+
this.height = 48,
|
|
15
|
+
this.lightAsset = 'assets/images/splash_logo_light.png',
|
|
16
|
+
this.darkAsset = 'assets/images/splash_logo_dark.png',
|
|
17
|
+
this.semanticLabel,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
/// Rendered logo height. Width follows the artwork's aspect ratio.
|
|
21
|
+
final double height;
|
|
22
|
+
|
|
23
|
+
/// Logo used in light mode (matches the splash `image`).
|
|
24
|
+
final String lightAsset;
|
|
25
|
+
|
|
26
|
+
/// Logo used in dark mode (matches the splash `image_dark`).
|
|
27
|
+
final String darkAsset;
|
|
28
|
+
|
|
29
|
+
final String? semanticLabel;
|
|
30
|
+
|
|
31
|
+
@override
|
|
32
|
+
Widget build(BuildContext context) {
|
|
33
|
+
return Image.asset(
|
|
34
|
+
context.isDark ? darkAsset : lightAsset,
|
|
35
|
+
height: height,
|
|
36
|
+
fit: BoxFit.contain,
|
|
37
|
+
semanticLabel: semanticLabel,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import 'package:flutter/material.dart';
|
|
2
|
+
import 'package:flutter/services.dart';
|
|
3
|
+
import 'package:kasy_kit/core/theme/theme.dart';
|
|
4
|
+
|
|
5
|
+
/// The kit's single keyboard focus indicator: a thin primary-coloured ring shown
|
|
6
|
+
/// ONLY during keyboard navigation (never on pointer/touch focus), lifted off the
|
|
7
|
+
/// control by a hair-line gap so it reads as a crisp outline, not a glow.
|
|
8
|
+
///
|
|
9
|
+
/// Visibility follows [FocusManager]'s highlight mode via
|
|
10
|
+
/// [FocusableActionDetector.onShowFocusHighlight], so clicking/tapping a control
|
|
11
|
+
/// (or it merely receiving pointer focus) never paints the ring.
|
|
12
|
+
///
|
|
13
|
+
/// Two ways to use it:
|
|
14
|
+
/// - Controls without their own keyboard activation: pass [onActivate]
|
|
15
|
+
/// (Enter/Space) and let this widget own focus.
|
|
16
|
+
/// - Controls that already activate from their own focusable (e.g. an
|
|
17
|
+
/// [InkWell]): set that focusable's `canRequestFocus: false` and wrap it here
|
|
18
|
+
/// with [onActivate] so focus + ring live in one place.
|
|
19
|
+
class KasyFocusRing extends StatefulWidget {
|
|
20
|
+
final Widget child;
|
|
21
|
+
final BorderRadius borderRadius;
|
|
22
|
+
|
|
23
|
+
/// Invoked on Enter/Space while focused. When null the ring still appears but
|
|
24
|
+
/// no keyboard activation is wired (use when a child handles its own keys).
|
|
25
|
+
final VoidCallback? onActivate;
|
|
26
|
+
|
|
27
|
+
/// When false the ring never takes focus or paints (disabled controls).
|
|
28
|
+
final bool enabled;
|
|
29
|
+
|
|
30
|
+
const KasyFocusRing({
|
|
31
|
+
super.key,
|
|
32
|
+
required this.child,
|
|
33
|
+
required this.borderRadius,
|
|
34
|
+
this.onActivate,
|
|
35
|
+
this.enabled = true,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
@override
|
|
39
|
+
State<KasyFocusRing> createState() => _KasyFocusRingState();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
class _KasyFocusRingState extends State<KasyFocusRing> {
|
|
43
|
+
bool _showFocusRing = false;
|
|
44
|
+
|
|
45
|
+
static const Map<ShortcutActivator, Intent> _activateShortcuts =
|
|
46
|
+
<ShortcutActivator, Intent>{
|
|
47
|
+
SingleActivator(LogicalKeyboardKey.enter): ActivateIntent(),
|
|
48
|
+
SingleActivator(LogicalKeyboardKey.numpadEnter): ActivateIntent(),
|
|
49
|
+
SingleActivator(LogicalKeyboardKey.space): ActivateIntent(),
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
@override
|
|
53
|
+
Widget build(BuildContext context) {
|
|
54
|
+
if (!widget.enabled) return widget.child;
|
|
55
|
+
|
|
56
|
+
final Color ringColor = context.colors.primary;
|
|
57
|
+
// A hair-line gap in the canvas colour lifts the ring just off the control
|
|
58
|
+
// so it reads as a crisp outline (same focus language as KasyTextField) and
|
|
59
|
+
// stays visible even on primary-coloured buttons (e.g. the chat send orb).
|
|
60
|
+
final Color gapColor = context.colors.background;
|
|
61
|
+
final VoidCallback? onActivate = widget.onActivate;
|
|
62
|
+
|
|
63
|
+
return FocusableActionDetector(
|
|
64
|
+
shortcuts: onActivate != null
|
|
65
|
+
? _activateShortcuts
|
|
66
|
+
: const <ShortcutActivator, Intent>{},
|
|
67
|
+
actions: <Type, Action<Intent>>{
|
|
68
|
+
if (onActivate != null)
|
|
69
|
+
ActivateIntent: CallbackAction<ActivateIntent>(
|
|
70
|
+
onInvoke: (_) {
|
|
71
|
+
onActivate();
|
|
72
|
+
return null;
|
|
73
|
+
},
|
|
74
|
+
),
|
|
75
|
+
},
|
|
76
|
+
onShowFocusHighlight: (show) {
|
|
77
|
+
if (mounted && show != _showFocusRing) {
|
|
78
|
+
setState(() => _showFocusRing = show);
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
// Two concentric layers instead of one box with two shadows: a [BoxShadow]
|
|
82
|
+
// inflates its box by [spreadRadius] but keeps the box's corner radius, so
|
|
83
|
+
// on a circular control the inflated ring squares off at the corners and
|
|
84
|
+
// reads as a rounded rectangle. Giving each layer its own radius — matched
|
|
85
|
+
// to its inflated edge — keeps the ring a true contour of any shape.
|
|
86
|
+
// Outer: thin solid primary ring (~1.5px). Inner: a 1.5px gap so the ring
|
|
87
|
+
// floats just off the control instead of hugging it.
|
|
88
|
+
child: AnimatedContainer(
|
|
89
|
+
duration: const Duration(milliseconds: 120),
|
|
90
|
+
decoration: _showFocusRing
|
|
91
|
+
? BoxDecoration(
|
|
92
|
+
borderRadius: _inflate(widget.borderRadius, 3),
|
|
93
|
+
boxShadow: <BoxShadow>[
|
|
94
|
+
BoxShadow(color: ringColor, spreadRadius: 3),
|
|
95
|
+
],
|
|
96
|
+
)
|
|
97
|
+
: const BoxDecoration(),
|
|
98
|
+
child: AnimatedContainer(
|
|
99
|
+
duration: const Duration(milliseconds: 120),
|
|
100
|
+
decoration: _showFocusRing
|
|
101
|
+
? BoxDecoration(
|
|
102
|
+
borderRadius: _inflate(widget.borderRadius, 1.5),
|
|
103
|
+
boxShadow: <BoxShadow>[
|
|
104
|
+
BoxShadow(color: gapColor, spreadRadius: 1.5),
|
|
105
|
+
],
|
|
106
|
+
)
|
|
107
|
+
: const BoxDecoration(),
|
|
108
|
+
child: widget.child,
|
|
109
|
+
),
|
|
110
|
+
),
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/// Grows every corner of [radius] by [delta] so a layer inflated by the same
|
|
115
|
+
/// amount stays a perfect contour (a circle stays a circle, not a squircle).
|
|
116
|
+
BorderRadius _inflate(BorderRadius radius, double delta) {
|
|
117
|
+
final Radius bump = Radius.circular(delta);
|
|
118
|
+
return BorderRadius.only(
|
|
119
|
+
topLeft: radius.topLeft + bump,
|
|
120
|
+
topRight: radius.topRight + bump,
|
|
121
|
+
bottomLeft: radius.bottomLeft + bump,
|
|
122
|
+
bottomRight: radius.bottomRight + bump,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
}
|