kasy-cli 1.34.0 → 1.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/README.md +1 -1
  2. package/bin/kasy.js +24 -2
  3. package/docs/cli-reference.md +7 -7
  4. package/lib/commands/new.js +11 -9
  5. package/lib/commands/release-version.js +234 -0
  6. package/lib/commands/update.js +27 -0
  7. package/lib/scaffold/CHANGELOG.json +18 -0
  8. package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api.dart +24 -0
  9. package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api_interface.dart +15 -0
  10. package/lib/scaffold/backends/api/patch/lib/features/authentication/repositories/authentication_repository.dart +27 -0
  11. package/lib/scaffold/backends/api/patch/lib/features/subscriptions/api/entities/subscription_entity.dart +1 -0
  12. package/lib/scaffold/backends/firebase/setup-from-scratch.js +35 -21
  13. package/lib/scaffold/backends/patch-base-hashes.json +66 -0
  14. package/lib/scaffold/backends/supabase/edge-functions/stripe-webhook/index.ts +2 -0
  15. package/lib/scaffold/backends/supabase/migrations/20240101000007_welcome_notification.sql +36 -23
  16. package/lib/scaffold/backends/supabase/migrations/20240101000014_subscription_trial_end.sql +6 -0
  17. package/lib/scaffold/backends/supabase/patch/lib/features/authentication/api/authentication_api.dart +82 -3
  18. package/lib/scaffold/backends/supabase/patch/lib/features/authentication/repositories/authentication_repository.dart +36 -0
  19. package/lib/scaffold/backends/supabase/patch/lib/features/subscriptions/api/entities/subscription_entity.dart +1 -0
  20. package/lib/scaffold/generate.js +53 -4
  21. package/lib/utils/i18n/messages-en.js +23 -0
  22. package/lib/utils/i18n/messages-es.js +23 -0
  23. package/lib/utils/i18n/messages-pt.js +23 -0
  24. package/package.json +5 -2
  25. package/templates/firebase/.firebase/hosting.YnVpbGQvd2Vi.cache +73 -0
  26. package/templates/firebase/AGENTS.md +83 -0
  27. package/templates/firebase/DESIGN_SYSTEM.md +37 -2
  28. package/templates/firebase/docs/auth-setup.en.md +2 -0
  29. package/templates/firebase/docs/auth-setup.es.md +2 -0
  30. package/templates/firebase/docs/auth-setup.pt.md +2 -0
  31. package/templates/firebase/firebase.json +56 -1
  32. package/templates/firebase/functions/src/authentication/triggers.ts +10 -6
  33. package/templates/firebase/functions/src/core/data/entities/subscription_entity.ts +5 -0
  34. package/templates/firebase/functions/src/core/data/entities/user_entity.ts +9 -1
  35. package/templates/firebase/functions/src/core/data/repositories/user_repository.ts +27 -0
  36. package/templates/firebase/functions/src/subscriptions/models/subscriptions.ts +3 -0
  37. package/templates/firebase/functions/src/subscriptions/stripe_functions.ts +4 -0
  38. package/templates/firebase/lib/components/kasy_alert.dart +0 -1
  39. package/templates/firebase/lib/components/kasy_app_bar.dart +31 -16
  40. package/templates/firebase/lib/components/kasy_bottom_sheet.dart +0 -1
  41. package/templates/firebase/lib/components/kasy_date_picker.dart +0 -5
  42. package/templates/firebase/lib/components/kasy_dialog.dart +0 -1
  43. package/templates/firebase/lib/components/kasy_otp_verification_bottom_sheet.dart +1 -1
  44. package/templates/firebase/lib/components/kasy_sidebar.dart +215 -178
  45. package/templates/firebase/lib/components/kasy_text_area.dart +0 -1
  46. package/templates/firebase/lib/components/kasy_text_field.dart +0 -1
  47. package/templates/firebase/lib/components/kasy_text_field_otp.dart +0 -1
  48. package/templates/firebase/lib/components/kasy_toast.dart +107 -41
  49. package/templates/firebase/lib/core/app_update/app_update_repository.dart +54 -0
  50. package/templates/firebase/lib/core/app_update/app_update_status.dart +14 -0
  51. package/templates/firebase/lib/core/app_update/update_available_sheet.dart +70 -0
  52. package/templates/firebase/lib/core/bottom_menu/active_tab_notifier.dart +40 -3
  53. package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +82 -34
  54. package/templates/firebase/lib/core/bottom_menu/web_content_wrapper.dart +6 -6
  55. package/templates/firebase/lib/core/bottom_menu/web_url.dart +20 -0
  56. package/templates/firebase/lib/core/data/api/remote_config_api.dart +38 -1
  57. package/templates/firebase/lib/core/guards/guard.dart +16 -2
  58. package/templates/firebase/lib/core/icons/kasy_icons.dart +3 -0
  59. package/templates/firebase/lib/core/rating/widgets/rate_banner.dart +2 -5
  60. package/templates/firebase/lib/core/rating/widgets/review_popup.dart +5 -3
  61. package/templates/firebase/lib/core/shared_preferences/shared_preferences.dart +14 -0
  62. package/templates/firebase/lib/core/states/components/maybe_show_update_available.dart +32 -0
  63. package/templates/firebase/lib/core/states/logout_action.dart +5 -1
  64. package/templates/firebase/lib/core/states/user_state_notifier.dart +85 -14
  65. package/templates/firebase/lib/core/theme/responsive_text_theme.dart +69 -0
  66. package/templates/firebase/lib/core/theme/texts.dart +90 -57
  67. package/templates/firebase/lib/core/theme/type_scale.dart +77 -0
  68. package/templates/firebase/lib/core/theme/web_background_sync_web.dart +20 -6
  69. package/templates/firebase/lib/core/utils/image_bytes_loader.dart +12 -0
  70. package/templates/firebase/lib/core/utils/image_bytes_loader_io.dart +28 -0
  71. package/templates/firebase/lib/core/utils/image_bytes_loader_web.dart +56 -0
  72. package/templates/firebase/lib/core/web_screen_width.dart +15 -0
  73. package/templates/firebase/lib/core/web_screen_width_io.dart +3 -0
  74. package/templates/firebase/lib/core/web_screen_width_web.dart +10 -0
  75. package/templates/firebase/lib/core/web_viewport_scale.dart +61 -25
  76. package/templates/firebase/lib/core/widgets/focus_visibility.dart +89 -0
  77. package/templates/firebase/lib/features/ai_chat/ai_chat_page.dart +1 -2
  78. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_chat_conversation_view.dart +1 -2
  79. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_list.dart +2 -3
  80. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_tile.dart +1 -2
  81. package/templates/firebase/lib/features/authentication/api/auth_web_support.dart +12 -0
  82. package/templates/firebase/lib/features/authentication/api/auth_web_support_web.dart +25 -0
  83. package/templates/firebase/lib/features/authentication/api/authentication_api.dart +205 -0
  84. package/templates/firebase/lib/features/authentication/api/authentication_api_interface.dart +22 -0
  85. package/templates/firebase/lib/features/authentication/navigation/post_login_navigation.dart +32 -0
  86. package/templates/firebase/lib/features/authentication/providers/phone_auth_notifier.dart +7 -7
  87. package/templates/firebase/lib/features/authentication/providers/signin_state_provider.dart +34 -10
  88. package/templates/firebase/lib/features/authentication/providers/signup_state_provider.dart +2 -2
  89. package/templates/firebase/lib/features/authentication/repositories/authentication_repository.dart +37 -0
  90. package/templates/firebase/lib/features/authentication/repositories/exceptions/authentication_exceptions.dart +8 -0
  91. package/templates/firebase/lib/features/authentication/ui/components/otp_verification.dart +2 -2
  92. package/templates/firebase/lib/features/authentication/ui/components/phone_input.dart +2 -2
  93. package/templates/firebase/lib/features/authentication/ui/signin_page.dart +59 -0
  94. package/templates/firebase/lib/features/authentication/ui/widgets/auth_card_scaffold.dart +2 -2
  95. package/templates/firebase/lib/features/authentication/ui/widgets/recover_password_result.dart +2 -2
  96. package/templates/firebase/lib/features/feedbacks/ui/widgets/add_feature_button.dart +0 -1
  97. package/templates/firebase/lib/features/home/design_system_page.dart +134 -67
  98. package/templates/firebase/lib/features/home/home_components_page.dart +4 -3
  99. package/templates/firebase/lib/features/home/home_components_preview_page.dart +1 -3
  100. package/templates/firebase/lib/features/home/home_components_preview_registry.dart +226 -105
  101. package/templates/firebase/lib/features/home/home_page.dart +4 -0
  102. package/templates/firebase/lib/features/local_reminders/ui/reminder_page.dart +8 -3
  103. package/templates/firebase/lib/features/notifications/providers/notifications_provider.dart +10 -0
  104. package/templates/firebase/lib/features/notifications/ui/notifications_page.dart +3 -1
  105. package/templates/firebase/lib/features/notifications/ui/widgets/notification_tile.dart +8 -3
  106. package/templates/firebase/lib/features/onboarding/providers/onboarding_model.dart +11 -0
  107. package/templates/firebase/lib/features/onboarding/providers/onboarding_provider.dart +30 -3
  108. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_loader.dart +13 -4
  109. package/templates/firebase/lib/features/settings/settings_page.dart +152 -11
  110. package/templates/firebase/lib/features/settings/ui/components/admin/admin_page.dart +43 -15
  111. package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_tab.dart +12 -12
  112. package/templates/firebase/lib/features/settings/ui/components/admin/send_push_notification_page.dart +1 -4
  113. package/templates/firebase/lib/features/settings/ui/components/create_password_sheet.dart +141 -0
  114. package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +0 -1
  115. package/templates/firebase/lib/features/settings/ui/widgets/admin_card.dart +42 -38
  116. package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +17 -2
  117. package/templates/firebase/lib/features/subscriptions/api/entities/subscription_entity.dart +3 -0
  118. package/templates/firebase/lib/features/subscriptions/api/stripe_product.dart +12 -11
  119. package/templates/firebase/lib/features/subscriptions/repositories/subscription_repository.dart +25 -2
  120. package/templates/firebase/lib/features/subscriptions/ui/component/active_premium_content.dart +319 -143
  121. package/templates/firebase/lib/features/subscriptions/ui/component/paywall_minimal.dart +1 -5
  122. package/templates/firebase/lib/features/subscriptions/ui/component/paywall_row.dart +1 -5
  123. package/templates/firebase/lib/features/subscriptions/ui/component/paywall_with_switch.dart +2 -5
  124. package/templates/firebase/lib/features/subscriptions/ui/component/premium_content.dart +1 -4
  125. package/templates/firebase/lib/features/subscriptions/ui/widgets/feature_line.dart +1 -2
  126. package/templates/firebase/lib/features/subscriptions/ui/widgets/premium_banner.dart +2 -7
  127. package/templates/firebase/lib/features/subscriptions/ui/widgets/premium_feature.dart +2 -4
  128. package/templates/firebase/lib/features/subscriptions/ui/widgets/selectable_col.dart +1 -4
  129. package/templates/firebase/lib/features/subscriptions/ui/widgets/selectable_row.dart +0 -3
  130. package/templates/firebase/lib/i18n/en.i18n.json +49 -3
  131. package/templates/firebase/lib/i18n/es.i18n.json +49 -3
  132. package/templates/firebase/lib/i18n/pt.i18n.json +49 -3
  133. package/templates/firebase/lib/main.dart +11 -2
  134. package/templates/firebase/lib/router.dart +92 -13
  135. package/templates/firebase/pubspec.yaml +1 -1
  136. package/templates/firebase/test/core/data/api/fake_remote_config_api.dart +14 -0
  137. package/templates/firebase/test/core/states/user_state_notifier_test.dart +47 -3
  138. package/templates/firebase/test/core/web_viewport_scale_test.dart +68 -0
  139. package/templates/firebase/test/features/authentication/data/api/auth_api_fake.dart +15 -0
  140. package/templates/firebase/web/index.html +162 -14
  141. package/templates/firebase/lib/core/guards/user_info_guard.dart +0 -61
