kasy-cli 1.34.0 → 1.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/kasy.js +24 -2
- package/docs/cli-reference.md +7 -7
- package/lib/commands/new.js +11 -9
- package/lib/commands/release-version.js +234 -0
- package/lib/commands/update.js +27 -0
- package/lib/scaffold/CHANGELOG.json +9 -0
- package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api.dart +24 -0
- package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api_interface.dart +15 -0
- package/lib/scaffold/backends/api/patch/lib/features/authentication/repositories/authentication_repository.dart +27 -0
- package/lib/scaffold/backends/api/patch/lib/features/subscriptions/api/entities/subscription_entity.dart +1 -0
- package/lib/scaffold/backends/firebase/setup-from-scratch.js +35 -21
- package/lib/scaffold/backends/patch-base-hashes.json +66 -0
- package/lib/scaffold/backends/supabase/edge-functions/stripe-webhook/index.ts +2 -0
- package/lib/scaffold/backends/supabase/migrations/20240101000007_welcome_notification.sql +36 -23
- package/lib/scaffold/backends/supabase/migrations/20240101000014_subscription_trial_end.sql +6 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/authentication/api/authentication_api.dart +82 -3
- package/lib/scaffold/backends/supabase/patch/lib/features/authentication/repositories/authentication_repository.dart +36 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/subscriptions/api/entities/subscription_entity.dart +1 -0
- package/lib/scaffold/generate.js +53 -4
- package/lib/utils/i18n/messages-en.js +23 -0
- package/lib/utils/i18n/messages-es.js +23 -0
- package/lib/utils/i18n/messages-pt.js +23 -0
- package/package.json +5 -2
- package/templates/firebase/.firebase/hosting.YnVpbGQvd2Vi.cache +73 -0
- package/templates/firebase/AGENTS.md +83 -0
- package/templates/firebase/DESIGN_SYSTEM.md +37 -2
- package/templates/firebase/docs/auth-setup.en.md +2 -0
- package/templates/firebase/docs/auth-setup.es.md +2 -0
- package/templates/firebase/docs/auth-setup.pt.md +2 -0
- package/templates/firebase/firebase.json +56 -1
- package/templates/firebase/functions/src/authentication/triggers.ts +10 -6
- package/templates/firebase/functions/src/core/data/entities/subscription_entity.ts +5 -0
- package/templates/firebase/functions/src/core/data/entities/user_entity.ts +9 -1
- package/templates/firebase/functions/src/core/data/repositories/user_repository.ts +27 -0
- package/templates/firebase/functions/src/subscriptions/models/subscriptions.ts +3 -0
- package/templates/firebase/functions/src/subscriptions/stripe_functions.ts +4 -0
- package/templates/firebase/lib/components/kasy_alert.dart +0 -1
- package/templates/firebase/lib/components/kasy_app_bar.dart +31 -16
- package/templates/firebase/lib/components/kasy_bottom_sheet.dart +0 -1
- package/templates/firebase/lib/components/kasy_date_picker.dart +0 -5
- package/templates/firebase/lib/components/kasy_dialog.dart +0 -1
- package/templates/firebase/lib/components/kasy_otp_verification_bottom_sheet.dart +1 -1
- package/templates/firebase/lib/components/kasy_sidebar.dart +189 -120
- package/templates/firebase/lib/components/kasy_text_area.dart +0 -1
- package/templates/firebase/lib/components/kasy_text_field.dart +0 -1
- package/templates/firebase/lib/components/kasy_text_field_otp.dart +0 -1
- package/templates/firebase/lib/components/kasy_toast.dart +107 -41
- package/templates/firebase/lib/core/app_update/app_update_repository.dart +54 -0
- package/templates/firebase/lib/core/app_update/app_update_status.dart +14 -0
- package/templates/firebase/lib/core/app_update/update_available_sheet.dart +70 -0
- package/templates/firebase/lib/core/bottom_menu/active_tab_notifier.dart +40 -3
- package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +82 -34
- package/templates/firebase/lib/core/bottom_menu/web_content_wrapper.dart +6 -6
- package/templates/firebase/lib/core/bottom_menu/web_url.dart +20 -0
- package/templates/firebase/lib/core/data/api/remote_config_api.dart +38 -1
- package/templates/firebase/lib/core/guards/guard.dart +16 -2
- package/templates/firebase/lib/core/icons/kasy_icons.dart +3 -0
- package/templates/firebase/lib/core/rating/widgets/rate_banner.dart +2 -5
- package/templates/firebase/lib/core/rating/widgets/review_popup.dart +5 -3
- package/templates/firebase/lib/core/shared_preferences/shared_preferences.dart +14 -0
- package/templates/firebase/lib/core/states/components/maybe_show_update_available.dart +32 -0
- package/templates/firebase/lib/core/states/logout_action.dart +5 -1
- package/templates/firebase/lib/core/states/user_state_notifier.dart +85 -14
- package/templates/firebase/lib/core/theme/responsive_text_theme.dart +69 -0
- package/templates/firebase/lib/core/theme/texts.dart +90 -57
- package/templates/firebase/lib/core/theme/type_scale.dart +77 -0
- package/templates/firebase/lib/core/theme/web_background_sync_web.dart +20 -6
- package/templates/firebase/lib/core/utils/image_bytes_loader.dart +12 -0
- package/templates/firebase/lib/core/utils/image_bytes_loader_io.dart +28 -0
- package/templates/firebase/lib/core/utils/image_bytes_loader_web.dart +56 -0
- package/templates/firebase/lib/core/web_screen_width.dart +15 -0
- package/templates/firebase/lib/core/web_screen_width_io.dart +3 -0
- package/templates/firebase/lib/core/web_screen_width_web.dart +10 -0
- package/templates/firebase/lib/core/web_viewport_scale.dart +61 -25
- package/templates/firebase/lib/core/widgets/focus_visibility.dart +89 -0
- package/templates/firebase/lib/features/ai_chat/ai_chat_page.dart +1 -2
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_chat_conversation_view.dart +1 -2
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_list.dart +2 -3
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_tile.dart +1 -2
- package/templates/firebase/lib/features/authentication/api/auth_web_support.dart +12 -0
- package/templates/firebase/lib/features/authentication/api/auth_web_support_web.dart +25 -0
- package/templates/firebase/lib/features/authentication/api/authentication_api.dart +205 -0
- package/templates/firebase/lib/features/authentication/api/authentication_api_interface.dart +22 -0
- package/templates/firebase/lib/features/authentication/navigation/post_login_navigation.dart +32 -0
- package/templates/firebase/lib/features/authentication/providers/phone_auth_notifier.dart +7 -7
- package/templates/firebase/lib/features/authentication/providers/signin_state_provider.dart +34 -10
- package/templates/firebase/lib/features/authentication/providers/signup_state_provider.dart +2 -2
- package/templates/firebase/lib/features/authentication/repositories/authentication_repository.dart +37 -0
- package/templates/firebase/lib/features/authentication/repositories/exceptions/authentication_exceptions.dart +8 -0
- package/templates/firebase/lib/features/authentication/ui/components/otp_verification.dart +2 -2
- package/templates/firebase/lib/features/authentication/ui/components/phone_input.dart +2 -2
- package/templates/firebase/lib/features/authentication/ui/signin_page.dart +59 -0
- package/templates/firebase/lib/features/authentication/ui/widgets/auth_card_scaffold.dart +2 -2
- package/templates/firebase/lib/features/authentication/ui/widgets/recover_password_result.dart +2 -2
- package/templates/firebase/lib/features/feedbacks/ui/widgets/add_feature_button.dart +0 -1
- package/templates/firebase/lib/features/home/design_system_page.dart +134 -67
- package/templates/firebase/lib/features/home/home_components_page.dart +4 -3
- package/templates/firebase/lib/features/home/home_components_preview_page.dart +1 -3
- package/templates/firebase/lib/features/home/home_components_preview_registry.dart +154 -56
- package/templates/firebase/lib/features/home/home_page.dart +4 -0
- package/templates/firebase/lib/features/local_reminders/ui/reminder_page.dart +8 -3
- package/templates/firebase/lib/features/notifications/providers/notifications_provider.dart +10 -0
- package/templates/firebase/lib/features/notifications/ui/notifications_page.dart +3 -1
- package/templates/firebase/lib/features/notifications/ui/widgets/notification_tile.dart +8 -3
- package/templates/firebase/lib/features/onboarding/providers/onboarding_model.dart +11 -0
- package/templates/firebase/lib/features/onboarding/providers/onboarding_provider.dart +30 -3
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_loader.dart +13 -4
- package/templates/firebase/lib/features/settings/settings_page.dart +152 -11
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_page.dart +43 -15
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_tab.dart +12 -12
- package/templates/firebase/lib/features/settings/ui/components/admin/send_push_notification_page.dart +1 -4
- package/templates/firebase/lib/features/settings/ui/components/create_password_sheet.dart +141 -0
- package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +0 -1
- package/templates/firebase/lib/features/settings/ui/widgets/admin_card.dart +42 -38
- package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +17 -2
- package/templates/firebase/lib/features/subscriptions/api/entities/subscription_entity.dart +3 -0
- package/templates/firebase/lib/features/subscriptions/api/stripe_product.dart +12 -11
- package/templates/firebase/lib/features/subscriptions/repositories/subscription_repository.dart +25 -2
- package/templates/firebase/lib/features/subscriptions/ui/component/active_premium_content.dart +319 -143
- package/templates/firebase/lib/features/subscriptions/ui/component/paywall_minimal.dart +1 -5
- package/templates/firebase/lib/features/subscriptions/ui/component/paywall_row.dart +1 -5
- package/templates/firebase/lib/features/subscriptions/ui/component/paywall_with_switch.dart +2 -5
- package/templates/firebase/lib/features/subscriptions/ui/component/premium_content.dart +1 -4
- package/templates/firebase/lib/features/subscriptions/ui/widgets/feature_line.dart +1 -2
- package/templates/firebase/lib/features/subscriptions/ui/widgets/premium_banner.dart +2 -7
- package/templates/firebase/lib/features/subscriptions/ui/widgets/premium_feature.dart +2 -4
- package/templates/firebase/lib/features/subscriptions/ui/widgets/selectable_col.dart +1 -4
- package/templates/firebase/lib/features/subscriptions/ui/widgets/selectable_row.dart +0 -3
- package/templates/firebase/lib/i18n/en.i18n.json +49 -3
- package/templates/firebase/lib/i18n/es.i18n.json +49 -3
- package/templates/firebase/lib/i18n/pt.i18n.json +49 -3
- package/templates/firebase/lib/main.dart +11 -2
- package/templates/firebase/lib/router.dart +92 -13
- package/templates/firebase/pubspec.yaml +1 -1
- package/templates/firebase/test/core/data/api/fake_remote_config_api.dart +14 -0
- package/templates/firebase/test/core/states/user_state_notifier_test.dart +47 -3
- package/templates/firebase/test/core/web_viewport_scale_test.dart +68 -0
- package/templates/firebase/test/features/authentication/data/api/auth_api_fake.dart +15 -0
- package/templates/firebase/web/index.html +162 -14
- package/templates/firebase/lib/core/guards/user_info_guard.dart +0 -61
|
@@ -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':
|
|
@@ -705,6 +710,96 @@ class _SidebarPreview extends StatelessWidget {
|
|
|
705
710
|
}
|
|
706
711
|
}
|
|
707
712
|
|
|
713
|
+
/// A [KasySidebar] wired to the live Home configuration (real pages) with
|
|
714
|
+
/// highlight-only navigation, so the showcase mirrors the actual app. Shared by
|
|
715
|
+
/// the sidebar previews and the app-bar drawer demo.
|
|
716
|
+
class _DemoSidebar extends StatefulWidget {
|
|
717
|
+
const _DemoSidebar({this.isDrawer = false});
|
|
718
|
+
|
|
719
|
+
final bool isDrawer;
|
|
720
|
+
|
|
721
|
+
@override
|
|
722
|
+
State<_DemoSidebar> createState() => _DemoSidebarState();
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
class _DemoSidebarState extends State<_DemoSidebar> {
|
|
726
|
+
final ValueNotifier<int> _current = ValueNotifier<int>(0);
|
|
727
|
+
|
|
728
|
+
// The same four tabs the live app wires into the sidebar (Home / Support /
|
|
729
|
+
// Notifications / Settings); pages are stubs since this is a visual demo.
|
|
730
|
+
late final List<BartMenuRoute> _routes = <BartMenuRoute>[
|
|
731
|
+
BartMenuRoute.bottomBar(
|
|
732
|
+
label: 'Home', icon: KasyIcons.home, path: 'home', pageBuilder: _stub),
|
|
733
|
+
BartMenuRoute.bottomBar(
|
|
734
|
+
label: 'Support',
|
|
735
|
+
icon: KasyIcons.help,
|
|
736
|
+
path: 'support',
|
|
737
|
+
pageBuilder: _stub),
|
|
738
|
+
BartMenuRoute.bottomBar(
|
|
739
|
+
label: 'Notifications',
|
|
740
|
+
icon: KasyIcons.notification,
|
|
741
|
+
path: 'notifications',
|
|
742
|
+
pageBuilder: _stub),
|
|
743
|
+
BartMenuRoute.bottomBar(
|
|
744
|
+
label: 'Settings',
|
|
745
|
+
icon: KasyIcons.settings,
|
|
746
|
+
path: 'settings',
|
|
747
|
+
pageBuilder: _stub),
|
|
748
|
+
];
|
|
749
|
+
|
|
750
|
+
static Widget _stub(BuildContext _, BuildContext _, RouteSettings? _) =>
|
|
751
|
+
const SizedBox.shrink();
|
|
752
|
+
|
|
753
|
+
@override
|
|
754
|
+
void dispose() {
|
|
755
|
+
_current.dispose();
|
|
756
|
+
super.dispose();
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
@override
|
|
760
|
+
Widget build(BuildContext context) {
|
|
761
|
+
return KasySidebar(
|
|
762
|
+
routes: _routes,
|
|
763
|
+
currentItem: _current,
|
|
764
|
+
onTapItem: (i) => _current.value = i, // highlight only
|
|
765
|
+
isDrawer: widget.isDrawer,
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/// Slides [child] in as a full-height drawer from the left ([fromEnd] = right),
|
|
771
|
+
/// over a dim barrier — the mobile navigation-drawer pattern.
|
|
772
|
+
void _openSidebarDrawer(
|
|
773
|
+
BuildContext context, {
|
|
774
|
+
required bool fromEnd,
|
|
775
|
+
required Widget child,
|
|
776
|
+
}) {
|
|
777
|
+
showGeneralDialog<void>(
|
|
778
|
+
context: context,
|
|
779
|
+
barrierDismissible: true,
|
|
780
|
+
barrierLabel: 'Dismiss',
|
|
781
|
+
barrierColor: Colors.black.withValues(alpha: 0.42),
|
|
782
|
+
transitionDuration: const Duration(milliseconds: 290),
|
|
783
|
+
pageBuilder: (ctx, anim, secAnim) => Align(
|
|
784
|
+
alignment: fromEnd ? Alignment.centerRight : Alignment.centerLeft,
|
|
785
|
+
child: SizedBox(height: double.infinity, child: child),
|
|
786
|
+
),
|
|
787
|
+
transitionBuilder: (ctx, anim, secAnim, c) {
|
|
788
|
+
final curved = CurvedAnimation(parent: anim, curve: Curves.easeOutCubic);
|
|
789
|
+
return SlideTransition(
|
|
790
|
+
position: Tween<Offset>(
|
|
791
|
+
begin: Offset(fromEnd ? 1.0 : -1.0, 0),
|
|
792
|
+
end: Offset.zero,
|
|
793
|
+
).animate(curved),
|
|
794
|
+
child: FadeTransition(
|
|
795
|
+
opacity: Tween<double>(begin: 0.85, end: 1.0).animate(curved),
|
|
796
|
+
child: c,
|
|
797
|
+
),
|
|
798
|
+
);
|
|
799
|
+
},
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
|
|
708
803
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
709
804
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
710
805
|
// Tabs — interactive demos
|
|
@@ -1781,6 +1876,13 @@ Widget _buildAppBarSubpageActionsVariant(BuildContext context) {
|
|
|
1781
1876
|
return const _AppBarPreview(variant: KasyAppBarStyle.subpageActions);
|
|
1782
1877
|
}
|
|
1783
1878
|
|
|
1879
|
+
Widget _buildAppBarMenuVariant(BuildContext context) {
|
|
1880
|
+
return const _AppBarPreview(
|
|
1881
|
+
variant: KasyAppBarStyle.rootTab,
|
|
1882
|
+
withMenu: true,
|
|
1883
|
+
);
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1784
1886
|
Widget _buildWebHeaderDefaultVariant(BuildContext context) {
|
|
1785
1887
|
return const _WebHeaderPreview();
|
|
1786
1888
|
}
|
|
@@ -3340,7 +3442,6 @@ class _ToastVariantsPreview extends StatelessWidget {
|
|
|
3340
3442
|
title: 'Join a team',
|
|
3341
3443
|
message:
|
|
3342
3444
|
'You have been invited to join the team!',
|
|
3343
|
-
icon: _logoChip(context),
|
|
3344
3445
|
),
|
|
3345
3446
|
),
|
|
3346
3447
|
const SizedBox(height: KasySpacing.sm),
|
|
@@ -3413,16 +3514,6 @@ class _ToastVariantsPreview extends StatelessWidget {
|
|
|
3413
3514
|
),
|
|
3414
3515
|
);
|
|
3415
3516
|
}
|
|
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
3517
|
}
|
|
3427
3518
|
|
|
3428
3519
|
// — Toast: static cards preview (all 5 tones, close buttons functional) —
|
|
@@ -3437,29 +3528,16 @@ class _ToastStaticPreview extends StatefulWidget {
|
|
|
3437
3528
|
class _ToastStaticPreviewState extends State<_ToastStaticPreview> {
|
|
3438
3529
|
final Set<int> _dismissed = {};
|
|
3439
3530
|
|
|
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
3531
|
@override
|
|
3454
3532
|
Widget build(BuildContext context) {
|
|
3455
3533
|
final items = <(int, KasyToast)>[
|
|
3456
3534
|
(
|
|
3457
3535
|
0,
|
|
3536
|
+
// Default (neutral) tone renders the app brand logo automatically.
|
|
3458
3537
|
KasyToast(
|
|
3459
3538
|
title: 'Join a team',
|
|
3460
3539
|
message:
|
|
3461
3540
|
'You have been invited to join the team!',
|
|
3462
|
-
icon: _logoChip(),
|
|
3463
3541
|
onClose: () => setState(() => _dismissed.add(0)),
|
|
3464
3542
|
),
|
|
3465
3543
|
),
|
|
@@ -3901,7 +3979,11 @@ class _DesktopMockContent extends StatelessWidget {
|
|
|
3901
3979
|
class _AppBarPreview extends StatelessWidget {
|
|
3902
3980
|
final KasyAppBarStyle variant;
|
|
3903
3981
|
|
|
3904
|
-
|
|
3982
|
+
/// Shows a hamburger leading that opens a [KasySidebar] drawer from the left —
|
|
3983
|
+
/// the mobile "menu opens navigation" pattern.
|
|
3984
|
+
final bool withMenu;
|
|
3985
|
+
|
|
3986
|
+
const _AppBarPreview({required this.variant, this.withMenu = false});
|
|
3905
3987
|
|
|
3906
3988
|
@override
|
|
3907
3989
|
Widget build(BuildContext context) {
|
|
@@ -3913,6 +3995,20 @@ class _AppBarPreview extends StatelessWidget {
|
|
|
3913
3995
|
tooltip: 'More',
|
|
3914
3996
|
onPressed: () {},
|
|
3915
3997
|
);
|
|
3998
|
+
final Widget? leading = withMenu
|
|
3999
|
+
? KasyChromeOrbIconButton(
|
|
4000
|
+
icon: KasyIcons.menu,
|
|
4001
|
+
iconSize: 20,
|
|
4002
|
+
foregroundColor: context.colors.onSurface,
|
|
4003
|
+
fillColor: kasyChromeOrbFillColor(context),
|
|
4004
|
+
tooltip: 'Menu',
|
|
4005
|
+
onPressed: () => _openSidebarDrawer(
|
|
4006
|
+
context,
|
|
4007
|
+
fromEnd: false,
|
|
4008
|
+
child: const _DemoSidebar(isDrawer: true),
|
|
4009
|
+
),
|
|
4010
|
+
)
|
|
4011
|
+
: null;
|
|
3916
4012
|
final KasyColors c = context.colors;
|
|
3917
4013
|
// KasyAppBar hides itself at desktop width; the preview must always show it,
|
|
3918
4014
|
// so pin a phone-sized MediaQuery just for the bar's own width check.
|
|
@@ -3941,9 +4037,10 @@ class _AppBarPreview extends StatelessWidget {
|
|
|
3941
4037
|
right: 0,
|
|
3942
4038
|
child: KasyAppBar(
|
|
3943
4039
|
useSafeArea: false,
|
|
3944
|
-
title: 'Preview',
|
|
4040
|
+
title: withMenu ? 'Home' : 'Preview',
|
|
3945
4041
|
style: variant,
|
|
3946
4042
|
onBack: () {},
|
|
4043
|
+
leading: leading,
|
|
3947
4044
|
trailing: variant == KasyAppBarStyle.subpageActions
|
|
3948
4045
|
? trailing
|
|
3949
4046
|
: null,
|
|
@@ -7625,15 +7722,15 @@ class _RealCardContent extends StatelessWidget {
|
|
|
7625
7722
|
children: [
|
|
7626
7723
|
Text(
|
|
7627
7724
|
'Alex Johnson',
|
|
7628
|
-
style:
|
|
7629
|
-
fontSize: 14,
|
|
7630
|
-
fontWeight: FontWeight.w600,
|
|
7725
|
+
style: context.kasyTextTheme.cardTitle.copyWith(
|
|
7631
7726
|
color: c.onSurface,
|
|
7632
7727
|
),
|
|
7633
7728
|
),
|
|
7634
7729
|
Text(
|
|
7635
7730
|
'@alexjohnson · 2h ago',
|
|
7636
|
-
style:
|
|
7731
|
+
style: context.kasyTextTheme.cardSubtitle.copyWith(
|
|
7732
|
+
color: c.muted,
|
|
7733
|
+
),
|
|
7637
7734
|
),
|
|
7638
7735
|
],
|
|
7639
7736
|
),
|
|
@@ -7682,8 +7779,7 @@ class _RealCardContent extends StatelessWidget {
|
|
|
7682
7779
|
'Golden hour light made every single step worth it.',
|
|
7683
7780
|
maxLines: 3,
|
|
7684
7781
|
overflow: TextOverflow.ellipsis,
|
|
7685
|
-
style:
|
|
7686
|
-
fontSize: 13,
|
|
7782
|
+
style: context.textTheme.bodySmall?.copyWith(
|
|
7687
7783
|
color: c.onSurface,
|
|
7688
7784
|
height: 1.5,
|
|
7689
7785
|
),
|
|
@@ -7748,9 +7844,7 @@ class _SkeletonControls extends StatelessWidget {
|
|
|
7748
7844
|
// Label
|
|
7749
7845
|
Text(
|
|
7750
7846
|
'Animation',
|
|
7751
|
-
style:
|
|
7752
|
-
fontSize: 12,
|
|
7753
|
-
fontWeight: FontWeight.w600,
|
|
7847
|
+
style: context.kasyTextTheme.sectionLabel.copyWith(
|
|
7754
7848
|
color: c.muted,
|
|
7755
7849
|
letterSpacing: 0.4,
|
|
7756
7850
|
),
|
|
@@ -7815,7 +7909,9 @@ class _SkeletonControls extends StatelessWidget {
|
|
|
7815
7909
|
),
|
|
7816
7910
|
Text(
|
|
7817
7911
|
'Toggle skeleton loading state',
|
|
7818
|
-
style:
|
|
7912
|
+
style: context.kasyTextTheme.caption.copyWith(
|
|
7913
|
+
color: c.muted,
|
|
7914
|
+
),
|
|
7819
7915
|
),
|
|
7820
7916
|
],
|
|
7821
7917
|
),
|
|
@@ -7965,15 +8061,15 @@ class _SkeletonListPreviewState extends State<_SkeletonListPreview> {
|
|
|
7965
8061
|
children: [
|
|
7966
8062
|
Text(
|
|
7967
8063
|
item.title,
|
|
7968
|
-
style:
|
|
7969
|
-
fontSize: 14,
|
|
7970
|
-
fontWeight: FontWeight.w600,
|
|
8064
|
+
style: context.kasyTextTheme.rowTitle.copyWith(
|
|
7971
8065
|
color: c.onSurface,
|
|
7972
8066
|
),
|
|
7973
8067
|
),
|
|
7974
8068
|
Text(
|
|
7975
8069
|
item.subtitle,
|
|
7976
|
-
style:
|
|
8070
|
+
style: context.kasyTextTheme.cardSubtitle.copyWith(
|
|
8071
|
+
color: c.muted,
|
|
8072
|
+
),
|
|
7977
8073
|
),
|
|
7978
8074
|
],
|
|
7979
8075
|
),
|
|
@@ -8118,8 +8214,7 @@ class _SkeletonTextPreviewState extends State<_SkeletonTextPreview> {
|
|
|
8118
8214
|
Widget _buildRealText(BuildContext context) {
|
|
8119
8215
|
return Text(
|
|
8120
8216
|
_kParagraph,
|
|
8121
|
-
style:
|
|
8122
|
-
fontSize: 16,
|
|
8217
|
+
style: context.textTheme.bodyLarge?.copyWith(
|
|
8123
8218
|
height: 1.6,
|
|
8124
8219
|
color: context.colors.onSurface,
|
|
8125
8220
|
),
|
|
@@ -8224,9 +8319,7 @@ class _SkeletonProfilePreviewState extends State<_SkeletonProfilePreview> {
|
|
|
8224
8319
|
// Name
|
|
8225
8320
|
Text(
|
|
8226
8321
|
'Alex Johnson',
|
|
8227
|
-
style:
|
|
8228
|
-
fontSize: 20,
|
|
8229
|
-
fontWeight: FontWeight.w700,
|
|
8322
|
+
style: context.textTheme.headlineSmall?.copyWith(
|
|
8230
8323
|
color: c.onSurface,
|
|
8231
8324
|
),
|
|
8232
8325
|
),
|
|
@@ -8234,14 +8327,19 @@ class _SkeletonProfilePreviewState extends State<_SkeletonProfilePreview> {
|
|
|
8234
8327
|
// Handle
|
|
8235
8328
|
Text(
|
|
8236
8329
|
'@alexjohnson',
|
|
8237
|
-
style:
|
|
8330
|
+
style: context.kasyTextTheme.cardSubtitle.copyWith(
|
|
8331
|
+
color: c.muted,
|
|
8332
|
+
),
|
|
8238
8333
|
),
|
|
8239
8334
|
const SizedBox(height: KasySpacing.smd),
|
|
8240
8335
|
// Bio
|
|
8241
8336
|
Text(
|
|
8242
8337
|
'Product designer & outdoor enthusiast. Building tools that help people do more with less.',
|
|
8243
8338
|
textAlign: TextAlign.center,
|
|
8244
|
-
style:
|
|
8339
|
+
style: context.textTheme.bodySmall?.copyWith(
|
|
8340
|
+
color: c.onSurface,
|
|
8341
|
+
height: 1.5,
|
|
8342
|
+
),
|
|
8245
8343
|
),
|
|
8246
8344
|
const SizedBox(height: KasySpacing.md),
|
|
8247
8345
|
// Stats row
|
|
@@ -8295,15 +8393,15 @@ class _StatCell extends StatelessWidget {
|
|
|
8295
8393
|
children: [
|
|
8296
8394
|
Text(
|
|
8297
8395
|
value,
|
|
8298
|
-
style:
|
|
8299
|
-
fontSize: 17,
|
|
8300
|
-
fontWeight: FontWeight.w700,
|
|
8396
|
+
style: context.textTheme.titleMedium?.copyWith(
|
|
8301
8397
|
color: c.onSurface,
|
|
8302
8398
|
),
|
|
8303
8399
|
),
|
|
8304
8400
|
Text(
|
|
8305
8401
|
label,
|
|
8306
|
-
style:
|
|
8402
|
+
style: context.kasyTextTheme.caption.copyWith(
|
|
8403
|
+
color: c.muted,
|
|
8404
|
+
),
|
|
8307
8405
|
),
|
|
8308
8406
|
],
|
|
8309
8407
|
),
|
|
@@ -8529,9 +8627,7 @@ class _SkeletonGridPreviewState extends State<_SkeletonGridPreview> {
|
|
|
8529
8627
|
children: [
|
|
8530
8628
|
Text(
|
|
8531
8629
|
item.title,
|
|
8532
|
-
style:
|
|
8533
|
-
fontSize: 13,
|
|
8534
|
-
fontWeight: FontWeight.w600,
|
|
8630
|
+
style: context.kasyTextTheme.cardTitle.copyWith(
|
|
8535
8631
|
color: c.onSurface,
|
|
8536
8632
|
),
|
|
8537
8633
|
maxLines: 1,
|
|
@@ -8540,7 +8636,9 @@ class _SkeletonGridPreviewState extends State<_SkeletonGridPreview> {
|
|
|
8540
8636
|
const SizedBox(height: 2),
|
|
8541
8637
|
Text(
|
|
8542
8638
|
item.subtitle,
|
|
8543
|
-
style:
|
|
8639
|
+
style: context.kasyTextTheme.cardSubtitle.copyWith(
|
|
8640
|
+
color: c.muted,
|
|
8641
|
+
),
|
|
8544
8642
|
maxLines: 1,
|
|
8545
8643
|
overflow: TextOverflow.ellipsis,
|
|
8546
8644
|
),
|
|
@@ -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,
|
|
@@ -1,15 +1,26 @@
|
|
|
1
|
+
import 'package:kasy_kit/features/onboarding/models/user_info.dart';
|
|
2
|
+
|
|
1
3
|
class OnboardingState {
|
|
2
4
|
DateTime? reminder;
|
|
3
5
|
|
|
6
|
+
/// Answers collected before the anonymous account exists. The account is now
|
|
7
|
+
/// created lazily at the end of onboarding (the loader screen), so the
|
|
8
|
+
/// question screens have no user id to write to yet — we buffer the answers
|
|
9
|
+
/// here and flush them once the account is created.
|
|
10
|
+
final List<UserInfoDetail> pendingUserInfo;
|
|
11
|
+
|
|
4
12
|
OnboardingState({
|
|
5
13
|
this.reminder,
|
|
14
|
+
this.pendingUserInfo = const [],
|
|
6
15
|
});
|
|
7
16
|
|
|
8
17
|
OnboardingState copyWith({
|
|
9
18
|
DateTime? reminder,
|
|
19
|
+
List<UserInfoDetail>? pendingUserInfo,
|
|
10
20
|
}) {
|
|
11
21
|
return OnboardingState(
|
|
12
22
|
reminder: reminder ?? this.reminder,
|
|
23
|
+
pendingUserInfo: pendingUserInfo ?? this.pendingUserInfo,
|
|
13
24
|
);
|
|
14
25
|
}
|
|
15
26
|
}
|
|
@@ -31,8 +31,18 @@ class OnboardingNotifier extends _$OnboardingNotifier {
|
|
|
31
31
|
|
|
32
32
|
Future<void> onAnsweredQuestion(UserInfoDetail value) async {
|
|
33
33
|
final userId = ref.read(userStateNotifierProvider).user.idOrNull;
|
|
34
|
-
if (userId
|
|
35
|
-
|
|
34
|
+
if (userId != null) {
|
|
35
|
+
// Account already exists (e.g. a returning guest re-onboarding): save now.
|
|
36
|
+
await ref.read(userInfosRepositoryProvider).save(userId, value);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
// No account yet — it's created at the end of onboarding. Buffer the answer
|
|
40
|
+
// (replacing any previous answer of the same kind) so it isn't lost; it's
|
|
41
|
+
// flushed in [onOnboardingCompleted] once the account exists.
|
|
42
|
+
final others = state.pendingUserInfo
|
|
43
|
+
.where((info) => info.runtimeType != value.runtimeType)
|
|
44
|
+
.toList();
|
|
45
|
+
state = state.copyWith(pendingUserInfo: [...others, value]);
|
|
36
46
|
}
|
|
37
47
|
|
|
38
48
|
Future<void> setupNotifications() async {
|
|
@@ -66,7 +76,24 @@ class OnboardingNotifier extends _$OnboardingNotifier {
|
|
|
66
76
|
Future<void> onOnboardingCompleted() async {
|
|
67
77
|
final userStateNotifier = ref.read(userStateNotifierProvider.notifier);
|
|
68
78
|
|
|
69
|
-
|
|
79
|
+
// This is the "preparing everything for you" moment (the loader screen):
|
|
80
|
+
// the only place the anonymous guest account gets created. Doing it here —
|
|
81
|
+
// instead of eagerly on app start — means a fresh anonymous account is born
|
|
82
|
+
// only when the user actually commits to using the app.
|
|
83
|
+
await userStateNotifier.continueAsGuest();
|
|
84
|
+
|
|
85
|
+
// Now that the account exists, flush the answers collected during the
|
|
86
|
+
// questions (gender, age, …) that had nowhere to go before.
|
|
87
|
+
final userId = ref.read(userStateNotifierProvider).user.idOrNull;
|
|
88
|
+
final pending = state.pendingUserInfo;
|
|
89
|
+
if (userId != null && pending.isNotEmpty) {
|
|
90
|
+
final repository = ref.read(userInfosRepositoryProvider);
|
|
91
|
+
for (final info in pending) {
|
|
92
|
+
await repository.save(userId, info);
|
|
93
|
+
}
|
|
94
|
+
state = state.copyWith(pendingUserInfo: const []);
|
|
95
|
+
}
|
|
96
|
+
|
|
70
97
|
await userStateNotifier.refresh();
|
|
71
98
|
}
|
|
72
99
|
|
|
@@ -20,10 +20,19 @@ class _OnboardingJournalLoaderState extends ConsumerState<OnboardingLoader> {
|
|
|
20
20
|
@override
|
|
21
21
|
void initState() {
|
|
22
22
|
super.initState();
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
_prepare();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
Future<void> _prepare() async {
|
|
27
|
+
// Run the real work (which now lazily creates the guest account) while the
|
|
28
|
+
// loader animation plays, so its network latency is hidden under the
|
|
29
|
+
// minimum display time instead of being added on top of it.
|
|
30
|
+
await Future.wait([
|
|
31
|
+
ref.onboardingNotifier.onOnboardingCompleted(),
|
|
32
|
+
Future<void>.delayed(const Duration(milliseconds: 3500)),
|
|
33
|
+
]);
|
|
34
|
+
if (!mounted) return;
|
|
35
|
+
widget.onCompleted();
|
|
27
36
|
}
|
|
28
37
|
|
|
29
38
|
@override
|