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,77 +1,106 @@
|
|
|
1
1
|
import 'dart:async';
|
|
2
2
|
|
|
3
|
+
import 'package:bart/bart/bart_model.dart';
|
|
4
|
+
import 'package:bart/bart/widgets/side_bar/custom_sidebar.dart';
|
|
3
5
|
import 'package:flutter/material.dart';
|
|
4
|
-
import 'package:kasy_kit/
|
|
6
|
+
import 'package:kasy_kit/components/kasy_avatar.dart';
|
|
7
|
+
import 'package:kasy_kit/components/kasy_avatar_presets.dart';
|
|
8
|
+
import 'package:kasy_kit/core/theme/theme.dart';
|
|
5
9
|
import 'package:kasy_kit/core/widgets/kasy_hover.dart';
|
|
10
|
+
import 'package:kasy_kit/i18n/translations.g.dart';
|
|
6
11
|
|
|
7
12
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
8
|
-
// Design tokens —
|
|
13
|
+
// Design tokens — HeroUI Figma Kit (node 4678:41088 light / 21964:53880 dark)
|
|
9
14
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
10
15
|
|
|
11
|
-
|
|
12
|
-
const double
|
|
13
|
-
const double
|
|
14
|
-
const double
|
|
15
|
-
const double
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
// Figma sidebar is 223 wide; we run a touch wider for breathing room.
|
|
17
|
+
const double _kWidthOpen = 248.0;
|
|
18
|
+
const double _kWidthCollapsed = 78.0;
|
|
19
|
+
const double _kPadH = 16.0; // px-4
|
|
20
|
+
const double _kPadBottom = 16.0; // pb-4
|
|
21
|
+
// Top band that holds the logo. Equals the web header height (kasyWebHeaderHeight
|
|
22
|
+
// = 68) so the sidebar's first divider lines up with the header's bottom border.
|
|
23
|
+
const double _kTopBandHeight = 68.0;
|
|
24
|
+
const double _kItemRadius = 14.0; // rounded item / active pill
|
|
25
|
+
const double _kItemMinH = 36.0;
|
|
26
|
+
const double _kItemHPad = 12.0; // px-3
|
|
27
|
+
const double _kItemVPad = 6.0; // py-1.5
|
|
28
|
+
const double _kIconSize = 16.0;
|
|
29
|
+
const double _kIconGap = 12.0; // gap-3 (icon → label)
|
|
30
|
+
const double _kItemGap = 8.0; // gap between items (spacing/2)
|
|
31
|
+
const double _kHeaderGap = 24.0; // logo row → workspace selector (spacing/6)
|
|
32
|
+
const double _kDividerGap = 20.0; // gap around the section dividers (spacing/5)
|
|
33
|
+
const double _kNavGap = 20.0; // tabs → list (spacing/5)
|
|
34
|
+
const double _kFooterGap = 16.0; // bottom divider → search (spacing/4)
|
|
35
|
+
const double _kToggleSize = 36.0; // header panel-toggle button
|
|
36
|
+
|
|
37
|
+
// Submenu tree tokens (kept from the previous sidebar — still used by the
|
|
38
|
+
// connected/Income dropdown and its collapsed hover popup).
|
|
18
39
|
const double _kSubItemH = 32.0;
|
|
19
40
|
const double _kSubItemGap = 4.0;
|
|
20
41
|
const double _kSubIndent = 36.0;
|
|
21
42
|
const double _kTreeConnectorW = 13.0;
|
|
22
|
-
const double _kCollapseButtonSize = 28.0;
|
|
23
43
|
|
|
24
44
|
/// Returns the height of the vertical tree line for [n] sub-items.
|
|
25
45
|
double _treeLineHeight(int n) =>
|
|
26
46
|
(n * (_kSubItemH + _kSubItemGap) - _kSubItemGap) * 0.8627;
|
|
27
47
|
|
|
28
48
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
29
|
-
// Color palette
|
|
49
|
+
// Color palette — every value derives from the global Kasy theme so the sidebar
|
|
50
|
+
// follows light/dark automatically (nothing hardcoded).
|
|
30
51
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
31
52
|
|
|
32
|
-
|
|
33
|
-
light(
|
|
34
|
-
bg: Color(0xFFFFFFFF),
|
|
35
|
-
border: Color(0x1A000000),
|
|
36
|
-
divider: Color(0xFFF6F6F6),
|
|
37
|
-
activeBg: Color(0xFFF6F6F6),
|
|
38
|
-
textMuted: Color(0xFF757575),
|
|
39
|
-
textActive: Color(0xFF000000),
|
|
40
|
-
logout: Color(0xFFD55F5A),
|
|
41
|
-
),
|
|
42
|
-
dark(
|
|
43
|
-
bg: Color(0xFF161A23),
|
|
44
|
-
border: Color(0x1AFFFFFF),
|
|
45
|
-
divider: Color(0xFF2D2F39),
|
|
46
|
-
activeBg: Color(0xFF2D2F39),
|
|
47
|
-
textMuted: Color(0x80FFFFFF),
|
|
48
|
-
textActive: Color(0xCCFFFFFF),
|
|
49
|
-
logout: Color(0xFFCC8889),
|
|
50
|
-
);
|
|
51
|
-
|
|
53
|
+
class _SidebarColors {
|
|
52
54
|
const _SidebarColors({
|
|
53
55
|
required this.bg,
|
|
54
56
|
required this.border,
|
|
55
57
|
required this.divider,
|
|
56
58
|
required this.activeBg,
|
|
59
|
+
required this.segmentThumb,
|
|
57
60
|
required this.textMuted,
|
|
58
61
|
required this.textActive,
|
|
59
62
|
required this.logout,
|
|
63
|
+
required this.isDark,
|
|
60
64
|
});
|
|
61
65
|
|
|
66
|
+
/// Maps the HeroUI Figma tokens onto the global [KasyColors]:
|
|
67
|
+
/// `surface → surface`, `foreground → onSurface`, `foreground/muted → muted`,
|
|
68
|
+
/// `border → border`, `separator → separator`, `default → surfaceNeutralSoft`
|
|
69
|
+
/// (hover/active fill + tabs track), and the selected-tab thumb to the
|
|
70
|
+
/// dedicated `segment` token.
|
|
71
|
+
factory _SidebarColors.fromContext(BuildContext context) {
|
|
72
|
+
final c = context.colors;
|
|
73
|
+
final bool dark = context.isDark;
|
|
74
|
+
return _SidebarColors(
|
|
75
|
+
bg: c.surface,
|
|
76
|
+
border: c.border,
|
|
77
|
+
// Same token as the vertical edge line + the web header's bottom border,
|
|
78
|
+
// so the top divider continues that line seamlessly across the chrome.
|
|
79
|
+
divider: c.border,
|
|
80
|
+
// Hover / active item fill + tabs track + kbd chip (default/default).
|
|
81
|
+
activeBg: c.surfaceNeutralSoft,
|
|
82
|
+
// Selected segment thumb (HeroUI `segment`): lifts off the track.
|
|
83
|
+
segmentThumb: c.segment,
|
|
84
|
+
textMuted: c.muted,
|
|
85
|
+
textActive: c.onSurface,
|
|
86
|
+
logout: c.error,
|
|
87
|
+
isDark: dark,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
62
91
|
final Color bg;
|
|
63
92
|
final Color border;
|
|
64
93
|
final Color divider;
|
|
65
94
|
final Color activeBg;
|
|
95
|
+
final Color segmentThumb;
|
|
66
96
|
final Color textMuted;
|
|
67
97
|
final Color textActive;
|
|
68
98
|
final Color logout;
|
|
69
99
|
|
|
70
|
-
/// True when
|
|
71
|
-
///
|
|
72
|
-
///
|
|
73
|
-
|
|
74
|
-
bool get isDark => identical(this, dark) || bg == dark.bg;
|
|
100
|
+
/// True when the current theme is dark. Used by overlay widgets (tooltips,
|
|
101
|
+
/// popups) that cannot rely on Theme.of(overlayContext) since overlay
|
|
102
|
+
/// contexts may not inherit the app theme correctly.
|
|
103
|
+
final bool isDark;
|
|
75
104
|
}
|
|
76
105
|
|
|
77
106
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -94,20 +123,44 @@ class _NavItem {
|
|
|
94
123
|
required this.icon,
|
|
95
124
|
required this.label,
|
|
96
125
|
this.subItems = const [],
|
|
97
|
-
this.
|
|
126
|
+
this.trailingControls = false,
|
|
98
127
|
});
|
|
99
128
|
|
|
100
129
|
final String id;
|
|
101
130
|
final IconData icon;
|
|
102
131
|
final String label;
|
|
103
132
|
final List<String> subItems;
|
|
104
|
-
|
|
133
|
+
|
|
134
|
+
/// When true, the expanded row shows the lock + eye trailing controls
|
|
135
|
+
/// (matches the active "Object 2" layer item in the Figma reference).
|
|
136
|
+
final bool trailingControls;
|
|
105
137
|
|
|
106
138
|
bool get hasSubmenu => subItems.isNotEmpty;
|
|
107
139
|
}
|
|
108
140
|
|
|
109
141
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
110
|
-
//
|
|
142
|
+
// Showcase nav data (mirrors the HeroUI Figma layers list 1:1)
|
|
143
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
const List<_NavItem> _kShowcaseItems = [
|
|
146
|
+
_NavItem(id: 'camera1', icon: KasyIcons.settings, label: 'Camera 1'),
|
|
147
|
+
_NavItem(id: 'domelight', icon: KasyIcons.lightMode, label: 'Dome Light'),
|
|
148
|
+
_NavItem(id: 'keylight', icon: KasyIcons.idea, label: 'Key Light'),
|
|
149
|
+
_NavItem(id: 'arealight', icon: KasyIcons.widgets, label: 'Area Light'),
|
|
150
|
+
_NavItem(
|
|
151
|
+
id: 'object2',
|
|
152
|
+
icon: KasyIcons.packageOutline,
|
|
153
|
+
label: 'Object 2',
|
|
154
|
+
trailingControls: true,
|
|
155
|
+
),
|
|
156
|
+
_NavItem(id: 'bg2', icon: KasyIcons.packageOutline, label: 'Background 2'),
|
|
157
|
+
_NavItem(id: 'character', icon: KasyIcons.packageOutline, label: 'Character'),
|
|
158
|
+
_NavItem(id: 'bg1', icon: KasyIcons.packageOutline, label: 'Background 1'),
|
|
159
|
+
];
|
|
160
|
+
|
|
161
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
162
|
+
// Connected (real navigation) nav data — kept so the live app keeps working,
|
|
163
|
+
// including the Income submenu the host still relies on.
|
|
111
164
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
112
165
|
|
|
113
166
|
const List<_NavItem> _kMainItems = [
|
|
@@ -123,61 +176,93 @@ const List<_NavItem> _kMainItems = [
|
|
|
123
176
|
),
|
|
124
177
|
];
|
|
125
178
|
|
|
126
|
-
const _NavItem
|
|
127
|
-
id: '
|
|
128
|
-
icon: KasyIcons.
|
|
129
|
-
label: '
|
|
179
|
+
const _NavItem _kHelpItem = _NavItem(
|
|
180
|
+
id: 'help',
|
|
181
|
+
icon: KasyIcons.help,
|
|
182
|
+
label: 'Help',
|
|
130
183
|
);
|
|
131
184
|
|
|
132
|
-
const List<_NavItem> _kBottomItems = [
|
|
133
|
-
_NavItem(id: 'help', icon: KasyIcons.help, label: 'Help'),
|
|
134
|
-
_NavItem(
|
|
135
|
-
id: 'logout',
|
|
136
|
-
icon: KasyIcons.logout,
|
|
137
|
-
label: 'Logout Account',
|
|
138
|
-
isLogout: true,
|
|
139
|
-
),
|
|
140
|
-
];
|
|
141
|
-
|
|
142
185
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
143
|
-
//
|
|
186
|
+
// KasySidebar — public widget
|
|
144
187
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
145
188
|
|
|
146
189
|
/// Which screen edge the sidebar is anchored to.
|
|
147
190
|
///
|
|
148
|
-
/// Controls which
|
|
149
|
-
|
|
150
|
-
enum KasySidebarProSide { left, right }
|
|
191
|
+
/// Controls which edge receives the content-facing hairline + shadow nudge.
|
|
192
|
+
enum KasySidebarSide { left, right }
|
|
151
193
|
|
|
152
|
-
/// A
|
|
153
|
-
///
|
|
194
|
+
/// A SaaS-style sidebar modelled on the HeroUI Figma kit: brand logo + panel
|
|
195
|
+
/// toggle, a workspace selector, a segmented control, a navigable list with an
|
|
196
|
+
/// active pill, and a pinned ⌘K search row. Collapses to an icon rail (with
|
|
197
|
+
/// tooltips and a hover submenu popup) on narrow viewports or via the toggle.
|
|
154
198
|
///
|
|
155
199
|
/// Pass [onSettingsTap] to respond when the user taps Settings.
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const KasySidebarPro({
|
|
200
|
+
class KasySidebar extends StatefulWidget {
|
|
201
|
+
const KasySidebar({
|
|
159
202
|
super.key,
|
|
160
203
|
this.onSettingsTap,
|
|
204
|
+
this.onLogout,
|
|
161
205
|
this.initiallyCollapsed = false,
|
|
162
|
-
this.side =
|
|
206
|
+
this.side = KasySidebarSide.left,
|
|
207
|
+
this.routes,
|
|
208
|
+
this.onTapItem,
|
|
209
|
+
this.currentItem,
|
|
210
|
+
this.showProfile = true,
|
|
211
|
+
this.profileName = 'Calvin Rice',
|
|
212
|
+
this.profileEmail = 'calvin@email.com',
|
|
213
|
+
this.profileAvatar,
|
|
214
|
+
this.profileGradient = KasyAvatarGradients.indigo,
|
|
215
|
+
this.onProfileTap,
|
|
163
216
|
});
|
|
164
217
|
|
|
165
218
|
final VoidCallback? onSettingsTap;
|
|
166
219
|
|
|
220
|
+
/// Whether the profile block is shown at the bottom of the rail. Set false to
|
|
221
|
+
/// drop it entirely (e.g. when the web header already carries the avatar).
|
|
222
|
+
final bool showProfile;
|
|
223
|
+
|
|
224
|
+
/// Profile block (bottom of the rail) — display name + email.
|
|
225
|
+
final String profileName;
|
|
226
|
+
final String profileEmail;
|
|
227
|
+
|
|
228
|
+
/// Custom avatar widget for the profile block — pass the signed-in user's
|
|
229
|
+
/// avatar (e.g. `KasyUserAvatar`) to show their real photo. When null, a
|
|
230
|
+
/// gradient-fill avatar ([profileGradient]) is shown instead.
|
|
231
|
+
final Widget? profileAvatar;
|
|
232
|
+
|
|
233
|
+
/// Gradient used for the profile avatar when no [profileImage] is given.
|
|
234
|
+
final KasyAvatarGradientData profileGradient;
|
|
235
|
+
|
|
236
|
+
/// Tap on the profile block (open account menu / profile).
|
|
237
|
+
final VoidCallback? onProfileTap;
|
|
238
|
+
|
|
239
|
+
/// Called when the user taps the Logout row in connected mode. The component
|
|
240
|
+
/// is purely presentational — the host (feature) owns the actual logout flow
|
|
241
|
+
/// (confirm dialog + sign-out). When null, the Logout row does nothing real.
|
|
242
|
+
final VoidCallback? onLogout;
|
|
243
|
+
|
|
167
244
|
/// Whether the sidebar starts in the narrow (icon-only) mode.
|
|
168
245
|
final bool initiallyCollapsed;
|
|
169
246
|
|
|
170
247
|
/// The screen edge this sidebar is anchored to.
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
///
|
|
174
|
-
|
|
248
|
+
final KasySidebarSide side;
|
|
249
|
+
|
|
250
|
+
/// Bart bottom-bar routes to wire into the sidebar. When provided (along with
|
|
251
|
+
/// [onTapItem] and [currentItem]) the sidebar runs in "connected" mode with
|
|
252
|
+
/// real, navigable items. When null, it shows the HeroUI showcase items.
|
|
253
|
+
final List<BartMenuRoute>? routes;
|
|
254
|
+
|
|
255
|
+
/// Called with the route index when a real nav item is tapped.
|
|
256
|
+
final OnTapItem? onTapItem;
|
|
257
|
+
|
|
258
|
+
/// Bart's active-tab index notifier, used to highlight the current screen.
|
|
259
|
+
final ValueNotifier<int>? currentItem;
|
|
175
260
|
|
|
176
261
|
@override
|
|
177
|
-
State<
|
|
262
|
+
State<KasySidebar> createState() => _KasySidebarState();
|
|
178
263
|
}
|
|
179
264
|
|
|
180
|
-
class
|
|
265
|
+
class _KasySidebarState extends State<KasySidebar> {
|
|
181
266
|
// User's explicit open/close preference (set by tapping the toggle button).
|
|
182
267
|
late bool _userChoseCollapsed;
|
|
183
268
|
|
|
@@ -186,27 +271,35 @@ class _KasySidebarProState extends State<KasySidebarPro> {
|
|
|
186
271
|
bool _collapsed = false;
|
|
187
272
|
|
|
188
273
|
bool _incomeExpanded = false;
|
|
189
|
-
|
|
274
|
+
|
|
275
|
+
// Showcase state.
|
|
276
|
+
int _showcaseTab = 0; // 0 = Layers, 1 = Assets
|
|
277
|
+
late String _activeItemId;
|
|
190
278
|
String _activeSubItem = '';
|
|
191
279
|
|
|
192
280
|
/// Viewport width below which the sidebar auto-collapses (tablet breakpoint).
|
|
193
281
|
static const double _kBreakpoint = 1024.0;
|
|
194
282
|
|
|
283
|
+
/// True when wired to Bart's navigation (real, tappable screens).
|
|
284
|
+
bool get _connected =>
|
|
285
|
+
widget.routes != null &&
|
|
286
|
+
widget.routes!.length >= 2 &&
|
|
287
|
+
widget.onTapItem != null &&
|
|
288
|
+
widget.currentItem != null;
|
|
289
|
+
|
|
195
290
|
@override
|
|
196
291
|
void initState() {
|
|
197
292
|
super.initState();
|
|
198
293
|
_userChoseCollapsed = widget.initiallyCollapsed;
|
|
294
|
+
// Connected mode follows Bart's currentItem (empty highlight here); the
|
|
295
|
+
// showcase defaults to the active layer from the Figma reference.
|
|
296
|
+
_activeItemId = _connected ? '' : 'object2';
|
|
199
297
|
}
|
|
200
298
|
|
|
201
299
|
bool _isViewportNarrow(BuildContext context) =>
|
|
202
300
|
MediaQuery.sizeOf(context).width < _kBreakpoint;
|
|
203
301
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
_SidebarColors get _colors =>
|
|
207
|
-
Theme.of(context).brightness == Brightness.dark
|
|
208
|
-
? _SidebarColors.dark
|
|
209
|
-
: _SidebarColors.light;
|
|
302
|
+
_SidebarColors get _colors => _SidebarColors.fromContext(context);
|
|
210
303
|
|
|
211
304
|
// ── Actions ───────────────────────────────────────────────────────────────
|
|
212
305
|
|
|
@@ -214,6 +307,12 @@ class _KasySidebarProState extends State<KasySidebarPro> {
|
|
|
214
307
|
void _toggleCollapse() =>
|
|
215
308
|
setState(() => _userChoseCollapsed = !_userChoseCollapsed);
|
|
216
309
|
|
|
310
|
+
/// Navigates to a real route via Bart and clears any static-item highlight.
|
|
311
|
+
void _navigateTo(int index) {
|
|
312
|
+
setState(() => _activeItemId = '');
|
|
313
|
+
widget.onTapItem!(index);
|
|
314
|
+
}
|
|
315
|
+
|
|
217
316
|
void _activateItem(String id) {
|
|
218
317
|
if (id == 'settings') {
|
|
219
318
|
widget.onSettingsTap?.call();
|
|
@@ -222,127 +321,122 @@ class _KasySidebarProState extends State<KasySidebarPro> {
|
|
|
222
321
|
}
|
|
223
322
|
setState(() {
|
|
224
323
|
_activeItemId = id;
|
|
225
|
-
// Leaving Income → clear the active sub-item so it doesn't stay
|
|
226
|
-
// highlighted when the user is on a completely different page.
|
|
227
324
|
if (id != 'income') _activeSubItem = '';
|
|
228
325
|
});
|
|
229
326
|
}
|
|
230
327
|
|
|
231
328
|
void _activateSubItem(String label) => setState(() {
|
|
232
329
|
_activeSubItem = label;
|
|
233
|
-
_activeItemId = 'income';
|
|
330
|
+
_activeItemId = 'income';
|
|
234
331
|
});
|
|
235
332
|
|
|
236
333
|
// ── Build ─────────────────────────────────────────────────────────────────
|
|
237
334
|
|
|
238
335
|
@override
|
|
239
336
|
Widget build(BuildContext context) {
|
|
240
|
-
// Recompute effective collapsed state every build so viewport changes
|
|
241
|
-
// are picked up automatically without any extra listener.
|
|
242
337
|
_collapsed = _userChoseCollapsed || _isViewportNarrow(context);
|
|
243
338
|
|
|
244
339
|
final c = _colors;
|
|
340
|
+
final bool anchoredLeft = widget.side == KasySidebarSide.left;
|
|
341
|
+
|
|
342
|
+
// Hairline on the content-facing edge (Figma `separator`).
|
|
343
|
+
final Border edgeBorder = anchoredLeft
|
|
344
|
+
? Border(right: BorderSide(color: c.border, width: 0.5))
|
|
345
|
+
: Border(left: BorderSide(color: c.border, width: 0.5));
|
|
346
|
+
|
|
347
|
+
// Figma `shadow-surface`: a soft lift in light mode only (dark uses none).
|
|
348
|
+
final List<BoxShadow> shadow = c.isDark
|
|
349
|
+
? const <BoxShadow>[]
|
|
350
|
+
: const <BoxShadow>[
|
|
351
|
+
BoxShadow(
|
|
352
|
+
color: Color(0x14000000),
|
|
353
|
+
blurRadius: 4,
|
|
354
|
+
offset: Offset(0, 2),
|
|
355
|
+
),
|
|
356
|
+
BoxShadow(
|
|
357
|
+
color: Color(0x0F000000),
|
|
358
|
+
blurRadius: 2,
|
|
359
|
+
offset: Offset(0, 1),
|
|
360
|
+
),
|
|
361
|
+
];
|
|
245
362
|
|
|
246
|
-
final
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
// visually straddle the edge AND stay within parent bounds — Flutter only
|
|
254
|
-
// delivers hit tests to widgets inside their parent's bounds.
|
|
255
|
-
//
|
|
256
|
-
// Layout: left-anchored example
|
|
257
|
-
// ┌── outer (256+14) ─────────────────────────┐
|
|
258
|
-
// │ ┌── sidebar box (256) ──────────┐ │
|
|
259
|
-
// │ │ content │ [button] │
|
|
260
|
-
// │ └───────────────────────────────┘ │
|
|
261
|
-
// └────────────────────────────────────────────┘
|
|
262
|
-
//
|
|
263
|
-
// The 14px transparent strip on the right holds the outer half of the
|
|
264
|
-
// button, giving it a full 28×28 hittable area.
|
|
265
|
-
const double halfBtn = _kCollapseButtonSize / 2; // 14px
|
|
363
|
+
final Widget content = _connected
|
|
364
|
+
? ValueListenableBuilder<int>(
|
|
365
|
+
valueListenable: widget.currentItem!,
|
|
366
|
+
builder: (_, currentIndex, _) =>
|
|
367
|
+
_buildConnectedContent(context, c, currentIndex),
|
|
368
|
+
)
|
|
369
|
+
: _buildShowcaseContent(context, c);
|
|
266
370
|
|
|
267
371
|
return Material(
|
|
268
372
|
type: MaterialType.transparency,
|
|
269
373
|
child: AnimatedContainer(
|
|
270
374
|
duration: const Duration(milliseconds: 220),
|
|
271
375
|
curve: Curves.easeInOut,
|
|
272
|
-
width:
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
left: anchoredLeft ? 0 : halfBtn,
|
|
278
|
-
right: anchoredLeft ? halfBtn : 0,
|
|
279
|
-
top: 0,
|
|
280
|
-
bottom: 0,
|
|
281
|
-
child: DecoratedBox(
|
|
282
|
-
decoration: BoxDecoration(
|
|
283
|
-
color: c.bg,
|
|
284
|
-
border: border,
|
|
285
|
-
),
|
|
286
|
-
child: _buildScrollableContent(context, c),
|
|
287
|
-
),
|
|
288
|
-
),
|
|
289
|
-
// Collapse button — centered on the sidebar edge, fully within
|
|
290
|
-
// the outer container so hit tests work correctly.
|
|
291
|
-
_buildCollapseButton(context, c),
|
|
292
|
-
],
|
|
293
|
-
),
|
|
376
|
+
width: _collapsed ? _kWidthCollapsed : _kWidthOpen,
|
|
377
|
+
decoration: BoxDecoration(color: c.bg, boxShadow: shadow),
|
|
378
|
+
foregroundDecoration: BoxDecoration(border: edgeBorder),
|
|
379
|
+
clipBehavior: Clip.hardEdge,
|
|
380
|
+
child: content,
|
|
294
381
|
),
|
|
295
382
|
);
|
|
296
383
|
}
|
|
297
384
|
|
|
298
|
-
// ──
|
|
385
|
+
// ── Showcase layout (HeroUI Figma 1:1) ──────────────────────────────────────
|
|
299
386
|
|
|
300
|
-
Widget
|
|
301
|
-
// Use a Column that fills the sidebar height.
|
|
302
|
-
// Top section scrolls if content overflows; bottom items (Help + Logout)
|
|
303
|
-
// stay pinned to the bottom via Spacer in the outer Column.
|
|
304
|
-
// SizedBox.expand (not Positioned.fill) because the parent here is a
|
|
305
|
-
// DecoratedBox, not a Stack — Positioned only works as a direct Stack child.
|
|
387
|
+
Widget _buildShowcaseContent(BuildContext context, _SidebarColors c) {
|
|
306
388
|
return SizedBox.expand(
|
|
307
389
|
child: Column(
|
|
308
|
-
crossAxisAlignment: CrossAxisAlignment.
|
|
390
|
+
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
309
391
|
children: [
|
|
310
|
-
//
|
|
392
|
+
// Logo band — 68px tall so the divider below aligns with the web
|
|
393
|
+
// header's bottom border (one continuous line across the chrome).
|
|
394
|
+
_buildTopBand(c),
|
|
395
|
+
_buildDivider(c),
|
|
396
|
+
// Nav: workspace selector + segmented tabs + the layers list.
|
|
311
397
|
Expanded(
|
|
312
|
-
child:
|
|
313
|
-
padding: const EdgeInsets.fromLTRB(
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
const SizedBox(height: _kPad),
|
|
329
|
-
_buildSectionLabel('SETTINGS', c),
|
|
330
|
-
const SizedBox(height: _kItemGap),
|
|
331
|
-
_buildNavItem(context, _kSettingsItem, c),
|
|
332
|
-
],
|
|
398
|
+
child: Padding(
|
|
399
|
+
padding: const EdgeInsets.fromLTRB(_kPadH, _kDividerGap, _kPadH, 0),
|
|
400
|
+
child: SingleChildScrollView(
|
|
401
|
+
child: Column(
|
|
402
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
403
|
+
children: [
|
|
404
|
+
if (!_collapsed) ...[
|
|
405
|
+
_buildWorkspaceSelector(c),
|
|
406
|
+
const SizedBox(height: _kHeaderGap),
|
|
407
|
+
_buildTabs(c),
|
|
408
|
+
const SizedBox(height: _kNavGap),
|
|
409
|
+
],
|
|
410
|
+
for (final item in _kShowcaseItems)
|
|
411
|
+
_buildNavItem(context, item, c),
|
|
412
|
+
],
|
|
413
|
+
),
|
|
333
414
|
),
|
|
334
415
|
),
|
|
335
416
|
),
|
|
336
|
-
//
|
|
417
|
+
// Pinned ⌘K search row + profile block.
|
|
418
|
+
_buildDivider(c),
|
|
337
419
|
Padding(
|
|
338
420
|
padding: const EdgeInsets.fromLTRB(
|
|
339
|
-
|
|
421
|
+
_kPadH,
|
|
422
|
+
_kFooterGap,
|
|
423
|
+
_kPadH,
|
|
424
|
+
_kPadBottom,
|
|
340
425
|
),
|
|
341
426
|
child: Column(
|
|
342
427
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
343
|
-
children:
|
|
344
|
-
|
|
345
|
-
|
|
428
|
+
children: [
|
|
429
|
+
_buildItemRow(
|
|
430
|
+
c,
|
|
431
|
+
icon: KasyIcons.search,
|
|
432
|
+
label: 'Search',
|
|
433
|
+
isActive: false,
|
|
434
|
+
onTap: () {},
|
|
435
|
+
bottomGap: 0,
|
|
436
|
+
trailing: [_buildKbd(c)],
|
|
437
|
+
),
|
|
438
|
+
if (widget.showProfile) _buildProfile(c),
|
|
439
|
+
],
|
|
346
440
|
),
|
|
347
441
|
),
|
|
348
442
|
],
|
|
@@ -350,161 +444,459 @@ class _KasySidebarProState extends State<KasySidebarPro> {
|
|
|
350
444
|
);
|
|
351
445
|
}
|
|
352
446
|
|
|
353
|
-
// ──
|
|
447
|
+
// ── Connected layout (real navigation) ──────────────────────────────────────
|
|
354
448
|
|
|
355
|
-
Widget
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
),
|
|
364
|
-
|
|
365
|
-
|
|
449
|
+
Widget _buildConnectedContent(
|
|
450
|
+
BuildContext context,
|
|
451
|
+
_SidebarColors c,
|
|
452
|
+
int currentIndex,
|
|
453
|
+
) {
|
|
454
|
+
final int settingsIndex = widget.routes!.length - 1;
|
|
455
|
+
final nav = context.t.navigation;
|
|
456
|
+
final List<({IconData icon, String label})> meta = [
|
|
457
|
+
(icon: KasyIcons.home, label: nav.home),
|
|
458
|
+
(icon: KasyIcons.help, label: nav.support),
|
|
459
|
+
(icon: KasyIcons.notification, label: nav.notifications),
|
|
460
|
+
];
|
|
461
|
+
final int mainCount = widget.routes!.length - 1; // exclude settings (last)
|
|
366
462
|
|
|
367
|
-
return
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
:
|
|
376
|
-
|
|
463
|
+
return SizedBox.expand(
|
|
464
|
+
child: Column(
|
|
465
|
+
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
466
|
+
children: [
|
|
467
|
+
_buildTopBand(c),
|
|
468
|
+
_buildDivider(c),
|
|
469
|
+
Expanded(
|
|
470
|
+
child: Padding(
|
|
471
|
+
padding: const EdgeInsets.fromLTRB(_kPadH, _kDividerGap, _kPadH, 0),
|
|
472
|
+
child: SingleChildScrollView(
|
|
473
|
+
child: Column(
|
|
474
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
377
475
|
children: [
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
),
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
Text(
|
|
395
|
-
'Andrew Smith',
|
|
396
|
-
style: TextStyle(
|
|
397
|
-
fontSize: 14,
|
|
398
|
-
fontWeight: FontWeight.w500,
|
|
399
|
-
color: c.textActive,
|
|
400
|
-
),
|
|
401
|
-
),
|
|
402
|
-
],
|
|
476
|
+
if (!_collapsed) ...[
|
|
477
|
+
_buildSectionLabel('MAIN', c),
|
|
478
|
+
const SizedBox(height: _kItemGap),
|
|
479
|
+
],
|
|
480
|
+
// Real, navigable screens.
|
|
481
|
+
for (int i = 0; i < mainCount; i++)
|
|
482
|
+
_buildItemRow(
|
|
483
|
+
c,
|
|
484
|
+
icon: i < meta.length
|
|
485
|
+
? meta[i].icon
|
|
486
|
+
: (widget.routes![i].icon ?? KasyIcons.note),
|
|
487
|
+
label: i < meta.length
|
|
488
|
+
? meta[i].label
|
|
489
|
+
: (widget.routes![i].label ?? ''),
|
|
490
|
+
isActive: _activeItemId.isEmpty && currentIndex == i,
|
|
491
|
+
onTap: () => _navigateTo(i),
|
|
403
492
|
),
|
|
493
|
+
// Static showcase extras (incl. the Income submenu).
|
|
494
|
+
for (final item in _kMainItems.skip(1))
|
|
495
|
+
_buildNavItem(context, item, c),
|
|
496
|
+
const SizedBox(height: _kDividerGap),
|
|
497
|
+
if (!_collapsed) ...[
|
|
498
|
+
_buildSectionLabel('SETTINGS', c),
|
|
499
|
+
const SizedBox(height: _kItemGap),
|
|
500
|
+
],
|
|
501
|
+
_buildItemRow(
|
|
502
|
+
c,
|
|
503
|
+
icon: KasyIcons.settings,
|
|
504
|
+
label: nav.settings,
|
|
505
|
+
isActive:
|
|
506
|
+
_activeItemId.isEmpty && currentIndex == settingsIndex,
|
|
507
|
+
onTap: () => _navigateTo(settingsIndex),
|
|
404
508
|
),
|
|
405
509
|
],
|
|
406
510
|
),
|
|
511
|
+
),
|
|
512
|
+
),
|
|
513
|
+
),
|
|
514
|
+
_buildDivider(c),
|
|
515
|
+
const SizedBox(height: _kFooterGap),
|
|
516
|
+
Padding(
|
|
517
|
+
padding: const EdgeInsets.fromLTRB(_kPadH, 0, _kPadH, _kPadBottom),
|
|
518
|
+
child: Column(
|
|
519
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
520
|
+
children: [
|
|
521
|
+
_buildNavItem(context, _kHelpItem, c),
|
|
522
|
+
_buildItemRow(
|
|
523
|
+
c,
|
|
524
|
+
icon: KasyIcons.logout,
|
|
525
|
+
label: nav.logout,
|
|
526
|
+
isActive: false,
|
|
527
|
+
isLogout: true,
|
|
528
|
+
bottomGap: 0,
|
|
529
|
+
onTap: () => widget.onLogout?.call(),
|
|
530
|
+
),
|
|
531
|
+
if (widget.showProfile) _buildProfile(c),
|
|
532
|
+
],
|
|
533
|
+
),
|
|
534
|
+
),
|
|
535
|
+
],
|
|
536
|
+
),
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// ── Top band (logo + panel toggle) ──────────────────────────────────────────
|
|
541
|
+
|
|
542
|
+
/// The brand band at the top of the rail. Fixed to [_kTopBandHeight] (= web
|
|
543
|
+
/// header height) so the divider underneath lines up with the header's bottom
|
|
544
|
+
/// border. Content is vertically centred, mirroring the header's toolbar row.
|
|
545
|
+
Widget _buildTopBand(_SidebarColors c) {
|
|
546
|
+
return Padding(
|
|
547
|
+
padding: const EdgeInsets.symmetric(horizontal: _kPadH),
|
|
548
|
+
child: SizedBox(
|
|
549
|
+
height: _kTopBandHeight,
|
|
550
|
+
child: _collapsed
|
|
551
|
+
? Center(child: _buildToggleButton(c))
|
|
552
|
+
: Row(
|
|
553
|
+
children: [
|
|
554
|
+
// Brand wordmark — same artwork as the splash screen.
|
|
555
|
+
Image.asset(
|
|
556
|
+
c.isDark
|
|
557
|
+
? 'assets/images/logo_wordmark_dark.png'
|
|
558
|
+
: 'assets/images/logo_wordmark_light.png',
|
|
559
|
+
height: 32,
|
|
560
|
+
fit: BoxFit.contain,
|
|
561
|
+
),
|
|
562
|
+
const Spacer(),
|
|
563
|
+
_buildToggleButton(c),
|
|
564
|
+
],
|
|
565
|
+
),
|
|
566
|
+
),
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
Widget _buildToggleButton(_SidebarColors c) {
|
|
571
|
+
return KasyHover(
|
|
572
|
+
borderRadius: BorderRadius.circular(_kToggleSize / 2),
|
|
573
|
+
hoverColor: c.activeBg,
|
|
574
|
+
pressColor: c.textActive,
|
|
575
|
+
onTap: _toggleCollapse,
|
|
576
|
+
child: Container(
|
|
577
|
+
width: _kToggleSize,
|
|
578
|
+
height: _kToggleSize,
|
|
579
|
+
alignment: Alignment.center,
|
|
580
|
+
decoration: BoxDecoration(
|
|
581
|
+
borderRadius: BorderRadius.circular(_kToggleSize / 2),
|
|
582
|
+
),
|
|
583
|
+
child: Icon(KasyIcons.panelLeft, size: _kIconSize, color: c.textMuted),
|
|
584
|
+
),
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// ── Workspace selector ──────────────────────────────────────────────────────
|
|
589
|
+
|
|
590
|
+
Widget _buildWorkspaceSelector(_SidebarColors c) {
|
|
591
|
+
return Column(
|
|
592
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
593
|
+
mainAxisSize: MainAxisSize.min,
|
|
594
|
+
children: [
|
|
595
|
+
Row(
|
|
596
|
+
mainAxisSize: MainAxisSize.min,
|
|
597
|
+
children: [
|
|
598
|
+
Flexible(
|
|
599
|
+
child: Text(
|
|
600
|
+
'3D Dog Character',
|
|
601
|
+
maxLines: 1,
|
|
602
|
+
overflow: TextOverflow.ellipsis,
|
|
603
|
+
style: TextStyle(
|
|
604
|
+
fontSize: 14,
|
|
605
|
+
height: 20 / 14,
|
|
606
|
+
fontWeight: FontWeight.w500,
|
|
607
|
+
color: c.textActive,
|
|
608
|
+
),
|
|
609
|
+
),
|
|
610
|
+
),
|
|
611
|
+
const SizedBox(width: 16),
|
|
612
|
+
Icon(KasyIcons.chevronDown, size: _kIconSize, color: c.textMuted),
|
|
613
|
+
],
|
|
614
|
+
),
|
|
615
|
+
const SizedBox(height: 4),
|
|
616
|
+
Text(
|
|
617
|
+
'3D Design Project',
|
|
618
|
+
maxLines: 1,
|
|
619
|
+
overflow: TextOverflow.ellipsis,
|
|
620
|
+
style: TextStyle(
|
|
621
|
+
fontSize: 12,
|
|
622
|
+
height: 16 / 12,
|
|
623
|
+
fontWeight: FontWeight.w400,
|
|
624
|
+
color: c.textMuted,
|
|
625
|
+
),
|
|
407
626
|
),
|
|
408
627
|
],
|
|
409
628
|
);
|
|
410
629
|
}
|
|
411
630
|
|
|
412
|
-
// ──
|
|
631
|
+
// ── Segmented tabs (Layers / Assets) ────────────────────────────────────────
|
|
413
632
|
|
|
414
|
-
Widget
|
|
633
|
+
Widget _buildTabs(_SidebarColors c) {
|
|
415
634
|
return Container(
|
|
416
|
-
|
|
635
|
+
width: double.infinity,
|
|
636
|
+
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
417
637
|
decoration: BoxDecoration(
|
|
418
|
-
color: c.
|
|
419
|
-
borderRadius: BorderRadius.circular(
|
|
638
|
+
color: c.activeBg,
|
|
639
|
+
borderRadius: BorderRadius.circular(24),
|
|
640
|
+
),
|
|
641
|
+
child: Row(
|
|
642
|
+
children: [
|
|
643
|
+
_buildTab(c, 'Layers', 0),
|
|
644
|
+
const SizedBox(width: 2),
|
|
645
|
+
_buildTab(c, 'Assets', 1),
|
|
646
|
+
],
|
|
420
647
|
),
|
|
421
648
|
);
|
|
422
649
|
}
|
|
423
650
|
|
|
424
|
-
|
|
651
|
+
Widget _buildTab(_SidebarColors c, String label, int index) {
|
|
652
|
+
final bool selected = _showcaseTab == index;
|
|
653
|
+
return Expanded(
|
|
654
|
+
child: MouseRegion(
|
|
655
|
+
cursor: SystemMouseCursors.click,
|
|
656
|
+
child: GestureDetector(
|
|
657
|
+
behavior: HitTestBehavior.opaque,
|
|
658
|
+
onTap: () => setState(() => _showcaseTab = index),
|
|
659
|
+
child: AnimatedContainer(
|
|
660
|
+
duration: const Duration(milliseconds: 180),
|
|
661
|
+
curve: Curves.easeOut,
|
|
662
|
+
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
|
663
|
+
alignment: Alignment.center,
|
|
664
|
+
decoration: BoxDecoration(
|
|
665
|
+
color: selected ? c.segmentThumb : Colors.transparent,
|
|
666
|
+
borderRadius: BorderRadius.circular(24),
|
|
667
|
+
boxShadow: selected
|
|
668
|
+
? const [
|
|
669
|
+
BoxShadow(
|
|
670
|
+
color: Color(0x0F000000),
|
|
671
|
+
blurRadius: 8,
|
|
672
|
+
offset: Offset(0, 2),
|
|
673
|
+
),
|
|
674
|
+
]
|
|
675
|
+
: null,
|
|
676
|
+
),
|
|
677
|
+
child: Text(
|
|
678
|
+
label,
|
|
679
|
+
style: TextStyle(
|
|
680
|
+
fontSize: 14,
|
|
681
|
+
height: 20 / 14,
|
|
682
|
+
fontWeight: FontWeight.w500,
|
|
683
|
+
color: selected ? c.textActive : c.textMuted,
|
|
684
|
+
),
|
|
685
|
+
),
|
|
686
|
+
),
|
|
687
|
+
),
|
|
688
|
+
),
|
|
689
|
+
);
|
|
690
|
+
}
|
|
425
691
|
|
|
426
|
-
|
|
692
|
+
// ── ⌘K keyboard chip ─────────────────────────────────────────────────────────
|
|
693
|
+
|
|
694
|
+
Widget _buildKbd(_SidebarColors c) {
|
|
427
695
|
final TextStyle style = TextStyle(
|
|
428
|
-
fontSize:
|
|
696
|
+
fontSize: 14,
|
|
697
|
+
height: 20 / 14,
|
|
429
698
|
fontWeight: FontWeight.w500,
|
|
430
699
|
color: c.textMuted,
|
|
431
|
-
letterSpacing: 0.4,
|
|
432
700
|
);
|
|
701
|
+
return Container(
|
|
702
|
+
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 1),
|
|
703
|
+
decoration: BoxDecoration(
|
|
704
|
+
color: c.activeBg,
|
|
705
|
+
borderRadius: BorderRadius.circular(8),
|
|
706
|
+
),
|
|
707
|
+
child: Row(
|
|
708
|
+
mainAxisSize: MainAxisSize.min,
|
|
709
|
+
children: [
|
|
710
|
+
Text('⌘', style: style),
|
|
711
|
+
const SizedBox(width: 2),
|
|
712
|
+
Text('K', style: style),
|
|
713
|
+
],
|
|
714
|
+
),
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// ── Profile block (bottom) ──────────────────────────────────────────────────
|
|
719
|
+
|
|
720
|
+
/// Account row pinned at the bottom: the [KasySidebar.profileAvatar] (the
|
|
721
|
+
/// signed-in user's photo) when provided, otherwise a gradient-fill avatar +
|
|
722
|
+
/// name + email. Collapses to just the avatar on the narrow rail.
|
|
723
|
+
Widget _buildProfile(_SidebarColors c) {
|
|
724
|
+
final Widget avatar =
|
|
725
|
+
widget.profileAvatar ??
|
|
726
|
+
KasyAvatar(diameter: 36, backgroundGradient: widget.profileGradient);
|
|
433
727
|
|
|
434
728
|
if (_collapsed) {
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
child:
|
|
438
|
-
label,
|
|
439
|
-
style: style,
|
|
440
|
-
overflow: TextOverflow.clip,
|
|
441
|
-
softWrap: false,
|
|
442
|
-
textAlign: TextAlign.center,
|
|
443
|
-
),
|
|
729
|
+
return Padding(
|
|
730
|
+
padding: const EdgeInsets.only(top: _kItemGap),
|
|
731
|
+
child: Center(child: avatar),
|
|
444
732
|
);
|
|
445
733
|
}
|
|
446
734
|
|
|
735
|
+
return Padding(
|
|
736
|
+
padding: const EdgeInsets.only(top: _kItemGap),
|
|
737
|
+
child: KasyHover(
|
|
738
|
+
borderRadius: BorderRadius.circular(_kItemRadius),
|
|
739
|
+
hoverColor: c.activeBg,
|
|
740
|
+
pressColor: c.textActive,
|
|
741
|
+
onTap: widget.onProfileTap ?? () {},
|
|
742
|
+
child: Padding(
|
|
743
|
+
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8),
|
|
744
|
+
child: Row(
|
|
745
|
+
children: [
|
|
746
|
+
avatar,
|
|
747
|
+
const SizedBox(width: _kIconGap),
|
|
748
|
+
Expanded(
|
|
749
|
+
child: Column(
|
|
750
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
751
|
+
mainAxisSize: MainAxisSize.min,
|
|
752
|
+
children: [
|
|
753
|
+
Text(
|
|
754
|
+
widget.profileName,
|
|
755
|
+
maxLines: 1,
|
|
756
|
+
overflow: TextOverflow.ellipsis,
|
|
757
|
+
style: TextStyle(
|
|
758
|
+
fontSize: 14,
|
|
759
|
+
height: 20 / 14,
|
|
760
|
+
fontWeight: FontWeight.w500,
|
|
761
|
+
color: c.textActive,
|
|
762
|
+
),
|
|
763
|
+
),
|
|
764
|
+
Text(
|
|
765
|
+
widget.profileEmail,
|
|
766
|
+
maxLines: 1,
|
|
767
|
+
overflow: TextOverflow.ellipsis,
|
|
768
|
+
style: TextStyle(
|
|
769
|
+
fontSize: 12,
|
|
770
|
+
height: 16 / 12,
|
|
771
|
+
fontWeight: FontWeight.w500,
|
|
772
|
+
color: c.textMuted,
|
|
773
|
+
),
|
|
774
|
+
),
|
|
775
|
+
],
|
|
776
|
+
),
|
|
777
|
+
),
|
|
778
|
+
],
|
|
779
|
+
),
|
|
780
|
+
),
|
|
781
|
+
),
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// ── Divider ───────────────────────────────────────────────────────────────
|
|
786
|
+
|
|
787
|
+
// 0.5px hairline — same width + colour as the vertical edge line and the web
|
|
788
|
+
// header's bottom border, so all the chrome lines read identically.
|
|
789
|
+
Widget _buildDivider(_SidebarColors c) =>
|
|
790
|
+
Container(height: 0.5, color: c.divider);
|
|
791
|
+
|
|
792
|
+
// ── Section label ─────────────────────────────────────────────────────────
|
|
793
|
+
|
|
794
|
+
Widget _buildSectionLabel(String label, _SidebarColors c) {
|
|
447
795
|
return Padding(
|
|
448
796
|
padding: const EdgeInsets.only(left: _kItemHPad),
|
|
449
|
-
child: Text(
|
|
797
|
+
child: Text(
|
|
798
|
+
label,
|
|
799
|
+
style: TextStyle(
|
|
800
|
+
fontSize: 11,
|
|
801
|
+
fontWeight: FontWeight.w600,
|
|
802
|
+
color: c.textMuted,
|
|
803
|
+
letterSpacing: 0.6,
|
|
804
|
+
),
|
|
805
|
+
),
|
|
450
806
|
);
|
|
451
807
|
}
|
|
452
808
|
|
|
453
|
-
// ── Nav item
|
|
809
|
+
// ── Nav item dispatch ───────────────────────────────────────────────────────
|
|
454
810
|
|
|
455
|
-
Widget _buildNavItem(
|
|
456
|
-
BuildContext context,
|
|
457
|
-
_NavItem item,
|
|
458
|
-
_SidebarColors c,
|
|
459
|
-
) {
|
|
811
|
+
Widget _buildNavItem(BuildContext context, _NavItem item, _SidebarColors c) {
|
|
460
812
|
if (item.hasSubmenu) {
|
|
461
813
|
return _buildDropdownItem(context, item, c);
|
|
462
814
|
}
|
|
463
|
-
|
|
464
815
|
final bool isActive = _activeItemId == item.id;
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
816
|
+
return _buildItemRow(
|
|
817
|
+
c,
|
|
818
|
+
icon: item.icon,
|
|
819
|
+
label: item.label,
|
|
820
|
+
isActive: isActive,
|
|
821
|
+
onTap: () => _activateItem(item.id),
|
|
822
|
+
trailing: item.trailingControls
|
|
823
|
+
? [
|
|
824
|
+
Icon(KasyIcons.security, size: _kIconSize, color: c.textMuted),
|
|
825
|
+
const SizedBox(width: 12),
|
|
826
|
+
Icon(KasyIcons.eye, size: _kIconSize, color: c.textMuted),
|
|
827
|
+
]
|
|
828
|
+
: const [],
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// ── Generic row (expanded) / icon+tooltip (collapsed) ────────────────────────
|
|
833
|
+
|
|
834
|
+
Widget _buildItemRow(
|
|
835
|
+
_SidebarColors c, {
|
|
836
|
+
required IconData icon,
|
|
837
|
+
required String label,
|
|
838
|
+
required bool isActive,
|
|
839
|
+
required VoidCallback onTap,
|
|
840
|
+
bool isLogout = false,
|
|
841
|
+
List<Widget> trailing = const [],
|
|
842
|
+
double bottomGap = _kItemGap,
|
|
843
|
+
}) {
|
|
844
|
+
final Color fill = isActive ? c.activeBg : Colors.transparent;
|
|
845
|
+
final Color iconColor = isLogout
|
|
468
846
|
? c.logout
|
|
469
847
|
: (isActive ? c.textActive : c.textMuted);
|
|
470
|
-
final Color
|
|
848
|
+
final Color labelColor = isLogout ? c.logout : c.textActive;
|
|
471
849
|
|
|
472
|
-
// Collapsed: icon only, optionally with hover popup for sub-items
|
|
473
850
|
if (_collapsed) {
|
|
474
|
-
return
|
|
851
|
+
return Padding(
|
|
852
|
+
padding: EdgeInsets.only(bottom: bottomGap),
|
|
853
|
+
child: _ProTooltipIcon(
|
|
854
|
+
icon: icon,
|
|
855
|
+
label: label,
|
|
856
|
+
iconBg: fill,
|
|
857
|
+
iconColor: iconColor,
|
|
858
|
+
activeBg: c.activeBg,
|
|
859
|
+
colors: c,
|
|
860
|
+
onTap: onTap,
|
|
861
|
+
),
|
|
862
|
+
);
|
|
475
863
|
}
|
|
476
864
|
|
|
477
865
|
return Padding(
|
|
478
|
-
padding:
|
|
866
|
+
padding: EdgeInsets.only(bottom: bottomGap),
|
|
479
867
|
child: KasyHover(
|
|
480
868
|
borderRadius: BorderRadius.circular(_kItemRadius),
|
|
481
869
|
hoverColor: c.activeBg,
|
|
482
|
-
pressColor: c.
|
|
483
|
-
onTap:
|
|
870
|
+
pressColor: c.textActive,
|
|
871
|
+
onTap: onTap,
|
|
484
872
|
child: Container(
|
|
873
|
+
constraints: const BoxConstraints(minHeight: _kItemMinH),
|
|
485
874
|
padding: const EdgeInsets.symmetric(
|
|
486
875
|
horizontal: _kItemHPad,
|
|
487
876
|
vertical: _kItemVPad,
|
|
488
877
|
),
|
|
489
878
|
decoration: BoxDecoration(
|
|
490
|
-
color:
|
|
879
|
+
color: fill,
|
|
491
880
|
borderRadius: BorderRadius.circular(_kItemRadius),
|
|
492
881
|
),
|
|
493
882
|
child: Row(
|
|
494
883
|
children: [
|
|
495
|
-
|
|
496
|
-
const SizedBox(width:
|
|
884
|
+
Icon(icon, size: _kIconSize, color: iconColor),
|
|
885
|
+
const SizedBox(width: _kIconGap),
|
|
497
886
|
Expanded(
|
|
498
887
|
child: Text(
|
|
499
|
-
|
|
888
|
+
label,
|
|
889
|
+
maxLines: 1,
|
|
890
|
+
overflow: TextOverflow.ellipsis,
|
|
500
891
|
style: TextStyle(
|
|
501
892
|
fontSize: 14,
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
893
|
+
height: 20 / 14,
|
|
894
|
+
fontWeight: FontWeight.w500,
|
|
895
|
+
color: labelColor,
|
|
505
896
|
),
|
|
506
897
|
),
|
|
507
898
|
),
|
|
899
|
+
...trailing,
|
|
508
900
|
],
|
|
509
901
|
),
|
|
510
902
|
),
|
|
@@ -512,30 +904,7 @@ class _KasySidebarProState extends State<KasySidebarPro> {
|
|
|
512
904
|
);
|
|
513
905
|
}
|
|
514
906
|
|
|
515
|
-
// ──
|
|
516
|
-
|
|
517
|
-
Widget _buildCollapsedIcon(
|
|
518
|
-
BuildContext context,
|
|
519
|
-
_NavItem item,
|
|
520
|
-
Color bg,
|
|
521
|
-
Color iconColor,
|
|
522
|
-
_SidebarColors c,
|
|
523
|
-
) {
|
|
524
|
-
return Padding(
|
|
525
|
-
padding: const EdgeInsets.only(bottom: _kItemGap),
|
|
526
|
-
child: _ProTooltipIcon(
|
|
527
|
-
icon: item.icon,
|
|
528
|
-
label: item.label,
|
|
529
|
-
iconBg: bg,
|
|
530
|
-
iconColor: iconColor,
|
|
531
|
-
activeBg: c.activeBg,
|
|
532
|
-
colors: c,
|
|
533
|
-
onTap: () => _activateItem(item.id),
|
|
534
|
-
),
|
|
535
|
-
);
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
// ── Dropdown item (Income) ────────────────────────────────────────────────
|
|
907
|
+
// ── Dropdown item (submenu — used by connected Income) ───────────────────────
|
|
539
908
|
|
|
540
909
|
Widget _buildDropdownItem(
|
|
541
910
|
BuildContext context,
|
|
@@ -547,7 +916,6 @@ class _KasySidebarProState extends State<KasySidebarPro> {
|
|
|
547
916
|
final Color iconColor = isActive ? c.textActive : c.textMuted;
|
|
548
917
|
|
|
549
918
|
if (_collapsed) {
|
|
550
|
-
// In collapsed mode: icon that shows a floating popup on hover
|
|
551
919
|
return Padding(
|
|
552
920
|
padding: const EdgeInsets.only(bottom: _kItemGap),
|
|
553
921
|
child: _ProHoverPopupIcon(
|
|
@@ -565,53 +933,49 @@ class _KasySidebarProState extends State<KasySidebarPro> {
|
|
|
565
933
|
return Column(
|
|
566
934
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
567
935
|
children: [
|
|
568
|
-
// Header row — no bottom padding here; the SizedBox at the bottom of
|
|
569
|
-
// this Column provides the single trailing gap (same as other items).
|
|
570
936
|
KasyHover(
|
|
937
|
+
borderRadius: BorderRadius.circular(_kItemRadius),
|
|
938
|
+
hoverColor: c.activeBg,
|
|
939
|
+
pressColor: c.textActive,
|
|
940
|
+
onTap: () => _activateItem(item.id),
|
|
941
|
+
child: Container(
|
|
942
|
+
constraints: const BoxConstraints(minHeight: _kItemMinH),
|
|
943
|
+
padding: const EdgeInsets.symmetric(
|
|
944
|
+
horizontal: _kItemHPad,
|
|
945
|
+
vertical: _kItemVPad,
|
|
946
|
+
),
|
|
947
|
+
decoration: BoxDecoration(
|
|
948
|
+
color: bg,
|
|
571
949
|
borderRadius: BorderRadius.circular(_kItemRadius),
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
children: [
|
|
586
|
-
_ProIcon(icon: item.icon, color: iconColor, active: isActive),
|
|
587
|
-
const SizedBox(width: 12),
|
|
588
|
-
Expanded(
|
|
589
|
-
child: Text(
|
|
590
|
-
item.label,
|
|
591
|
-
style: TextStyle(
|
|
592
|
-
fontSize: 14,
|
|
593
|
-
fontWeight: isActive
|
|
594
|
-
? FontWeight.w600
|
|
595
|
-
: FontWeight.w500,
|
|
596
|
-
color: iconColor,
|
|
597
|
-
letterSpacing: -0.28,
|
|
598
|
-
),
|
|
599
|
-
),
|
|
600
|
-
),
|
|
601
|
-
AnimatedRotation(
|
|
602
|
-
turns: _incomeExpanded ? 0.5 : 0,
|
|
603
|
-
duration: const Duration(milliseconds: 200),
|
|
604
|
-
child: Icon(
|
|
605
|
-
KasyIcons.chevronDown,
|
|
606
|
-
size: 16,
|
|
607
|
-
color: iconColor,
|
|
608
|
-
),
|
|
950
|
+
),
|
|
951
|
+
child: Row(
|
|
952
|
+
children: [
|
|
953
|
+
Icon(item.icon, size: _kIconSize, color: iconColor),
|
|
954
|
+
const SizedBox(width: _kIconGap),
|
|
955
|
+
Expanded(
|
|
956
|
+
child: Text(
|
|
957
|
+
item.label,
|
|
958
|
+
style: TextStyle(
|
|
959
|
+
fontSize: 14,
|
|
960
|
+
height: 20 / 14,
|
|
961
|
+
fontWeight: FontWeight.w500,
|
|
962
|
+
color: c.textActive,
|
|
609
963
|
),
|
|
610
|
-
|
|
964
|
+
),
|
|
611
965
|
),
|
|
612
|
-
|
|
966
|
+
AnimatedRotation(
|
|
967
|
+
turns: _incomeExpanded ? 0.5 : 0,
|
|
968
|
+
duration: const Duration(milliseconds: 200),
|
|
969
|
+
child: Icon(
|
|
970
|
+
KasyIcons.chevronDown,
|
|
971
|
+
size: _kIconSize,
|
|
972
|
+
color: iconColor,
|
|
973
|
+
),
|
|
974
|
+
),
|
|
975
|
+
],
|
|
976
|
+
),
|
|
977
|
+
),
|
|
613
978
|
),
|
|
614
|
-
// Expandable sub-items tree
|
|
615
979
|
AnimatedCrossFade(
|
|
616
980
|
duration: const Duration(milliseconds: 200),
|
|
617
981
|
crossFadeState: _incomeExpanded
|
|
@@ -628,9 +992,7 @@ class _KasySidebarProState extends State<KasySidebarPro> {
|
|
|
628
992
|
// ── Sub-items tree ────────────────────────────────────────────────────────
|
|
629
993
|
|
|
630
994
|
Widget _buildSubItemsTree(_SidebarColors c) {
|
|
631
|
-
final subItems = _kMainItems
|
|
632
|
-
.firstWhere((i) => i.id == 'income')
|
|
633
|
-
.subItems;
|
|
995
|
+
final subItems = _kMainItems.firstWhere((i) => i.id == 'income').subItems;
|
|
634
996
|
final double lineH = _treeLineHeight(subItems.length);
|
|
635
997
|
|
|
636
998
|
return Padding(
|
|
@@ -640,12 +1002,11 @@ class _KasySidebarProState extends State<KasySidebarPro> {
|
|
|
640
1002
|
child: Stack(
|
|
641
1003
|
clipBehavior: Clip.none,
|
|
642
1004
|
children: [
|
|
643
|
-
// Vertical tree line
|
|
644
1005
|
Positioned(
|
|
645
1006
|
left: -_kTreeConnectorW,
|
|
646
1007
|
top: 0,
|
|
647
1008
|
child: Container(
|
|
648
|
-
width:
|
|
1009
|
+
width: 1.5,
|
|
649
1010
|
height: lineH,
|
|
650
1011
|
decoration: BoxDecoration(
|
|
651
1012
|
color: c.divider,
|
|
@@ -653,7 +1014,6 @@ class _KasySidebarProState extends State<KasySidebarPro> {
|
|
|
653
1014
|
),
|
|
654
1015
|
),
|
|
655
1016
|
),
|
|
656
|
-
// Sub-items
|
|
657
1017
|
Column(
|
|
658
1018
|
children: subItems.asMap().entries.map((entry) {
|
|
659
1019
|
final i = entry.key;
|
|
@@ -670,9 +1030,6 @@ class _KasySidebarProState extends State<KasySidebarPro> {
|
|
|
670
1030
|
|
|
671
1031
|
Widget _buildSubItem(String label, bool isLast, _SidebarColors c) {
|
|
672
1032
|
final bool isActive = _activeSubItem == label;
|
|
673
|
-
// Sub-items: active state = bolder text only, no background fill.
|
|
674
|
-
// Hover still shows the fill via KasyHover.hoverColor for interactivity.
|
|
675
|
-
const Color bg = Colors.transparent;
|
|
676
1033
|
final Color textColor = isActive ? c.textActive : c.textMuted;
|
|
677
1034
|
|
|
678
1035
|
return Padding(
|
|
@@ -680,7 +1037,6 @@ class _KasySidebarProState extends State<KasySidebarPro> {
|
|
|
680
1037
|
child: Stack(
|
|
681
1038
|
clipBehavior: Clip.none,
|
|
682
1039
|
children: [
|
|
683
|
-
// L-connector: bottom-left rounded border
|
|
684
1040
|
Positioned(
|
|
685
1041
|
left: -_kTreeConnectorW,
|
|
686
1042
|
top: _kSubItemH / 2 - 4,
|
|
@@ -689,8 +1045,8 @@ class _KasySidebarProState extends State<KasySidebarPro> {
|
|
|
689
1045
|
height: 8,
|
|
690
1046
|
decoration: BoxDecoration(
|
|
691
1047
|
border: Border(
|
|
692
|
-
left: BorderSide(color: c.divider, width:
|
|
693
|
-
bottom: BorderSide(color: c.divider, width:
|
|
1048
|
+
left: BorderSide(color: c.divider, width: 1.5),
|
|
1049
|
+
bottom: BorderSide(color: c.divider, width: 1.5),
|
|
694
1050
|
),
|
|
695
1051
|
borderRadius: const BorderRadius.only(
|
|
696
1052
|
bottomLeft: Radius.circular(8),
|
|
@@ -698,11 +1054,10 @@ class _KasySidebarProState extends State<KasySidebarPro> {
|
|
|
698
1054
|
),
|
|
699
1055
|
),
|
|
700
1056
|
),
|
|
701
|
-
// Item row
|
|
702
1057
|
KasyHover(
|
|
703
1058
|
borderRadius: BorderRadius.circular(_kItemRadius),
|
|
704
1059
|
hoverColor: c.activeBg,
|
|
705
|
-
pressColor: c.
|
|
1060
|
+
pressColor: c.textActive,
|
|
706
1061
|
onTap: () => _activateSubItem(label),
|
|
707
1062
|
child: Container(
|
|
708
1063
|
height: _kSubItemH,
|
|
@@ -711,7 +1066,7 @@ class _KasySidebarProState extends State<KasySidebarPro> {
|
|
|
711
1066
|
vertical: 8,
|
|
712
1067
|
),
|
|
713
1068
|
decoration: BoxDecoration(
|
|
714
|
-
color:
|
|
1069
|
+
color: Colors.transparent,
|
|
715
1070
|
borderRadius: BorderRadius.circular(_kItemRadius),
|
|
716
1071
|
),
|
|
717
1072
|
child: Align(
|
|
@@ -732,95 +1087,6 @@ class _KasySidebarProState extends State<KasySidebarPro> {
|
|
|
732
1087
|
),
|
|
733
1088
|
);
|
|
734
1089
|
}
|
|
735
|
-
|
|
736
|
-
// ── Collapse toggle button ────────────────────────────────────────────────
|
|
737
|
-
|
|
738
|
-
Widget _buildCollapseButton(BuildContext context, _SidebarColors c) {
|
|
739
|
-
final bool anchoredLeft = widget.side == KasySidebarProSide.left;
|
|
740
|
-
|
|
741
|
-
// Left sidebar: button at the right edge; chevron ← when open, → when collapsed.
|
|
742
|
-
// Right sidebar: button at the left edge; chevron → when open, ← when collapsed.
|
|
743
|
-
// The button sits inside the AnimatedContainer bounds so it receives full
|
|
744
|
-
// hit tests (Flutter clips hit testing to the parent's bounds).
|
|
745
|
-
final double turns = anchoredLeft
|
|
746
|
-
? (_collapsed ? -0.25 : 0.25) // ← open, → collapsed
|
|
747
|
-
: (_collapsed ? 0.25 : -0.25); // → open, ← collapsed
|
|
748
|
-
|
|
749
|
-
return Positioned(
|
|
750
|
-
// The outer container is sidebar_width + halfBtn wide.
|
|
751
|
-
// right: 0 → button right edge at outer container right edge.
|
|
752
|
-
// Button spans outer_width-28 to outer_width, center = sidebar_width. ✓
|
|
753
|
-
// Same logic mirrored for right-anchored sidebar.
|
|
754
|
-
left: anchoredLeft ? null : 0,
|
|
755
|
-
right: anchoredLeft ? 0 : null,
|
|
756
|
-
top: 34,
|
|
757
|
-
child: GestureDetector(
|
|
758
|
-
onTap: _toggleCollapse,
|
|
759
|
-
child: AnimatedContainer(
|
|
760
|
-
duration: const Duration(milliseconds: 220),
|
|
761
|
-
width: _kCollapseButtonSize,
|
|
762
|
-
height: _kCollapseButtonSize,
|
|
763
|
-
decoration: BoxDecoration(
|
|
764
|
-
color: c.bg,
|
|
765
|
-
border: Border.all(color: c.divider),
|
|
766
|
-
borderRadius: BorderRadius.circular(8),
|
|
767
|
-
boxShadow: [
|
|
768
|
-
BoxShadow(
|
|
769
|
-
color: const Color(0xFF000000).withValues(alpha: 0.06),
|
|
770
|
-
blurRadius: 4,
|
|
771
|
-
offset: const Offset(0, 1),
|
|
772
|
-
),
|
|
773
|
-
],
|
|
774
|
-
),
|
|
775
|
-
child: Center(
|
|
776
|
-
child: AnimatedRotation(
|
|
777
|
-
turns: turns,
|
|
778
|
-
duration: const Duration(milliseconds: 220),
|
|
779
|
-
child: Icon(
|
|
780
|
-
KasyIcons.chevronDown,
|
|
781
|
-
size: 16,
|
|
782
|
-
color: c.textMuted,
|
|
783
|
-
),
|
|
784
|
-
),
|
|
785
|
-
),
|
|
786
|
-
),
|
|
787
|
-
),
|
|
788
|
-
);
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
793
|
-
// _ProIcon — nav icon with subtle shadow weight when active
|
|
794
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
795
|
-
|
|
796
|
-
/// Renders a nav icon at 20px. When [active], adds a soft shadow using the
|
|
797
|
-
/// same [color] to simulate a slightly heavier stroke — consistent with the
|
|
798
|
-
/// bolder text weight used on selected items.
|
|
799
|
-
class _ProIcon extends StatelessWidget {
|
|
800
|
-
const _ProIcon({
|
|
801
|
-
required this.icon,
|
|
802
|
-
required this.color,
|
|
803
|
-
required this.active,
|
|
804
|
-
});
|
|
805
|
-
|
|
806
|
-
final IconData icon;
|
|
807
|
-
final Color color;
|
|
808
|
-
final bool active;
|
|
809
|
-
|
|
810
|
-
@override
|
|
811
|
-
Widget build(BuildContext context) {
|
|
812
|
-
return Icon(
|
|
813
|
-
icon,
|
|
814
|
-
size: 20,
|
|
815
|
-
color: color,
|
|
816
|
-
shadows: active
|
|
817
|
-
? [
|
|
818
|
-
Shadow(color: color.withValues(alpha: 0.55), blurRadius: 0.6),
|
|
819
|
-
Shadow(color: color.withValues(alpha: 0.35), blurRadius: 1.4),
|
|
820
|
-
]
|
|
821
|
-
: null,
|
|
822
|
-
);
|
|
823
|
-
}
|
|
824
1090
|
}
|
|
825
1091
|
|
|
826
1092
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -1069,12 +1335,11 @@ class _TooltipCard extends StatelessWidget {
|
|
|
1069
1335
|
@override
|
|
1070
1336
|
Widget build(BuildContext context) {
|
|
1071
1337
|
// Use colors.isDark instead of Theme.of(context) — the overlay context
|
|
1072
|
-
// does not always inherit the app theme correctly.
|
|
1073
|
-
//
|
|
1074
|
-
//
|
|
1075
|
-
final Color bg = colors.isDark ? colors.divider :
|
|
1076
|
-
final Color textColor =
|
|
1077
|
-
colors.isDark ? const Color(0xCCFFFFFF) : Colors.white;
|
|
1338
|
+
// does not always inherit the app theme correctly. Colors come from the
|
|
1339
|
+
// global theme: an inverted tooltip in light mode (dark surface / light
|
|
1340
|
+
// text — Figma spec / industry standard), neutral surface in dark mode.
|
|
1341
|
+
final Color bg = colors.isDark ? colors.divider : colors.textActive;
|
|
1342
|
+
final Color textColor = colors.isDark ? colors.textActive : colors.bg;
|
|
1078
1343
|
|
|
1079
1344
|
return Material(
|
|
1080
1345
|
color: Colors.transparent,
|
|
@@ -1128,7 +1393,9 @@ class _TooltipArrowPainter extends CustomPainter {
|
|
|
1128
1393
|
|
|
1129
1394
|
@override
|
|
1130
1395
|
void paint(Canvas canvas, Size size) {
|
|
1131
|
-
final paint = Paint()
|
|
1396
|
+
final paint = Paint()
|
|
1397
|
+
..color = color
|
|
1398
|
+
..style = PaintingStyle.fill;
|
|
1132
1399
|
final path = Path()
|
|
1133
1400
|
..moveTo(size.width, 0)
|
|
1134
1401
|
..lineTo(0, size.height / 2)
|