@@ -1,3 +1,4 @@
1
+ import 'package:bart/bart/bart_model.dart';
1
2
  import 'package:flutter/foundation.dart' show kIsWeb;
2
3
  import 'package:flutter/material.dart';
3
4
  import 'package:flutter/services.dart';
@@ -82,6 +83,10 @@ ComponentPreviewDefinition? getComponentPreviewDefinition(
82
83
  label: 'Subpage Actions',
83
84
  builder: _buildAppBarSubpageActionsVariant,
84
85
  ),
86
+ ComponentPreviewVariant(
87
+ label: 'Menu opens sidebar',
88
+ builder: _buildAppBarMenuVariant,
89
+ ),
85
90
  ],
86
91
  );
87
92
  case 'Web Header':
@@ -550,16 +555,24 @@ ComponentPreviewDefinition? getComponentPreviewDefinition(
550
555
  title: 'Sidebar',
551
556
  variants: [
552
557
  ComponentPreviewVariant(
553
- label: 'Expanded',
554
- builder: _buildSidebarExpanded,
558
+ label: 'App navigation',
559
+ builder: _buildSidebarAppNav,
555
560
  ),
556
561
  ComponentPreviewVariant(
557
- label: 'Collapsed',
562
+ label: 'With search',
563
+ builder: _buildSidebarWithSearch,
564
+ ),
565
+ ComponentPreviewVariant(
566
+ label: 'Collapsed rail',
558
567
  builder: _buildSidebarCollapsed,
559
568
  ),
560
569
  ComponentPreviewVariant(
561
- label: 'Without profile',
562
- builder: _buildSidebarNoProfile,
570
+ label: 'Mobile drawer',
571
+ builder: _buildSidebarMobileDrawer,
572
+ ),
573
+ ComponentPreviewVariant(
574
+ label: 'Workspace showcase',
575
+ builder: _buildSidebarShowcase,
563
576
  ),
564
577
  ],
565
578
  );
