kasy-cli 1.19.1 → 1.20.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 (44) hide show
  1. package/bin/kasy.js +1 -0
  2. package/lib/commands/new.js +9 -0
  3. package/lib/commands/run.js +22 -6
  4. package/lib/scaffold/backends/firebase/setup-from-scratch.js +91 -2
  5. package/lib/scaffold/engine.js +5 -0
  6. package/lib/scaffold/generate.js +4 -0
  7. package/lib/scaffold/shared/generator-utils.js +38 -1
  8. package/lib/utils/flutter-run.js +16 -4
  9. package/lib/utils/i18n/messages-en.js +2 -1
  10. package/lib/utils/i18n/messages-es.js +2 -1
  11. package/lib/utils/i18n/messages-pt.js +2 -1
  12. package/package.json +1 -1
  13. package/templates/firebase/android/app/src/main/res/drawable/background.png +0 -0
  14. package/templates/firebase/android/app/src/main/res/drawable-night/background.png +0 -0
  15. package/templates/firebase/android/app/src/main/res/drawable-night-v21/background.png +0 -0
  16. package/templates/firebase/android/app/src/main/res/drawable-v21/background.png +0 -0
  17. package/templates/firebase/android/app/src/main/res/values-night-v31/styles.xml +1 -1
  18. package/templates/firebase/android/app/src/main/res/values-v31/styles.xml +1 -1
  19. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png +0 -0
  20. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png +0 -0
  21. package/templates/firebase/lib/components/kasy_date_picker.dart +14 -8
  22. package/templates/firebase/lib/components/kasy_sidebar_pro.dart +1148 -0
  23. package/templates/firebase/lib/components/kasy_tabs.dart +156 -43
  24. package/templates/firebase/lib/components/kasy_text_field.dart +39 -29
  25. package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +90 -69
  26. package/templates/firebase/lib/core/bottom_menu/bottom_router.dart +30 -7
  27. package/templates/firebase/lib/core/bottom_menu/kasy_bottom_bar_factory.dart +8 -1
  28. package/templates/firebase/lib/core/dev_inspector/dev_inspector.dart +439 -232
  29. package/templates/firebase/lib/core/dev_inspector/dev_inspector_service.dart +198 -83
  30. package/templates/firebase/lib/core/icons/kasy_icons.dart +1 -0
  31. package/templates/firebase/lib/core/theme/colors.dart +6 -2
  32. package/templates/firebase/lib/core/web_device_preview/web_device_preview.dart +119 -19
  33. package/templates/firebase/lib/core/widgets/kasy_hover.dart +68 -27
  34. package/templates/firebase/lib/features/home/home_components_page.dart +3 -0
  35. package/templates/firebase/lib/features/home/home_components_preview_registry.dart +202 -79
  36. package/templates/firebase/lib/features/settings/settings_page.dart +27 -146
  37. package/templates/firebase/lib/features/settings/ui/components/admin/admin_bottom_sheet.dart +16 -3
  38. package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +22 -5
  39. package/templates/firebase/lib/i18n/en.i18n.json +3 -1
  40. package/templates/firebase/lib/i18n/es.i18n.json +3 -1
  41. package/templates/firebase/lib/i18n/pt.i18n.json +3 -1
  42. package/templates/firebase/pubspec.yaml +6 -4
  43. package/templates/firebase/web/index.html +7 -17
  44. package/templates/firebase/lib/firebase_options.dart +0 -75
@@ -3,30 +3,31 @@ import 'package:bart/bart/bart_bottombar_actions.dart';
3
3
  import 'package:flutter/foundation.dart';
4
4
  import 'package:flutter/material.dart';
5
5
  import 'package:flutter/services.dart';
6
+ import 'package:kasy_kit/components/kasy_sidebar_pro.dart';
6
7
  import 'package:kasy_kit/core/bottom_menu/bottom_router.dart';
7
8
  import 'package:kasy_kit/core/bottom_menu/kasy_bottom_bar_factory.dart';
8
- import 'package:kasy_kit/core/sidebar/kasy_sidebar.dart';
9
9
  import 'package:kasy_kit/core/theme/theme.dart';
10
10
  import 'package:kasy_kit/core/widgets/responsive_layout.dart';
11
11
 
12
12
  /// Bottom navigation host powered by Bart (https://pub.dev/packages/bart).
13
13
  ///
14
- /// Bart stores its bottom-bar visibility inside a [ValueNotifier] created in
15
- /// the [bart.BartScaffold] constructor. Because [ResponsiveLayout] recreates
16
- /// one of three [bart.BartScaffold]s on every rebuild (breakpoint change,
17
- /// theme toggle, device-preview resize), that notifier resets to its initial
18
- /// value and a previously-hidden bar can come back wrong — or, when returning
19
- /// from a native overlay (FaceID, photo picker, permission dialog), Bart's
20
- /// internal state ends up out of sync with the visible route.
14
+ /// Bart's [bart.BartScaffold] owns a [ValueNotifier<bool>] for bottom-bar
15
+ /// visibility, created in its constructor. Two issues fall out of that:
21
16
  ///
