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.
- package/README.md +1 -1
- package/bin/kasy.js +24 -2
- package/docs/cli-reference.md +7 -7
- package/lib/commands/new.js +11 -9
- package/lib/commands/release-version.js +234 -0
- package/lib/commands/update.js +27 -0
- package/lib/scaffold/CHANGELOG.json +18 -0
- package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api.dart +24 -0
- package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api_interface.dart +15 -0
- package/lib/scaffold/backends/api/patch/lib/features/authentication/repositories/authentication_repository.dart +27 -0
- package/lib/scaffold/backends/api/patch/lib/features/subscriptions/api/entities/subscription_entity.dart +1 -0
- package/lib/scaffold/backends/firebase/setup-from-scratch.js +35 -21
- package/lib/scaffold/backends/patch-base-hashes.json +66 -0
- package/lib/scaffold/backends/supabase/edge-functions/stripe-webhook/index.ts +2 -0
- package/lib/scaffold/backends/supabase/migrations/20240101000007_welcome_notification.sql +36 -23
- package/lib/scaffold/backends/supabase/migrations/20240101000014_subscription_trial_end.sql +6 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/authentication/api/authentication_api.dart +82 -3
- package/lib/scaffold/backends/supabase/patch/lib/features/authentication/repositories/authentication_repository.dart +36 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/subscriptions/api/entities/subscription_entity.dart +1 -0
- package/lib/scaffold/generate.js +53 -4
- package/lib/utils/i18n/messages-en.js +23 -0
- package/lib/utils/i18n/messages-es.js +23 -0
- package/lib/utils/i18n/messages-pt.js +23 -0
- package/package.json +5 -2
- package/templates/firebase/.firebase/hosting.YnVpbGQvd2Vi.cache +73 -0
- package/templates/firebase/AGENTS.md +83 -0
- package/templates/firebase/DESIGN_SYSTEM.md +37 -2
- package/templates/firebase/docs/auth-setup.en.md +2 -0
- package/templates/firebase/docs/auth-setup.es.md +2 -0
- package/templates/firebase/docs/auth-setup.pt.md +2 -0
- package/templates/firebase/firebase.json +56 -1
- package/templates/firebase/functions/src/authentication/triggers.ts +10 -6
- package/templates/firebase/functions/src/core/data/entities/subscription_entity.ts +5 -0
- package/templates/firebase/functions/src/core/data/entities/user_entity.ts +9 -1
- package/templates/firebase/functions/src/core/data/repositories/user_repository.ts +27 -0
- package/templates/firebase/functions/src/subscriptions/models/subscriptions.ts +3 -0
- package/templates/firebase/functions/src/subscriptions/stripe_functions.ts +4 -0
- package/templates/firebase/lib/components/kasy_alert.dart +0 -1
- package/templates/firebase/lib/components/kasy_app_bar.dart +31 -16
- package/templates/firebase/lib/components/kasy_bottom_sheet.dart +0 -1
- package/templates/firebase/lib/components/kasy_date_picker.dart +0 -5
- package/templates/firebase/lib/components/kasy_dialog.dart +0 -1
- package/templates/firebase/lib/components/kasy_otp_verification_bottom_sheet.dart +1 -1
- package/templates/firebase/lib/components/kasy_sidebar.dart +215 -178
- package/templates/firebase/lib/components/kasy_text_area.dart +0 -1
- package/templates/firebase/lib/components/kasy_text_field.dart +0 -1
- package/templates/firebase/lib/components/kasy_text_field_otp.dart +0 -1
- package/templates/firebase/lib/components/kasy_toast.dart +107 -41
- package/templates/firebase/lib/core/app_update/app_update_repository.dart +54 -0
- package/templates/firebase/lib/core/app_update/app_update_status.dart +14 -0
- package/templates/firebase/lib/core/app_update/update_available_sheet.dart +70 -0
- package/templates/firebase/lib/core/bottom_menu/active_tab_notifier.dart +40 -3
- package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +82 -34
- package/templates/firebase/lib/core/bottom_menu/web_content_wrapper.dart +6 -6
- package/templates/firebase/lib/core/bottom_menu/web_url.dart +20 -0
- package/templates/firebase/lib/core/data/api/remote_config_api.dart +38 -1
- package/templates/firebase/lib/core/guards/guard.dart +16 -2
- package/templates/firebase/lib/core/icons/kasy_icons.dart +3 -0
- package/templates/firebase/lib/core/rating/widgets/rate_banner.dart +2 -5
- package/templates/firebase/lib/core/rating/widgets/review_popup.dart +5 -3
- package/templates/firebase/lib/core/shared_preferences/shared_preferences.dart +14 -0
- package/templates/firebase/lib/core/states/components/maybe_show_update_available.dart +32 -0
- package/templates/firebase/lib/core/states/logout_action.dart +5 -1
- package/templates/firebase/lib/core/states/user_state_notifier.dart +85 -14
- package/templates/firebase/lib/core/theme/responsive_text_theme.dart +69 -0
- package/templates/firebase/lib/core/theme/texts.dart +90 -57
- package/templates/firebase/lib/core/theme/type_scale.dart +77 -0
- package/templates/firebase/lib/core/theme/web_background_sync_web.dart +20 -6
- package/templates/firebase/lib/core/utils/image_bytes_loader.dart +12 -0
- package/templates/firebase/lib/core/utils/image_bytes_loader_io.dart +28 -0
- package/templates/firebase/lib/core/utils/image_bytes_loader_web.dart +56 -0
- package/templates/firebase/lib/core/web_screen_width.dart +15 -0
- package/templates/firebase/lib/core/web_screen_width_io.dart +3 -0
- package/templates/firebase/lib/core/web_screen_width_web.dart +10 -0
- package/templates/firebase/lib/core/web_viewport_scale.dart +61 -25
- package/templates/firebase/lib/core/widgets/focus_visibility.dart +89 -0
- package/templates/firebase/lib/features/ai_chat/ai_chat_page.dart +1 -2
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_chat_conversation_view.dart +1 -2
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_list.dart +2 -3
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_tile.dart +1 -2
- package/templates/firebase/lib/features/authentication/api/auth_web_support.dart +12 -0
- package/templates/firebase/lib/features/authentication/api/auth_web_support_web.dart +25 -0
- package/templates/firebase/lib/features/authentication/api/authentication_api.dart +205 -0
- package/templates/firebase/lib/features/authentication/api/authentication_api_interface.dart +22 -0
- package/templates/firebase/lib/features/authentication/navigation/post_login_navigation.dart +32 -0
- package/templates/firebase/lib/features/authentication/providers/phone_auth_notifier.dart +7 -7
- package/templates/firebase/lib/features/authentication/providers/signin_state_provider.dart +34 -10
- package/templates/firebase/lib/features/authentication/providers/signup_state_provider.dart +2 -2
- package/templates/firebase/lib/features/authentication/repositories/authentication_repository.dart +37 -0
- package/templates/firebase/lib/features/authentication/repositories/exceptions/authentication_exceptions.dart +8 -0
- package/templates/firebase/lib/features/authentication/ui/components/otp_verification.dart +2 -2
- package/templates/firebase/lib/features/authentication/ui/components/phone_input.dart +2 -2
- package/templates/firebase/lib/features/authentication/ui/signin_page.dart +59 -0
- package/templates/firebase/lib/features/authentication/ui/widgets/auth_card_scaffold.dart +2 -2
- package/templates/firebase/lib/features/authentication/ui/widgets/recover_password_result.dart +2 -2
- package/templates/firebase/lib/features/feedbacks/ui/widgets/add_feature_button.dart +0 -1
- package/templates/firebase/lib/features/home/design_system_page.dart +134 -67
- package/templates/firebase/lib/features/home/home_components_page.dart +4 -3
- package/templates/firebase/lib/features/home/home_components_preview_page.dart +1 -3
- package/templates/firebase/lib/features/home/home_components_preview_registry.dart +226 -105
- package/templates/firebase/lib/features/home/home_page.dart +4 -0
- package/templates/firebase/lib/features/local_reminders/ui/reminder_page.dart +8 -3
- package/templates/firebase/lib/features/notifications/providers/notifications_provider.dart +10 -0
- package/templates/firebase/lib/features/notifications/ui/notifications_page.dart +3 -1
- package/templates/firebase/lib/features/notifications/ui/widgets/notification_tile.dart +8 -3
- package/templates/firebase/lib/features/onboarding/providers/onboarding_model.dart +11 -0
- package/templates/firebase/lib/features/onboarding/providers/onboarding_provider.dart +30 -3
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_loader.dart +13 -4
- package/templates/firebase/lib/features/settings/settings_page.dart +152 -11
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_page.dart +43 -15
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_tab.dart +12 -12
- package/templates/firebase/lib/features/settings/ui/components/admin/send_push_notification_page.dart +1 -4
- package/templates/firebase/lib/features/settings/ui/components/create_password_sheet.dart +141 -0
- package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +0 -1
- package/templates/firebase/lib/features/settings/ui/widgets/admin_card.dart +42 -38
- package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +17 -2
- package/templates/firebase/lib/features/subscriptions/api/entities/subscription_entity.dart +3 -0
- package/templates/firebase/lib/features/subscriptions/api/stripe_product.dart +12 -11
- package/templates/firebase/lib/features/subscriptions/repositories/subscription_repository.dart +25 -2
- package/templates/firebase/lib/features/subscriptions/ui/component/active_premium_content.dart +319 -143
- package/templates/firebase/lib/features/subscriptions/ui/component/paywall_minimal.dart +1 -5
- package/templates/firebase/lib/features/subscriptions/ui/component/paywall_row.dart +1 -5
- package/templates/firebase/lib/features/subscriptions/ui/component/paywall_with_switch.dart +2 -5
- package/templates/firebase/lib/features/subscriptions/ui/component/premium_content.dart +1 -4
- package/templates/firebase/lib/features/subscriptions/ui/widgets/feature_line.dart +1 -2
- package/templates/firebase/lib/features/subscriptions/ui/widgets/premium_banner.dart +2 -7
- package/templates/firebase/lib/features/subscriptions/ui/widgets/premium_feature.dart +2 -4
- package/templates/firebase/lib/features/subscriptions/ui/widgets/selectable_col.dart +1 -4
- package/templates/firebase/lib/features/subscriptions/ui/widgets/selectable_row.dart +0 -3
- package/templates/firebase/lib/i18n/en.i18n.json +49 -3
- package/templates/firebase/lib/i18n/es.i18n.json +49 -3
- package/templates/firebase/lib/i18n/pt.i18n.json +49 -3
- package/templates/firebase/lib/main.dart +11 -2
- package/templates/firebase/lib/router.dart +92 -13
- package/templates/firebase/pubspec.yaml +1 -1
- package/templates/firebase/test/core/data/api/fake_remote_config_api.dart +14 -0
- package/templates/firebase/test/core/states/user_state_notifier_test.dart +47 -3
- package/templates/firebase/test/core/web_viewport_scale_test.dart +68 -0
- package/templates/firebase/test/features/authentication/data/api/auth_api_fake.dart +15 -0
- package/templates/firebase/web/index.html +162 -14
- package/templates/firebase/lib/core/guards/user_info_guard.dart +0 -61
|
@@ -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: '
|
|
554
|
-
builder:
|
|
558
|
+
label: 'App navigation',
|
|
559
|
+
builder: _buildSidebarAppNav,
|
|
555
560
|
),
|
|
556
561
|
ComponentPreviewVariant(
|
|
557
|
-
label: '
|
|
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: '
|
|
562
|
-
builder:
|
|
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
|
-
|
|
620
|
-
|
|
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
|
-
|
|
626
|
-
|
|
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
|
-
|
|
631
|
-
this.
|
|
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
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
71
|
+
0,
|
|
69
72
|
KasySpacing.belowChromeContentGap,
|
|
70
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
142
|
+
fontWeight: FontWeight.w600,
|
|
138
143
|
),
|
|
139
144
|
overflow: TextOverflow.ellipsis,
|
|
140
145
|
maxLines: 1,
|