@@ -616,63 +629,67 @@ ComponentPreviewDefinition? getComponentPreviewDefinition(
616
629
  // Sidebar — interactive preview
617
630
  // ─────────────────────────────────────────────────────────────────────────────
618
631
 
619
- Widget _buildSidebarExpanded(BuildContext context) =>
620
- const _SidebarPreview(initiallyCollapsed: false);
632
+ // The live Home configuration (real pages, highlight-only nav). The default
633
+ // preview — what actually ships in the app.
634
+ Widget _buildSidebarAppNav(BuildContext context) => const _SidebarPreview();
635
+
636
+ // Same Home config plus the opt-in ⌘K search row pinned above the footer.
637
+ Widget _buildSidebarWithSearch(BuildContext context) =>
638
+ const _SidebarPreview(showSearch: true);
621
639
 
640
+ // Home config starting as the narrow icon rail (tooltips on hover).
622
641
  Widget _buildSidebarCollapsed(BuildContext context) =>
623
642
  const _SidebarPreview(initiallyCollapsed: true);
624
643
 
625
- Widget _buildSidebarNoProfile(BuildContext context) =>
626
- const _SidebarPreview(initiallyCollapsed: false, showProfile: false);
644
+ // The mobile pattern: always wide, collapse toggle hidden (isDrawer).
645
+ Widget _buildSidebarMobileDrawer(BuildContext context) =>
646
+ const _SidebarPreview(isDrawer: true);
647
+
648
+ // The rich SaaS showcase (workspace selector, KasyTabs segment, layers list).
649
+ Widget _buildSidebarShowcase(BuildContext context) =>
650
+ const _SidebarPreview(showcase: true);
627
651
 
652
+ /// Launches a [KasySidebar] as a full-height drawer (left + right buttons) so
653
+ /// the full-bleed chrome can be inspected without crushing the preview card.
654
+ /// Connected variants mirror the live Home; [showcase] swaps to the HeroUI
655
+ /// workspace demo.
628
656
  class _SidebarPreview extends StatelessWidget {
629
657
  const _SidebarPreview({
630
- required this.initiallyCollapsed,
631
- this.showProfile = true,
658
+ this.showcase = false,
659
+ this.initiallyCollapsed = false,
660
+ this.isDrawer = false,
661
+ this.showSearch = false,
632
662
  });
633
663
 
664
+ final bool showcase;
634
665
  final bool initiallyCollapsed;
635
- final bool showProfile;
636
-
637
- void _open(BuildContext context, {required bool fromEnd}) {
638
- showGeneralDialog<void>(
639
- context: context,
640
- barrierDismissible: true,
641
- barrierLabel: 'Dismiss',
642
- barrierColor: Colors.black.withValues(alpha: 0.42),
643
- transitionDuration: const Duration(milliseconds: 290),
644
- pageBuilder: (ctx, anim, secAnim) => Align(
645
- alignment: fromEnd ? Alignment.centerRight : Alignment.centerLeft,
646
- child: SizedBox(
647
- height: double.infinity,
648
- child: KasySidebar(
649
- initiallyCollapsed: initiallyCollapsed,
650
- showProfile: showProfile,
651
- side: fromEnd
652
- ? KasySidebarSide.right
653
- : KasySidebarSide.left,
654
- ),
655
- ),
656
- ),
657
- transitionBuilder: (ctx, anim, secAnim, child) {
658
- final curved = CurvedAnimation(
659
- parent: anim,
660
- curve: Curves.easeOutCubic,
661
- );
662
- return SlideTransition(
663
- position: Tween<Offset>(
664
- begin: Offset(fromEnd ? 1.0 : -1.0, 0),
665
- end: Offset.zero,
666
- ).animate(curved),
667
- child: FadeTransition(
668
- opacity: Tween<double>(begin: 0.85, end: 1.0).animate(curved),
669
- child: child,
670
- ),
671
- );
672
- },
666
+ final bool isDrawer;
667
+ final bool showSearch;
668
+
669
+ Widget _sidebar({required bool fromEnd}) {
670
+ final KasySidebarSide side =
671
+ fromEnd ? KasySidebarSide.right : KasySidebarSide.left;
672
+ if (showcase) {
673
+ return KasySidebar(
674
+ initiallyCollapsed: initiallyCollapsed,
675
+ side: side,
676
+ );
677
+ }
678
+ return _DemoSidebar(
679
+ isDrawer: isDrawer,
680
+ initiallyCollapsed: initiallyCollapsed,
681
+ showSearch: showSearch,
682
+ side: side,
673
683
  );
674
684
  }
675
685
 
686
+ void _open(BuildContext context, {required bool fromEnd}) =>
687
+ _openSidebarDrawer(
688
+ context,
689
+ fromEnd: fromEnd,
690
+ child: _sidebar(fromEnd: fromEnd),
691
+ );
692
+
676
693
  @override
677
694
  Widget build(BuildContext context) {
678
695
  return Column(
@@ -705,6 +722,107 @@ class _SidebarPreview extends StatelessWidget {
705
722
  }
706
723
  }
707
724
 
725
+ /// A [KasySidebar] wired to the live Home configuration (real pages) with
726
+ /// highlight-only navigation, so the showcase mirrors the actual app. Shared by
727
+ /// the sidebar previews and the app-bar drawer demo.
728
+ class _DemoSidebar extends StatefulWidget {
729
+ const _DemoSidebar({
730
+ this.isDrawer = false,
731
+ this.initiallyCollapsed = false,
732
+ this.showSearch = false,
733
+ this.side = KasySidebarSide.left,
734
+ });
735
+
736
+ final bool isDrawer;
737
+ final bool initiallyCollapsed;
738
+ final bool showSearch;
739
+ final KasySidebarSide side;
740
+
741
+ @override
742
+ State<_DemoSidebar> createState() => _DemoSidebarState();
743
+ }
744
+
745
+ class _DemoSidebarState extends State<_DemoSidebar> {
746
+ final ValueNotifier<int> _current = ValueNotifier<int>(0);
747
+
748
+ // The same four tabs the live app wires into the sidebar (Home / Support /
749
+ // Notifications / Settings); pages are stubs since this is a visual demo.
750
+ late final List<BartMenuRoute> _routes = <BartMenuRoute>[
751
+ BartMenuRoute.bottomBar(
752
+ label: 'Home', icon: KasyIcons.home, path: 'home', pageBuilder: _stub),
753
+ BartMenuRoute.bottomBar(
754
+ label: 'Support',
755
+ icon: KasyIcons.help,
756
+ path: 'support',
757
+ pageBuilder: _stub),
758
+ BartMenuRoute.bottomBar(
759
+ label: 'Notifications',
760
+ icon: KasyIcons.notification,
761
+ path: 'notifications',
762
+ pageBuilder: _stub),
763
+ BartMenuRoute.bottomBar(
764
+ label: 'Settings',
765
+ icon: KasyIcons.settings,
766
+ path: 'settings',
767
+ pageBuilder: _stub),
768
+ ];
769
+
770
+ static Widget _stub(BuildContext _, BuildContext _, RouteSettings? _) =>
771
+ const SizedBox.shrink();
772
+
773
+ @override
774
+ void dispose() {
775
+ _current.dispose();
776
+ super.dispose();
777
+ }
778
+
779
+ @override
780
+ Widget build(BuildContext context) {
781
+ return KasySidebar(
782
+ routes: _routes,
783
+ currentItem: _current,
784
+ onTapItem: (i) => _current.value = i, // highlight only
785
+ isDrawer: widget.isDrawer,
786
+ initiallyCollapsed: widget.initiallyCollapsed,
787
+ showSearch: widget.showSearch,
788
+ side: widget.side,
789
+ );
790
+ }
791
+ }
792
+
793
+ /// Slides [child] in as a full-height drawer from the left ([fromEnd] = right),
794
+ /// over a dim barrier — the mobile navigation-drawer pattern.
795
+ void _openSidebarDrawer(
796
+ BuildContext context, {
797
+ required bool fromEnd,
798
+ required Widget child,
799
+ }) {
800
+ showGeneralDialog<void>(
801
+ context: context,
802
+ barrierDismissible: true,
803
+ barrierLabel: 'Dismiss',
804
+ barrierColor: Colors.black.withValues(alpha: 0.42),
805
+ transitionDuration: const Duration(milliseconds: 290),
806
+ pageBuilder: (ctx, anim, secAnim) => Align(
807
+ alignment: fromEnd ? Alignment.centerRight : Alignment.centerLeft,
808
+ child: SizedBox(height: double.infinity, child: child),
809
+ ),
810
+ transitionBuilder: (ctx, anim, secAnim, c) {
811
+ final curved = CurvedAnimation(parent: anim, curve: Curves.easeOutCubic);
812
+ return SlideTransition(
813
+ position: Tween<Offset>(
814
+ begin: Offset(fromEnd ? 1.0 : -1.0, 0),
815
+ end: Offset.zero,
816
+ ).animate(curved),
817
+ child: FadeTransition(
818
+ opacity: Tween<double>(begin: 0.85, end: 1.0).animate(curved),
819
+ child: c,
820
+ ),
821
+ );
822
+ },
823
+ );
824
+ }
825
+
708
826
  // ─────────────────────────────────────────────────────────────────────────────
709
827
  // ─────────────────────────────────────────────────────────────────────────────
710
828
  // Tabs — interactive demos
@@ -1781,6 +1899,13 @@ Widget _buildAppBarSubpageActionsVariant(BuildContext context) {
1781
1899
  return const _AppBarPreview(variant: KasyAppBarStyle.subpageActions);
1782
1900
  }
1783
1901
 
1902
+ Widget _buildAppBarMenuVariant(BuildContext context) {
1903
+ return const _AppBarPreview(
1904
+ variant: KasyAppBarStyle.rootTab,
1905
+ withMenu: true,
1906
+ );
1907
+ }
1908
+
1784
1909
  Widget _buildWebHeaderDefaultVariant(BuildContext context) {
1785
1910
  return const _WebHeaderPreview();
1786
1911
  }
@@ -3340,7 +3465,6 @@ class _ToastVariantsPreview extends StatelessWidget {
3340
3465
  title: 'Join a team',
3341
3466
  message:
3342
3467
  'You have been invited to join the team!',
3343
- icon: _logoChip(context),
3344
3468
  ),
3345
3469
  ),
3346
3470
  const SizedBox(height: KasySpacing.sm),
@@ -3413,16 +3537,6 @@ class _ToastVariantsPreview extends StatelessWidget {
3413
3537
  ),
3414
3538
  );
3415
3539
  }
3416
-
3417
- Widget _logoChip(BuildContext context) => Container(
3418
- width: 26,
3419
- height: 26,
3420
- decoration: BoxDecoration(
3421
- color: context.colors.onSurface,
3422
- borderRadius: BorderRadius.circular(KasyRadius.xs),
3423
- ),
3424
- child: Icon(KasyIcons.widgets, size: 15, color: context.colors.surface),
3425
- );
3426
3540
  }
3427
3541
 
3428
3542
  // — Toast: static cards preview (all 5 tones, close buttons functional) —
@@ -3437,29 +3551,16 @@ class _ToastStaticPreview extends StatefulWidget {
3437
3551
  class _ToastStaticPreviewState extends State<_ToastStaticPreview> {
3438
3552
  final Set<int> _dismissed = {};
3439
3553
 
3440
- Widget _logoChip() => Builder(
3441
- builder: (ctx) => Container(
3442
- width: 26,
3443
- height: 26,
3444
- decoration: BoxDecoration(
3445
- color: ctx.colors.onSurface,
3446
- borderRadius: BorderRadius.circular(KasyRadius.xs),
3447
- ),
3448
- child:
3449
- Icon(KasyIcons.widgets, size: 15, color: ctx.colors.surface),
3450
- ),
3451
- );
3452
-
3453
3554
  @override
3454
3555
  Widget build(BuildContext context) {
3455
3556
  final items = <(int, KasyToast)>[
3456
3557
  (
3457
3558
  0,
3559
+ // Default (neutral) tone renders the app brand logo automatically.
3458
3560
  KasyToast(
3459
3561
  title: 'Join a team',
3460
3562
  message:
3461
3563
  'You have been invited to join the team!',
3462
- icon: _logoChip(),
3463
3564
  onClose: () => setState(() => _dismissed.add(0)),
3464
3565
  ),
3465
3566
  ),
@@ -3901,7 +4002,11 @@ class _DesktopMockContent extends StatelessWidget {
3901
4002
  class _AppBarPreview extends StatelessWidget {
3902
4003
  final KasyAppBarStyle variant;
3903
4004
 
3904
- const _AppBarPreview({required this.variant});
4005
+ /// Shows a hamburger leading that opens a [KasySidebar] drawer from the left —
4006
+ /// the mobile "menu opens navigation" pattern.
4007
+ final bool withMenu;
4008
+
4009
+ const _AppBarPreview({required this.variant, this.withMenu = false});
3905
4010
 
3906
4011
  @override
3907
4012
  Widget build(BuildContext context) {
@@ -3913,6 +4018,20 @@ class _AppBarPreview extends StatelessWidget {
3913
4018
  tooltip: 'More',
3914
4019
  onPressed: () {},
3915
4020
  );
4021
+ final Widget? leading = withMenu
4022
+ ? KasyChromeOrbIconButton(
4023
+ icon: KasyIcons.menu,
4024
+ iconSize: 20,
4025
+ foregroundColor: context.colors.onSurface,
4026
+ fillColor: kasyChromeOrbFillColor(context),
4027
+ tooltip: 'Menu',
4028
+ onPressed: () => _openSidebarDrawer(
4029
+ context,
4030
+ fromEnd: false,
4031
+ child: const _DemoSidebar(isDrawer: true),
4032
+ ),
4033
+ )
4034
+ : null;
3916
4035
  final KasyColors c = context.colors;
3917
4036
  // KasyAppBar hides itself at desktop width; the preview must always show it,
3918
4037
  // so pin a phone-sized MediaQuery just for the bar's own width check.
@@ -3941,9 +4060,10 @@ class _AppBarPreview extends StatelessWidget {
3941
4060
  right: 0,
3942
4061
  child: KasyAppBar(
3943
4062
  useSafeArea: false,
3944
- title: 'Preview',
4063
+ title: withMenu ? 'Home' : 'Preview',
3945
4064
  style: variant,
3946
4065
  onBack: () {},
4066
+ leading: leading,
3947
4067
  trailing: variant == KasyAppBarStyle.subpageActions
3948
4068
  ? trailing
3949
4069
  : null,
@@ -7625,15 +7745,15 @@ class _RealCardContent extends StatelessWidget {
7625
7745
  children: [
7626
7746
  Text(
7627
7747
  'Alex Johnson',
7628
- style: TextStyle(
7629
- fontSize: 14,
7630
- fontWeight: FontWeight.w600,
7748
+ style: context.kasyTextTheme.cardTitle.copyWith(
7631
7749
  color: c.onSurface,
7632
7750
  ),
7633
7751
  ),
7634
7752
  Text(
7635
7753
  '@alexjohnson · 2h ago',
7636
- style: TextStyle(fontSize: 12, color: c.muted),
7754
+ style: context.kasyTextTheme.cardSubtitle.copyWith(
7755
+ color: c.muted,
7756
+ ),
7637
7757
  ),
7638
7758
  ],
7639
7759
  ),
@@ -7682,8 +7802,7 @@ class _RealCardContent extends StatelessWidget {
7682
7802
  'Golden hour light made every single step worth it.',
7683
7803
  maxLines: 3,
7684
7804
  overflow: TextOverflow.ellipsis,
7685
- style: TextStyle(
7686
- fontSize: 13,
7805
+ style: context.textTheme.bodySmall?.copyWith(
7687
7806
  color: c.onSurface,
7688
7807
  height: 1.5,
7689
7808
  ),
@@ -7748,9 +7867,7 @@ class _SkeletonControls extends StatelessWidget {
7748
7867
  // Label
7749
7868
  Text(
7750
7869
  'Animation',
7751
- style: TextStyle(
7752
- fontSize: 12,
7753
- fontWeight: FontWeight.w600,
7870
+ style: context.kasyTextTheme.sectionLabel.copyWith(
7754
7871
  color: c.muted,
7755
7872
  letterSpacing: 0.4,
7756
7873
  ),
@@ -7815,7 +7932,9 @@ class _SkeletonControls extends StatelessWidget {
7815
7932
  ),
7816
7933
  Text(
7817
7934
  'Toggle skeleton loading state',
7818
- style: TextStyle(fontSize: 11, color: c.muted),
7935
+ style: context.kasyTextTheme.caption.copyWith(
7936
+ color: c.muted,
7937
+ ),
7819
7938
  ),
7820
7939
  ],
7821
7940
  ),
@@ -7965,15 +8084,15 @@ class _SkeletonListPreviewState extends State<_SkeletonListPreview> {
7965
8084
  children: [
7966
8085
  Text(
7967
8086
  item.title,
7968
- style: TextStyle(
7969
- fontSize: 14,
7970
- fontWeight: FontWeight.w600,
8087
+ style: context.kasyTextTheme.rowTitle.copyWith(
7971
8088
  color: c.onSurface,
7972
8089
  ),
7973
8090
  ),
7974
8091
  Text(
7975
8092
  item.subtitle,
7976
- style: TextStyle(fontSize: 12, color: c.muted),
8093
+ style: context.kasyTextTheme.cardSubtitle.copyWith(
8094
+ color: c.muted,
8095
+ ),
7977
8096
  ),
7978
8097
  ],
7979
8098
  ),
@@ -8118,8 +8237,7 @@ class _SkeletonTextPreviewState extends State<_SkeletonTextPreview> {
8118
8237
  Widget _buildRealText(BuildContext context) {
8119
8238
  return Text(
8120
8239
  _kParagraph,
8121
- style: TextStyle(
8122
- fontSize: 16,
8240
+ style: context.textTheme.bodyLarge?.copyWith(
8123
8241
  height: 1.6,
8124
8242
  color: context.colors.onSurface,
8125
8243
  ),
@@ -8224,9 +8342,7 @@ class _SkeletonProfilePreviewState extends State<_SkeletonProfilePreview> {
8224
8342
  // Name
8225
8343
  Text(
8226
8344
  'Alex Johnson',
8227
- style: TextStyle(
8228
- fontSize: 20,
8229
- fontWeight: FontWeight.w700,
8345
+ style: context.textTheme.headlineSmall?.copyWith(
8230
8346
  color: c.onSurface,
8231
8347
  ),
8232
8348
  ),
@@ -8234,14 +8350,19 @@ class _SkeletonProfilePreviewState extends State<_SkeletonProfilePreview> {
8234
8350
  // Handle
8235
8351
  Text(
8236
8352
  '@alexjohnson',
8237
- style: TextStyle(fontSize: 13, color: c.muted),
8353
+ style: context.kasyTextTheme.cardSubtitle.copyWith(
8354
+ color: c.muted,
8355
+ ),
8238
8356
  ),
8239
8357
  const SizedBox(height: KasySpacing.smd),
8240
8358
  // Bio
8241
8359
  Text(
8242
8360
  'Product designer & outdoor enthusiast. Building tools that help people do more with less.',
8243
8361
  textAlign: TextAlign.center,
8244
- style: TextStyle(fontSize: 13, color: c.onSurface, height: 1.5),
8362
+ style: context.textTheme.bodySmall?.copyWith(
8363
+ color: c.onSurface,
8364
+ height: 1.5,
8365
+ ),
8245
8366
  ),
8246
8367
  const SizedBox(height: KasySpacing.md),
8247
8368
  // Stats row
@@ -8295,15 +8416,15 @@ class _StatCell extends StatelessWidget {
8295
8416
  children: [
8296
8417
  Text(
8297
8418
  value,
8298
- style: TextStyle(
8299
- fontSize: 17,
8300
- fontWeight: FontWeight.w700,
8419
+ style: context.textTheme.titleMedium?.copyWith(
8301
8420
  color: c.onSurface,
8302
8421
  ),
8303
8422
  ),
8304
8423
  Text(
8305
8424
  label,
8306
- style: TextStyle(fontSize: 11, color: c.muted),
8425
+ style: context.kasyTextTheme.caption.copyWith(
8426
+ color: c.muted,
8427
+ ),
8307
8428
  ),
8308
8429
  ],
8309
8430
  ),
@@ -8529,9 +8650,7 @@ class _SkeletonGridPreviewState extends State<_SkeletonGridPreview> {
8529
8650
  children: [
8530
8651
  Text(
8531
8652
  item.title,
8532
- style: TextStyle(
8533
- fontSize: 13,
8534
- fontWeight: FontWeight.w600,
8653
+ style: context.kasyTextTheme.cardTitle.copyWith(
8535
8654
  color: c.onSurface,
8536
8655
  ),
8537
8656
  maxLines: 1,
@@ -8540,7 +8659,9 @@ class _SkeletonGridPreviewState extends State<_SkeletonGridPreview> {
8540
8659
  const SizedBox(height: 2),
8541
8660
  Text(
8542
8661
  item.subtitle,
8543
- style: TextStyle(fontSize: 11, color: c.muted),
8662
+ style: context.kasyTextTheme.cardSubtitle.copyWith(
8663
+ color: c.muted,
8664
+ ),
8544
8665
  maxLines: 1,
8545
8666
  overflow: TextOverflow.ellipsis,
8546
8667
  ),
@@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
3
3
  import 'package:kasy_kit/components/kasy_app_bar.dart';
4
4
  import 'package:kasy_kit/core/haptics/kasy_haptics.dart';
5
5
  import 'package:kasy_kit/core/states/components/maybe_ask_rating.dart';
6
+ import 'package:kasy_kit/core/states/components/maybe_show_update_available.dart';
6
7
  import 'package:kasy_kit/core/states/components/maybe_show_update_bottom_sheet.dart';
7
8
  import 'package:kasy_kit/core/states/components/maybeshow_component.dart';
8
9
  import 'package:kasy_kit/core/theme/theme.dart';
@@ -21,6 +22,9 @@ class HomePage extends ConsumerWidget {
21
22
  Widget build(BuildContext context, WidgetRef ref) {
22
23
  return ConditionalWidgetsEvents(
23
24
  eventWidgets: [
25
+ // A required/optional store update pre-empts every other start-up
26
+ // prompt — if the user is behind, fix that before anything else.
27
+ MaybeShowUpdateAvailable(),
24
28
  MaybeAskForReview(),
25
29
  MaybeAskForRating(),
26
30
  MaybeShowPremiumPage(),
@@ -64,10 +64,13 @@ class _ReminderForm extends ConsumerWidget {
64
64
  child: ConstrainedBox(
65
65
  constraints: const BoxConstraints(maxWidth: 600),
66
66
  child: Padding(
67
+ // Horizontal gutter is already applied by KasyOverlayScaffold; only
68
+ // add vertical spacing here so the side padding matches the
69
+ // Notifications screen (a single gutter, not a doubled one).
67
70
  padding: const EdgeInsets.fromLTRB(
68
- KasySpacing.pageHorizontalGutter,
71
+ 0,
69
72
  KasySpacing.belowChromeContentGap,
70
- KasySpacing.pageHorizontalGutter,
73
+ 0,
71
74
  KasySpacing.xl,
72
75
  ),
73
76
  child: Column(
@@ -174,7 +177,9 @@ class _FieldLabel extends StatelessWidget {
174
177
  Widget build(BuildContext context) {
175
178
  return Text(
176
179
  label,
177
- style: KasyTextTheme.sectionLabel.copyWith(color: context.colors.muted),
180
+ style: context.kasyTextTheme.sectionLabel.copyWith(
181
+ color: context.colors.muted,
182
+ ),
178
183
  );
179
184
  }
180
185
  }
@@ -3,6 +3,7 @@ import 'dart:async';
3
3
  import 'package:kasy_kit/core/states/user_state_notifier.dart';
4
4
  import 'package:kasy_kit/features/notifications/providers/models/notification.dart';
5
5
  import 'package:kasy_kit/features/notifications/providers/models/notification_list.dart';
6
+ import 'package:kasy_kit/features/notifications/providers/unread_notifications_count_provider.dart';
6
7
  import 'package:kasy_kit/features/notifications/repositories/notifications_repository.dart';
7
8
  import 'package:logger/logger.dart';
8
9
  import 'package:riverpod_annotation/riverpod_annotation.dart';
@@ -62,6 +63,10 @@ class NotificationsNotifier extends _$NotificationsNotifier {
62
63
  data: [...updatedNotifications, ...seenNotifications],
63
64
  ),
64
65
  );
66
+ // The bottom-bar unread badge reads from a separate, polled source. Nudge
67
+ // it to re-check now so it drops as soon as we mark these read, instead of
68
+ // lagging until the next poll cycle.
69
+ ref.invalidate(unreadNotificationsCountProvider);
65
70
  } catch (e) {
66
71
  Logger().e("error $e");
67
72
  }
@@ -82,6 +87,9 @@ class NotificationsNotifier extends _$NotificationsNotifier {
82
87
  );
83
88
  try {
84
89
  await notificationRepository.delete(userId, notification);
90
+ // Deleting an unread notification changes the unread count: refresh the
91
+ // badge source now rather than waiting for its next poll.
92
+ ref.invalidate(unreadNotificationsCountProvider);
85
93
  } catch (e) {
86
94
  Logger().e("delete error $e");
87
95
  state = AsyncValue.data(previous);
@@ -102,6 +110,8 @@ class NotificationsNotifier extends _$NotificationsNotifier {
102
110
  await Future.wait(
103
111
  previous.data.map((n) => notificationRepository.delete(userId, n)),
104
112
  );
113
+ // Clearing the list zeroes the unread count: refresh the badge now.
114
+ ref.invalidate(unreadNotificationsCountProvider);
105
115
  } catch (e) {
106
116
  Logger().e("deleteAll error $e");
107
117
  state = AsyncValue.data(previous);
@@ -259,7 +259,9 @@ class _GroupLabel extends StatelessWidget {
259
259
  label.toUpperCase(),
260
260
  // Design-system section-eyebrow role (same as Settings' section labels),
261
261
  // instead of a bespoke 11/w700 style.
262
- style: KasyTextTheme.sectionLabel.copyWith(color: context.colors.muted),
262
+ style: context.kasyTextTheme.sectionLabel.copyWith(
263
+ color: context.colors.muted,
264
+ ),
263
265
  ),
264
266
  );
265
267
  }
@@ -59,7 +59,9 @@ class NotificationTile extends StatelessWidget {
59
59
  date: date,
60
60
  title: title,
61
61
  description: description,
62
- titleColor: context.colors.onSurface,
62
+ // Unread notifications carry the brand primary on the title so they read
63
+ // as the active, attention-worthy item (the read variant stays muted).
64
+ titleColor: context.colors.primary,
63
65
  descriptionColor: context.colors.onSurface,
64
66
  dateColor: context.colors.muted,
65
67
  onTap: onTap,
@@ -82,7 +84,10 @@ class NotificationTile extends StatelessWidget {
82
84
  date: date,
83
85
  title: title,
84
86
  description: description,
85
- titleColor: context.colors.onSurface.withValues(alpha: 0.55),
87
+ // Read notifications keep a full-strength, bold title (it just loses the
88
+ // primary colour and the unread dot); only the description is muted, so the
89
+ // title still reads as bold instead of washed out.
90
+ titleColor: context.colors.onSurface,
86
91
  descriptionColor: context.colors.onSurface.withValues(alpha: 0.45),
87
92
  dateColor: context.colors.muted,
88
93
  onTap: onTap,
@@ -134,7 +139,7 @@ class NotificationTile extends StatelessWidget {
134
139
  title,
135
140
  style: context.textTheme.titleSmall?.copyWith(
136
141
  color: titleColor,
137
- fontWeight: FontWeight.w700,
142
+ fontWeight: FontWeight.w600,
138
143
  ),
139
144
  overflow: TextOverflow.ellipsis,
140
145
  maxLines: 1,