22
- /// This widget owns visibility end-to-end so feature pages never need to
23
- /// touch it. The rules are simple:
24
- /// * Bottom-bar tab (1 path segment) bar visible.
25
- /// * Inner route (multi-segment, or [bart.BartMenuRoute.showBottomBar] =
26
- /// false) bar hidden.
17
+ /// 1. [ResponsiveLayout] swaps between three [bart.BartScaffold]s (small /
18
+ /// medium / large). Any rebuild (theme toggle, locale switch,
19
+ /// device-preview resize) instantiates a fresh notifier with the initial
20
+ /// value, dropping whatever visibility we had set.
21
+ /// 2. Bart's [bart.BartScaffold.onRouteChanged] only fires when the active
22
+ /// *tab index* changes — pushing an inner route inside the same tab
23
+ /// (e.g. `home → home/components`) is silent. We can't drive visibility
24
+ /// from that callback alone.
27
25
  ///
28
- /// Sync points: [onRouteChanged], post-frame after every [build] (catches
29
- /// scaffold rebuilds), and [didChangeAppLifecycleState] on resume.
26
+ /// We solve both by treating inner-route presence as the source of truth.
27
+ /// [BottomBarVisibilityScope] exposes a counter; the [HomeInnerRoute] wrapper
28
+ /// increments while mounted and decrements on dispose. This widget reacts to
29
+ /// the counter (and to lifecycle resume + every rebuild) and re-asserts the
30
+ /// matching [BottomBarIntent] on Bart.
30
31
  class BottomMenu extends StatefulWidget {
31
32
  final String? initialRoute;
32
33
 
@@ -38,17 +39,19 @@ class BottomMenu extends StatefulWidget {
38
39
 
39
40
  class _BottomMenuState extends State<BottomMenu>
40
41
  with WidgetsBindingObserver, BartNotifier {
41
- String? _currentRoutePath;
42
+ final ValueNotifier<int> _innerRouteDepth = ValueNotifier<int>(0);
42
43
 
43
44
  @override
44
45
  void initState() {
45
46
  super.initState();
46
47
  WidgetsBinding.instance.addObserver(this);
47
- _currentRoutePath = _resolveInitialRoute(widget.initialRoute);
48
+ _innerRouteDepth.addListener(_scheduleSync);
48
49
  }
49
50
 
50
51
  @override
51
52
  void dispose() {
53
+ _innerRouteDepth.removeListener(_scheduleSync);
54
+ _innerRouteDepth.dispose();
52
55
  WidgetsBinding.instance.removeObserver(this);
53
56
  super.dispose();
54
57
  }
@@ -61,77 +64,72 @@ class _BottomMenuState extends State<BottomMenu>
61
64
  }
62
65
  }
63
66
 
64
- void _onRouteChanged(bart.BartMenuRoute route) {
65
- _currentRoutePath = route.path;
66
- _scheduleSync();
67
- }
68
-
69
67
  void _scheduleSync() {
70
68
  WidgetsBinding.instance.addPostFrameCallback((_) {
71
69
  if (!mounted) return;
72
- _applyVisibility();
70
+ if (_innerRouteDepth.value > 0) {
71
+ hideBottomBar(context);
72
+ } else {
73
+ showBottomBar(context);
74
+ }
73
75
  });
74
76
  }
75
77
 
76
- void _applyVisibility() {
77
- if (_shouldShowBottomBar(_currentRoutePath)) {
78
- showBottomBar(context);
79
- } else {
80
- hideBottomBar(context);
81
- }
82
- }
83
-
84
78
  @override
85
79
  Widget build(BuildContext context) {
86
- // Re-assert visibility after every rebuild: the Bart scaffold underneath
80
+ // Re-assert visibility after every rebuild: the BartScaffold underneath
87
81
  // may have been freshly instantiated (see class doc).
88
82
  _scheduleSync();
89
83
 
90
84
  final String? resolvedInitialRoute = _resolveInitialRoute(
91
85
  widget.initialRoute,
92
86
  );
93
- final bool showBottomBarOnStart = _shouldShowBottomBar(resolvedInitialRoute);
87
+ final bool showBottomBarOnStart = _shouldShowBottomBarOnStart(
88
+ resolvedInitialRoute,
89
+ );
94
90
  final scaffoldOptions = bart.ScaffoldOptions(
95
91
  backgroundColor: context.colors.background,
96
92
  );
97
93
 
98
- return AnnotatedRegion<SystemUiOverlayStyle>(
99
- value: switch (Theme.brightnessOf(context)) {
100
- Brightness.dark => SystemUiOverlayStyle.light,
101
- Brightness.light => SystemUiOverlayStyle.dark,
102
- },
103
- child: ResponsiveLayout(
104
- small: bart.BartScaffold(
105
- routesBuilder: subRoutes,
106
- bottomBar: kasyPaddedSurfaceBottomBar(),
107
- initialRoute: resolvedInitialRoute,
108
- showBottomBarOnStart: showBottomBarOnStart,
109
- scaffoldOptions: scaffoldOptions,
110
- onRouteChanged: _onRouteChanged,
111
- ),
112
- // medium (768–1024 px): icon-only collapsed rail
113
- medium: bart.BartScaffold(
114
- routesBuilder: subRoutes,
115
- bottomBar: kasyPaddedSurfaceBottomBar(),
116
- initialRoute: resolvedInitialRoute,
117
- showBottomBarOnStart: showBottomBarOnStart,
118
- scaffoldOptions: scaffoldOptions,
119
- sideBarOptions: bart.CustomSideBarOptions(
120
- sideBarBuilder: kasySidebarCollapsedBuilder,
94
+ return BottomBarVisibilityScope(
95
+ innerRouteDepth: _innerRouteDepth,
96
+ child: AnnotatedRegion<SystemUiOverlayStyle>(
97
+ value: switch (Theme.brightnessOf(context)) {
98
+ Brightness.dark => SystemUiOverlayStyle.light,
99
+ Brightness.light => SystemUiOverlayStyle.dark,
100
+ },
101
+ child: ResponsiveLayout(
102
+ small: bart.BartScaffold(
103
+ routesBuilder: subRoutes,
104
+ bottomBar: kasyPaddedSurfaceBottomBar(),
105
+ initialRoute: resolvedInitialRoute,
106
+ showBottomBarOnStart: showBottomBarOnStart,
107
+ scaffoldOptions: scaffoldOptions,
121
108
  ),
122
- onRouteChanged: _onRouteChanged,
123
- ),
124
- // large (1024 px+): full expanded sidebar
125
- large: bart.BartScaffold(
126
- routesBuilder: subRoutes,
127
- bottomBar: kasyPaddedSurfaceBottomBar(),
128
- initialRoute: resolvedInitialRoute,
129
- showBottomBarOnStart: showBottomBarOnStart,
130
- scaffoldOptions: scaffoldOptions,
131
- sideBarOptions: bart.CustomSideBarOptions(
132
- sideBarBuilder: kasySidebarBuilder,
109
+ // medium (768–1024 px): KasySidebarPro auto-collapses at this width
110
+ medium: bart.BartScaffold(
111
+ routesBuilder: subRoutes,
112
+ bottomBar: kasyPaddedSurfaceBottomBar(),
113
+ initialRoute: resolvedInitialRoute,
114
+ showBottomBarOnStart: showBottomBarOnStart,
115
+ scaffoldOptions: scaffoldOptions,
116
+ sideBarOptions: bart.CustomSideBarOptions(
117
+ sideBarBuilder: (routes, onTap, current) =>
118
+ const KasySidebarPro(),
119
+ ),
120
+ ),
121
+ // large (1024 px+): full expanded sidebar
122
+ large: bart.BartScaffold(
123
+ routesBuilder: subRoutes,
124
+ bottomBar: kasyPaddedSurfaceBottomBar(),
125
+ initialRoute: resolvedInitialRoute,
126
+ showBottomBarOnStart: showBottomBarOnStart,
127
+ scaffoldOptions: scaffoldOptions,
128
+ sideBarOptions: bart.CustomSideBarOptions(
129
+ sideBarBuilder: (routes, onTap, current) =>
130
+ const KasySidebarPro(),
131
+ ),
133
132
  ),
134
- onRouteChanged: _onRouteChanged,
135
133
  ),
136
134
  ),
137
135
  );
@@ -170,7 +168,7 @@ class _BottomMenuState extends State<BottomMenu>
170
168
  return path;
171
169
  }
172
170
 
173
- bool _shouldShowBottomBar(String? route) {
171
+ bool _shouldShowBottomBarOnStart(String? route) {
174
172
  if (route == null) {
175
173
  return true;
176
174
  }
@@ -178,3 +176,26 @@ class _BottomMenuState extends State<BottomMenu>
178
176
  return segments.length < 2;
179
177
  }
180
178
  }
179
+
180
+ /// Shared counter that inner home routes increment while mounted. The host
181
+ /// [BottomMenu] observes it and toggles Bart's bottom-bar accordingly.
182
+ class BottomBarVisibilityScope extends InheritedWidget {
183
+ final ValueNotifier<int> innerRouteDepth;
184
+
185
+ const BottomBarVisibilityScope({
186
+ super.key,
187
+ required this.innerRouteDepth,
188
+ required super.child,
189
+ });
190
+
191
+ static ValueNotifier<int>? maybeOf(BuildContext context) {
192
+ final scope = context
193
+ .getInheritedWidgetOfExactType<BottomBarVisibilityScope>();
194
+ return scope?.innerRouteDepth;
195
+ }
196
+
197
+ @override
198
+ bool updateShouldNotify(covariant BottomBarVisibilityScope oldWidget) {
199
+ return innerRouteDepth != oldWidget.innerRouteDepth;
200
+ }
201
+ }
@@ -2,6 +2,7 @@ import 'package:bart/bart.dart';
2
2
  import 'package:flutter/material.dart';
3
3
  import 'package:kasy_kit/components/kasy_app_bar.dart';
4
4
  import 'package:kasy_kit/core/bottom_menu/bart_inner_paths.dart';
5
+ import 'package:kasy_kit/core/bottom_menu/bottom_menu.dart';
5
6
  import 'package:kasy_kit/core/bottom_menu/notification_bottom_item.dart';
6
7
  import 'package:kasy_kit/core/navigation/kasy_navigation_config.dart';
7
8
  import 'package:kasy_kit/core/navigation/kasy_route_transition.dart';
@@ -109,18 +110,40 @@ List<BartMenuRoute> subRoutes() {
109
110
  ];
110
111
  }
111
112
 
112
- /// Marker wrapper for inner home routes. Visibility is owned by
113
- /// [BottomMenu] (see [BottomMenu._onRouteChanged] + [BartMenuRoute.innerRoute]
114
- /// `showBottomBar: false`); this widget exists for symmetry with the dashboard
115
- /// cards and to keep `HomeFeaturesPage` / `HomeComponentsPage` / etc. agnostic
116
- /// of how they were pushed.
117
- class _HomeInnerRoute extends StatelessWidget {
113
+ /// Wrapper for inner home routes. While mounted, it bumps a shared counter
114
+ /// exposed by [BottomBarVisibilityScope] so [BottomMenu] knows to hide the
115
+ /// bottom bar covering cases Bart's tab-only `onRouteChanged` misses (e.g.
116
+ /// pushing inside the same tab) and surviving scaffold rebuilds.
117
+ class _HomeInnerRoute extends StatefulWidget {
118
118
  final Widget child;
119
119
 
120
120
  const _HomeInnerRoute({required this.child});
121
121
 
122
122
  @override
123
- Widget build(BuildContext context) => child;
123
+ State<_HomeInnerRoute> createState() => _HomeInnerRouteState();
124
+ }
125
+
126
+ class _HomeInnerRouteState extends State<_HomeInnerRoute> {
127
+ ValueNotifier<int>? _depth;
128
+
129
+ @override
130
+ void didChangeDependencies() {
131
+ super.didChangeDependencies();
132
+ final next = BottomBarVisibilityScope.maybeOf(context);
133
+ if (identical(_depth, next)) return;
134
+ _depth?.value -= 1;
135
+ _depth = next;
136
+ _depth?.value += 1;
137
+ }
138
+
139
+ @override
140
+ void dispose() {
141
+ _depth?.value -= 1;
142
+ super.dispose();
143
+ }
144
+
145
+ @override
146
+ Widget build(BuildContext context) => widget.child;
124
147
  }
125
148
 
126
149
  /// Placeholder page for the wishlist tab.
@@ -38,6 +38,13 @@ final class KasyPaddedSurfaceBottomBarFactory extends BartBottomBarFactory {
38
38
  required void Function(int) onTap,
39
39
  required int currentIndex,
40
40
  }) {
41
+ return TooltipVisibility(
42
+ visible: false,
43
+ child: _build(context),
44
+ );
45
+ }
46
+
47
+ Widget _build(BuildContext context) {
41
48
  final KasyColors colors = context.colors;
42
49
  final MenuRouter menuRouter = MenuRouter.of(context);
43
50
  final BorderSide topEdge = BorderSide(
@@ -72,7 +79,7 @@ final class KasyPaddedSurfaceBottomBarFactory extends BartBottomBarFactory {
72
79
  ),
73
80
  ),
74
81
  BartCupertinoBottomBar(
75
- routes: routes,
82
+ routes: menuRouter.routesBuilder(),
76
83
  theme: CupertinoBottomBarTheme(
77
84
  bgColor: colors.surface,
78
85
  height: _kBottomBarHeightCupertino - kasyBottomBarTopPadding,