kasy-cli 1.20.0 → 1.20.1
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 +11 -3
- package/lib/commands/new.js +78 -37
- package/lib/commands/run.js +7 -0
- package/lib/scaffold/backends/api/patch/lib/core/data/api/meta_ads_api.dart +1 -1
- package/lib/scaffold/backends/api/patch/lib/core/data/api/storage_api.dart +1 -1
- package/lib/scaffold/backends/api/patch/lib/core/data/entities/user_entity.dart +1 -1
- package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api.dart +4 -5
- package/lib/scaffold/backends/api/patch/lib/features/feedbacks/api/feature_request_api.dart +1 -1
- package/lib/scaffold/backends/api/patch/lib/features/feedbacks/api/feature_vote_api.dart +1 -1
- package/lib/scaffold/backends/api/patch/lib/features/llm_chat/api/llm_chat_api.dart +1 -1
- package/lib/scaffold/backends/api/patch/lib/features/llm_chat/providers/llm_chat_notifier.dart +317 -0
- package/lib/scaffold/backends/api/patch/lib/features/notifications/api/device_api.dart +40 -1
- package/lib/scaffold/backends/api/patch/lib/features/notifications/api/entities/notifications_entity.dart +2 -0
- package/lib/scaffold/backends/api/patch/lib/features/onboarding/api/entities/user_info_entity.dart +1 -1
- package/lib/scaffold/backends/api/patch/lib/features/subscription/api/entities/subscription_entity.dart +1 -1
- package/lib/scaffold/backends/api/patch/lib/features/subscription/shared/maybeshow_premium.dart +0 -2
- package/lib/scaffold/backends/api/pubspec.yaml.tpl +2 -0
- package/lib/scaffold/backends/firebase/enable-auth-via-cli.js +11 -8
- package/lib/scaffold/backends/supabase/deploy.js +56 -3
- package/lib/scaffold/backends/supabase/patch/lib/core/data/api/storage_api.dart +5 -11
- package/lib/scaffold/backends/supabase/patch/lib/core/data/entities/user_entity.dart +2 -2
- package/lib/scaffold/backends/supabase/patch/lib/features/notifications/api/device_api.dart +31 -1
- package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/api/entities/user_info_entity.dart +1 -1
- package/lib/scaffold/backends/supabase/patch/lib/features/subscription/api/entities/subscription_entity.dart +1 -1
- package/lib/scaffold/backends/supabase/pubspec.yaml.tpl +2 -0
- package/lib/scaffold/catalog.js +2 -2
- package/lib/scaffold/generate.js +19 -3
- package/lib/scaffold/shared/generator-utils.js +265 -55
- package/lib/scaffold/shared/post-build.js +11 -0
- package/lib/utils/i18n/messages-en.js +5 -1
- package/lib/utils/i18n/messages-es.js +5 -1
- package/lib/utils/i18n/messages-pt.js +5 -1
- package/package.json +1 -1
- package/templates/firebase/lib/components/kasy_sidebar_pro.dart +3 -1
- package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +38 -128
- package/templates/firebase/lib/core/bottom_menu/bottom_router.dart +6 -125
- package/templates/firebase/lib/core/states/user_state_notifier.dart +8 -10
- package/templates/firebase/lib/features/home/home_components_page.dart +8 -14
- package/templates/firebase/lib/features/home/home_page.dart +7 -8
- package/templates/firebase/lib/router.dart +60 -0
- package/templates/firebase/test/core/bottom_menu/detail_route_menu_test.dart +57 -0
- package/lib/scaffold/backends/api/patch/lib/core/rating/widgets/review_popup.dart +0 -211
- package/lib/scaffold/backends/api/patch/lib/features/notifications/providers/models/notification.dart +0 -185
- package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +0 -73
- package/lib/scaffold/backends/api/patch/lib/main.dart +0 -275
- package/lib/scaffold/backends/api/patch/lib/router.dart +0 -133
- package/lib/scaffold/backends/supabase/patch/lib/core/rating/widgets/review_popup.dart +0 -211
- package/lib/scaffold/backends/supabase/patch/lib/features/feedbacks/ui/component/add_feature_form.dart +0 -199
- package/lib/scaffold/backends/supabase/patch/lib/features/notifications/providers/models/notification.dart +0 -174
- package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +0 -73
- package/lib/scaffold/backends/supabase/patch/lib/main.dart +0 -307
- package/lib/scaffold/backends/supabase/patch/lib/router.dart +0 -133
|
@@ -524,7 +524,7 @@ module.exports = {
|
|
|
524
524
|
'new.firebase.module.sentry': '🚨 Crash Reports (Sentry)',
|
|
525
525
|
'new.firebase.module.analytics': '📊 Analytics (Mixpanel)',
|
|
526
526
|
'new.firebase.module.facebook': '👤 Facebook (Login + Ads)',
|
|
527
|
-
'new.firebase.module.web': '🌐 Web Support (PWA
|
|
527
|
+
'new.firebase.module.web': '🌐 Web Support (PWA)',
|
|
528
528
|
'new.firebase.module.widget': '📱 Home Widget (iOS/Android)',
|
|
529
529
|
'new.firebase.module.llm_chat': '🤖 AI Chat (OpenAI/Gemini)',
|
|
530
530
|
'new.firebase.module.local_notifications': '🔔 Local Reminders (sem servidor)',
|
|
@@ -692,6 +692,10 @@ module.exports = {
|
|
|
692
692
|
'new.google.refreshConfigs': 'Atualizando google-services.json e GoogleService-Info.plist com Client IDs do Google…',
|
|
693
693
|
'new.google.manualHint': 'Login com Google: ative manualmente no Console (provedor Google):',
|
|
694
694
|
'new.google.manualHint.noEmail': 'Login com Google: não consegui detectar um e-mail de suporte (gcloud sem conta). Ative manualmente no Console:',
|
|
695
|
+
'new.google.supabaseManual': 'Login com Google: client criado, mas o secret ainda não estava disponível. Ative depois no painel do Supabase (Authentication > Providers > Google).',
|
|
696
|
+
'new.fcm.ok': 'gerada automaticamente',
|
|
697
|
+
'new.fcm.failSupabase': 'não gerada (permissão do GCP ainda propagando); defina FIREBASE_SERVICE_ACCOUNT_JSON nos secrets do Supabase',
|
|
698
|
+
'new.fcm.failApi': 'não gerada (permissão do GCP ainda propagando); rode o comando de novo em alguns minutos',
|
|
695
699
|
'new.sha1.registering': 'Registrando SHA-1 para Google Sign-In (Android)…',
|
|
696
700
|
'new.sha1.failed': 'SHA-1 não adicionado automaticamente: {error}',
|
|
697
701
|
'new.sha1.manual': 'Adicione manualmente para o Google Sign-In funcionar no Android:',
|
package/package.json
CHANGED
|
@@ -309,7 +309,9 @@ class _KasySidebarProState extends State<KasySidebarPro> {
|
|
|
309
309
|
// Use a Column that fills the sidebar height.
|
|
310
310
|
// Top section scrolls if content overflows; bottom items (Help + Logout)
|
|
311
311
|
// stay pinned to the bottom via Spacer in the outer Column.
|
|
312
|
-
|
|
312
|
+
// SizedBox.expand (not Positioned.fill) because the parent here is a
|
|
313
|
+
// DecoratedBox, not a Stack — Positioned only works as a direct Stack child.
|
|
314
|
+
return SizedBox.expand(
|
|
313
315
|
child: Column(
|
|
314
316
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
315
317
|
children: [
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import 'package:bart/bart.dart' as bart;
|
|
2
|
-
import 'package:bart/bart/bart_bottombar_actions.dart';
|
|
3
2
|
import 'package:flutter/foundation.dart';
|
|
4
3
|
import 'package:flutter/material.dart';
|
|
5
4
|
import 'package:flutter/services.dart';
|
|
@@ -11,79 +10,18 @@ import 'package:kasy_kit/core/widgets/responsive_layout.dart';
|
|
|
11
10
|
|
|
12
11
|
/// Bottom navigation host powered by Bart (https://pub.dev/packages/bart).
|
|
13
12
|
///
|
|
14
|
-
///
|
|
15
|
-
///
|
|
16
|
-
///
|
|
17
|
-
///
|
|
18
|
-
|
|
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.
|
|
25
|
-
///
|
|
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.
|
|
31
|
-
class BottomMenu extends StatefulWidget {
|
|
13
|
+
/// [ResponsiveLayout] swaps between three [bart.BartScaffold]s (small / medium /
|
|
14
|
+
/// large) based on width. The bottom bar shows on every tab; detail screens
|
|
15
|
+
/// (Features, Components, …) are pushed on the root navigator from the pages
|
|
16
|
+
/// themselves, so they cover this menu and there's nothing to toggle here.
|
|
17
|
+
class BottomMenu extends StatelessWidget {
|
|
32
18
|
final String? initialRoute;
|
|
33
19
|
|
|
34
20
|
const BottomMenu({super.key, this.initialRoute});
|
|
35
21
|
|
|
36
|
-
@override
|
|
37
|
-
State<BottomMenu> createState() => _BottomMenuState();
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
class _BottomMenuState extends State<BottomMenu>
|
|
41
|
-
with WidgetsBindingObserver, BartNotifier {
|
|
42
|
-
final ValueNotifier<int> _innerRouteDepth = ValueNotifier<int>(0);
|
|
43
|
-
|
|
44
|
-
@override
|
|
45
|
-
void initState() {
|
|
46
|
-
super.initState();
|
|
47
|
-
WidgetsBinding.instance.addObserver(this);
|
|
48
|
-
_innerRouteDepth.addListener(_scheduleSync);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
@override
|
|
52
|
-
void dispose() {
|
|
53
|
-
_innerRouteDepth.removeListener(_scheduleSync);
|
|
54
|
-
_innerRouteDepth.dispose();
|
|
55
|
-
WidgetsBinding.instance.removeObserver(this);
|
|
56
|
-
super.dispose();
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
@override
|
|
60
|
-
void didChangeAppLifecycleState(AppLifecycleState state) {
|
|
61
|
-
super.didChangeAppLifecycleState(state);
|
|
62
|
-
if (state == AppLifecycleState.resumed) {
|
|
63
|
-
_scheduleSync();
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
void _scheduleSync() {
|
|
68
|
-
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
69
|
-
if (!mounted) return;
|
|
70
|
-
if (_innerRouteDepth.value > 0) {
|
|
71
|
-
hideBottomBar(context);
|
|
72
|
-
} else {
|
|
73
|
-
showBottomBar(context);
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
|
|
78
22
|
@override
|
|
79
23
|
Widget build(BuildContext context) {
|
|
80
|
-
|
|
81
|
-
// may have been freshly instantiated (see class doc).
|
|
82
|
-
_scheduleSync();
|
|
83
|
-
|
|
84
|
-
final String? resolvedInitialRoute = _resolveInitialRoute(
|
|
85
|
-
widget.initialRoute,
|
|
86
|
-
);
|
|
24
|
+
final String? resolvedInitialRoute = _resolveInitialRoute(initialRoute);
|
|
87
25
|
final bool showBottomBarOnStart = _shouldShowBottomBarOnStart(
|
|
88
26
|
resolvedInitialRoute,
|
|
89
27
|
);
|
|
@@ -91,44 +29,39 @@ class _BottomMenuState extends State<BottomMenu>
|
|
|
91
29
|
backgroundColor: context.colors.background,
|
|
92
30
|
);
|
|
93
31
|
|
|
94
|
-
return
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
sideBarOptions: bart.CustomSideBarOptions(
|
|
117
|
-
sideBarBuilder: (routes, onTap, current) =>
|
|
118
|
-
const KasySidebarPro(),
|
|
119
|
-
),
|
|
32
|
+
return AnnotatedRegion<SystemUiOverlayStyle>(
|
|
33
|
+
value: switch (Theme.brightnessOf(context)) {
|
|
34
|
+
Brightness.dark => SystemUiOverlayStyle.light,
|
|
35
|
+
Brightness.light => SystemUiOverlayStyle.dark,
|
|
36
|
+
},
|
|
37
|
+
child: ResponsiveLayout(
|
|
38
|
+
small: bart.BartScaffold(
|
|
39
|
+
routesBuilder: subRoutes,
|
|
40
|
+
bottomBar: kasyPaddedSurfaceBottomBar(),
|
|
41
|
+
initialRoute: resolvedInitialRoute,
|
|
42
|
+
showBottomBarOnStart: showBottomBarOnStart,
|
|
43
|
+
scaffoldOptions: scaffoldOptions,
|
|
44
|
+
),
|
|
45
|
+
// medium (768–1024 px): KasySidebarPro auto-collapses at this width
|
|
46
|
+
medium: bart.BartScaffold(
|
|
47
|
+
routesBuilder: subRoutes,
|
|
48
|
+
bottomBar: kasyPaddedSurfaceBottomBar(),
|
|
49
|
+
initialRoute: resolvedInitialRoute,
|
|
50
|
+
showBottomBarOnStart: showBottomBarOnStart,
|
|
51
|
+
scaffoldOptions: scaffoldOptions,
|
|
52
|
+
sideBarOptions: bart.CustomSideBarOptions(
|
|
53
|
+
sideBarBuilder: (routes, onTap, current) => const KasySidebarPro(),
|
|
120
54
|
),
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
),
|
|
55
|
+
),
|
|
56
|
+
// large (1024 px+): full expanded sidebar
|
|
57
|
+
large: bart.BartScaffold(
|
|
58
|
+
routesBuilder: subRoutes,
|
|
59
|
+
bottomBar: kasyPaddedSurfaceBottomBar(),
|
|
60
|
+
initialRoute: resolvedInitialRoute,
|
|
61
|
+
showBottomBarOnStart: showBottomBarOnStart,
|
|
62
|
+
scaffoldOptions: scaffoldOptions,
|
|
63
|
+
sideBarOptions: bart.CustomSideBarOptions(
|
|
64
|
+
sideBarBuilder: (routes, onTap, current) => const KasySidebarPro(),
|
|
132
65
|
),
|
|
133
66
|
),
|
|
134
67
|
),
|
|
@@ -176,26 +109,3 @@ class _BottomMenuState extends State<BottomMenu>
|
|
|
176
109
|
return segments.length < 2;
|
|
177
110
|
}
|
|
178
111
|
}
|
|
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
|
-
}
|
|
@@ -1,22 +1,20 @@
|
|
|
1
1
|
import 'package:bart/bart.dart';
|
|
2
2
|
import 'package:flutter/material.dart';
|
|
3
3
|
import 'package:kasy_kit/components/kasy_app_bar.dart';
|
|
4
|
-
import 'package:kasy_kit/core/bottom_menu/bart_inner_paths.dart';
|
|
5
|
-
import 'package:kasy_kit/core/bottom_menu/bottom_menu.dart';
|
|
6
4
|
import 'package:kasy_kit/core/bottom_menu/notification_bottom_item.dart';
|
|
7
5
|
import 'package:kasy_kit/core/navigation/kasy_navigation_config.dart';
|
|
8
6
|
import 'package:kasy_kit/core/navigation/kasy_route_transition.dart';
|
|
9
7
|
import 'package:kasy_kit/core/rating/widgets/rate_banner.dart';
|
|
10
8
|
import 'package:kasy_kit/core/theme/theme.dart';
|
|
11
|
-
import 'package:kasy_kit/features/home/design_system_page.dart';
|
|
12
|
-
import 'package:kasy_kit/features/home/home_components_page.dart';
|
|
13
|
-
import 'package:kasy_kit/features/home/home_components_preview_page.dart';
|
|
14
|
-
import 'package:kasy_kit/features/home/home_features_page.dart';
|
|
15
9
|
import 'package:kasy_kit/features/home/home_page.dart';
|
|
16
10
|
import 'package:kasy_kit/features/notifications/ui/notifications_page.dart';
|
|
17
11
|
import 'package:kasy_kit/features/settings/settings_page.dart';
|
|
18
12
|
import 'package:kasy_kit/i18n/translations.g.dart';
|
|
19
13
|
|
|
14
|
+
/// Bart tab routes (bottom bar). Detail screens reachable from a tab — Features,
|
|
15
|
+
/// Components, Design System, component previews — are NOT inner routes here:
|
|
16
|
+
/// they're pushed on the ROOT navigator (see home_page.dart / home_components_page.dart)
|
|
17
|
+
/// so they open full-screen without the bottom bar and return to the tab intact.
|
|
20
18
|
List<BartMenuRoute> subRoutes() {
|
|
21
19
|
return [
|
|
22
20
|
BartMenuRoute.bottomBar(
|
|
@@ -51,101 +49,9 @@ List<BartMenuRoute> subRoutes() {
|
|
|
51
49
|
transitionDuration: bottomBarTransitionDuration,
|
|
52
50
|
transitionsBuilder: bottomBarTransition,
|
|
53
51
|
),
|
|
54
|
-
BartMenuRoute.innerRoute(
|
|
55
|
-
path: bartInnerPath('home', 'features'),
|
|
56
|
-
showBottomBar: false,
|
|
57
|
-
pageBuilder: (_, _, _) =>
|
|
58
|
-
const _HomeInnerRoute(child: HomeFeaturesPage()),
|
|
59
|
-
transitionDuration: bottomBarTransitionDuration,
|
|
60
|
-
transitionsBuilder: bottomBarTransition,
|
|
61
|
-
),
|
|
62
|
-
BartMenuRoute.innerRoute(
|
|
63
|
-
path: bartInnerPath('home', 'components'),
|
|
64
|
-
showBottomBar: false,
|
|
65
|
-
pageBuilder: (_, _, _) =>
|
|
66
|
-
const _HomeInnerRoute(child: HomeComponentsPage()),
|
|
67
|
-
transitionDuration: bottomBarTransitionDuration,
|
|
68
|
-
transitionsBuilder: bottomBarTransition,
|
|
69
|
-
),
|
|
70
|
-
BartMenuRoute.innerRoute(
|
|
71
|
-
path: bartInnerPath('home', 'design-system'),
|
|
72
|
-
showBottomBar: false,
|
|
73
|
-
pageBuilder: (_, _, _) =>
|
|
74
|
-
const _HomeInnerRoute(child: DesignSystemPage()),
|
|
75
|
-
transitionDuration: bottomBarTransitionDuration,
|
|
76
|
-
transitionsBuilder: bottomBarTransition,
|
|
77
|
-
),
|
|
78
|
-
BartMenuRoute.innerRoute(
|
|
79
|
-
path: bartInnerPath('home', 'components-preview'),
|
|
80
|
-
showBottomBar: false,
|
|
81
|
-
pageBuilder: (_, _, settings) {
|
|
82
|
-
final extra = settings?.arguments;
|
|
83
|
-
if (extra is! HomeComponentPreviewRouteExtra) {
|
|
84
|
-
return const PageFake();
|
|
85
|
-
}
|
|
86
|
-
return _HomeInnerRoute(
|
|
87
|
-
child: HomeComponentsPreviewPage(
|
|
88
|
-
title: extra.title,
|
|
89
|
-
variants: extra.variants,
|
|
90
|
-
),
|
|
91
|
-
);
|
|
92
|
-
},
|
|
93
|
-
transitionDuration: bottomBarTransitionDuration,
|
|
94
|
-
transitionsBuilder: bottomBarTransition,
|
|
95
|
-
),
|
|
96
|
-
BartMenuRoute.innerRoute(
|
|
97
|
-
path: bartInnerPath('home', 'search'),
|
|
98
|
-
showBottomBar: false,
|
|
99
|
-
pageBuilder: (_, _, _) => const _HomeInnerRoute(child: PageFake()),
|
|
100
|
-
transitionDuration: bottomBarTransitionDuration,
|
|
101
|
-
transitionsBuilder: bottomBarTransition,
|
|
102
|
-
),
|
|
103
|
-
BartMenuRoute.innerRoute(
|
|
104
|
-
path: bartInnerPath('home', 'search/subscribe'),
|
|
105
|
-
showBottomBar: false,
|
|
106
|
-
pageBuilder: (_, _, _) => const _HomeInnerRoute(child: PageFake()),
|
|
107
|
-
transitionDuration: bottomBarTransitionDuration,
|
|
108
|
-
transitionsBuilder: bottomBarTransition,
|
|
109
|
-
),
|
|
110
52
|
];
|
|
111
53
|
}
|
|
112
54
|
|
|
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
|
-
final Widget child;
|
|
119
|
-
|
|
120
|
-
const _HomeInnerRoute({required this.child});
|
|
121
|
-
|
|
122
|
-
@override
|
|
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;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
55
|
/// Placeholder page for the wishlist tab.
|
|
150
56
|
/// Replace this with your own implementation.
|
|
151
57
|
class WishlistPage extends StatelessWidget {
|
|
@@ -159,8 +65,8 @@ class WishlistPage extends StatelessWidget {
|
|
|
159
65
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
160
66
|
children: [
|
|
161
67
|
KasyAppBar(
|
|
162
|
-
|
|
163
|
-
|
|
68
|
+
title: context.t.navigation.wishlist,
|
|
69
|
+
style: KasyAppBarStyle.rootTab,
|
|
164
70
|
),
|
|
165
71
|
Expanded(
|
|
166
72
|
child: Padding(
|
|
@@ -224,31 +130,6 @@ class WishlistPage extends StatelessWidget {
|
|
|
224
130
|
}
|
|
225
131
|
}
|
|
226
132
|
|
|
227
|
-
/// This is a fake page to show how to use Bart
|
|
228
|
-
/// You can replace it with your own pages
|
|
229
|
-
class PageFake extends StatelessWidget {
|
|
230
|
-
final Color? color;
|
|
231
|
-
|
|
232
|
-
const PageFake({this.color, super.key});
|
|
233
|
-
|
|
234
|
-
@override
|
|
235
|
-
Widget build(BuildContext context) {
|
|
236
|
-
return SafeArea(
|
|
237
|
-
// ignore: use_colored_box
|
|
238
|
-
child: Container(
|
|
239
|
-
color: color ?? context.colors.background,
|
|
240
|
-
child: Column(
|
|
241
|
-
children: [
|
|
242
|
-
const RateBanner(forceInDebug: true),
|
|
243
|
-
const SizedBox(height: 100),
|
|
244
|
-
Opacity(opacity: 0.5, child: Text(context.t.bottom_router.fake_page_text)),
|
|
245
|
-
],
|
|
246
|
-
),
|
|
247
|
-
),
|
|
248
|
-
);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
133
|
Widget bottomBarTransition(
|
|
253
134
|
BuildContext context,
|
|
254
135
|
Animation<double> animation,
|
|
@@ -267,19 +267,17 @@ class UserStateNotifier extends _$UserStateNotifier implements OnStartService {
|
|
|
267
267
|
}
|
|
268
268
|
if (user == null) {
|
|
269
269
|
// User document not found after login. Possible causes:
|
|
270
|
-
// -
|
|
271
|
-
// -
|
|
272
|
-
// -
|
|
273
|
-
//
|
|
270
|
+
// - Cloud Functions not deployed yet (run `kasy deploy` to fix)
|
|
271
|
+
// - onUserRegistration Cloud Function failed
|
|
272
|
+
// - Supabase handle_new_user trigger not run yet (race) or failed
|
|
273
|
+
// Fall back to keeping the user authenticated with their auth ID so
|
|
274
|
+
// the app works locally before `kasy deploy` is run.
|
|
274
275
|
_logger.e(
|
|
275
276
|
'⚠️ User profile not found in database after sign-in.\n'
|
|
276
|
-
'
|
|
277
|
+
' If this is a new project, run: kasy deploy\n'
|
|
278
|
+
' to deploy the backend and create the user document.',
|
|
277
279
|
);
|
|
278
|
-
|
|
279
|
-
state = state.copyWith(user: const User.anonymous());
|
|
280
|
-
if (mode == AuthenticationMode.anonymous) {
|
|
281
|
-
await _loadAnonymousState();
|
|
282
|
-
}
|
|
280
|
+
state = state.copyWith(user: User.anonymous(id: credentials.id));
|
|
283
281
|
return;
|
|
284
282
|
}
|
|
285
283
|
// If the Firestore document is anonymous (no email) but Firebase Auth
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import 'package:flutter/material.dart';
|
|
2
|
+
import 'package:go_router/go_router.dart';
|
|
2
3
|
import 'package:kasy_kit/components/kasy_app_bar.dart';
|
|
3
|
-
import 'package:kasy_kit/core/bottom_menu/bart_inner_paths.dart';
|
|
4
4
|
import 'package:kasy_kit/core/haptics/kasy_haptics.dart';
|
|
5
5
|
import 'package:kasy_kit/core/theme/theme.dart';
|
|
6
6
|
import 'package:kasy_kit/core/widgets/kasy_hover.dart';
|
|
7
7
|
import 'package:kasy_kit/core/widgets/kasy_scroll_behavior.dart';
|
|
8
|
-
import 'package:kasy_kit/features/home/home_components_preview_page.dart';
|
|
9
8
|
import 'package:kasy_kit/features/home/home_components_preview_registry.dart';
|
|
10
9
|
|
|
11
10
|
/// UI-kit catalog, reachable from the home dashboard.
|
|
@@ -66,22 +65,17 @@ class HomeComponentsPage extends StatelessWidget {
|
|
|
66
65
|
onTap: () {
|
|
67
66
|
KasyHaptics.selection(context);
|
|
68
67
|
if (row.name == 'Design System') {
|
|
69
|
-
|
|
70
|
-
bartInnerPath('home', 'design-system'),
|
|
71
|
-
);
|
|
68
|
+
context.push('/design-system');
|
|
72
69
|
return;
|
|
73
70
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
71
|
+
// Skip rows without a preview yet (registry returns
|
|
72
|
+
// null); otherwise open /components/:name, which the
|
|
73
|
+
// router resolves back to this component's preview.
|
|
74
|
+
if (getComponentPreviewDefinition(row.name) == null) {
|
|
77
75
|
return;
|
|
78
76
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
arguments: HomeComponentPreviewRouteExtra(
|
|
82
|
-
title: definition.title,
|
|
83
|
-
variants: definition.variants,
|
|
84
|
-
),
|
|
77
|
+
context.push(
|
|
78
|
+
'/components/${Uri.encodeComponent(row.name)}',
|
|
85
79
|
);
|
|
86
80
|
},
|
|
87
81
|
child: Padding(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import 'package:flutter/material.dart';
|
|
2
2
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
3
|
+
import 'package:go_router/go_router.dart';
|
|
3
4
|
import 'package:kasy_kit/components/kasy_app_bar.dart';
|
|
4
|
-
import 'package:kasy_kit/core/bottom_menu/bart_inner_paths.dart';
|
|
5
5
|
import 'package:kasy_kit/core/haptics/kasy_haptics.dart';
|
|
6
6
|
import 'package:kasy_kit/core/states/components/maybe_ask_rating.dart';
|
|
7
7
|
import 'package:kasy_kit/core/states/components/maybe_show_update_bottom_sheet.dart';
|
|
@@ -13,7 +13,7 @@ import 'package:kasy_kit/features/notifications/shared/att_permission.dart';
|
|
|
13
13
|
import 'package:kasy_kit/features/subscription/shared/maybeshow_premium.dart';
|
|
14
14
|
import 'package:kasy_kit/i18n/translations.g.dart';
|
|
15
15
|
|
|
16
|
-
/// Home hub with dashboard cards
|
|
16
|
+
/// Home hub with dashboard cards linking to the Features and Components screens.
|
|
17
17
|
class HomePage extends ConsumerWidget {
|
|
18
18
|
const HomePage({super.key});
|
|
19
19
|
|
|
@@ -59,9 +59,10 @@ class HomePage extends ConsumerWidget {
|
|
|
59
59
|
gradient: _featuresGradient(context, isDark),
|
|
60
60
|
onOpen: () {
|
|
61
61
|
KasyHaptics.medium(context);
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
// Top-level go_router route: opens full-screen on the
|
|
63
|
+
// root navigator (above the BottomMenu, no bottom bar)
|
|
64
|
+
// and pops back to home with the menu intact.
|
|
65
|
+
context.push('/features');
|
|
65
66
|
},
|
|
66
67
|
);
|
|
67
68
|
final componentsCard = _DashboardGradientCard(
|
|
@@ -74,9 +75,7 @@ class HomePage extends ConsumerWidget {
|
|
|
74
75
|
gradient: _componentsGradient(context, isDark),
|
|
75
76
|
onOpen: () {
|
|
76
77
|
KasyHaptics.medium(context);
|
|
77
|
-
|
|
78
|
-
bartInnerPath('home', 'components'),
|
|
79
|
-
);
|
|
78
|
+
context.push('/components');
|
|
80
79
|
},
|
|
81
80
|
);
|
|
82
81
|
if (isWide) {
|
|
@@ -15,6 +15,11 @@ import 'package:kasy_kit/features/authentication/ui/recover_password_page.dart';
|
|
|
15
15
|
import 'package:kasy_kit/features/authentication/ui/signin_page.dart';
|
|
16
16
|
import 'package:kasy_kit/features/authentication/ui/signup_page.dart';
|
|
17
17
|
import 'package:kasy_kit/features/feedbacks/ui/feedback_page.dart';
|
|
18
|
+
import 'package:kasy_kit/features/home/design_system_page.dart';
|
|
19
|
+
import 'package:kasy_kit/features/home/home_components_page.dart';
|
|
20
|
+
import 'package:kasy_kit/features/home/home_components_preview_page.dart';
|
|
21
|
+
import 'package:kasy_kit/features/home/home_components_preview_registry.dart';
|
|
22
|
+
import 'package:kasy_kit/features/home/home_features_page.dart';
|
|
18
23
|
import 'package:kasy_kit/features/llm_chat/llm_chat_page.dart';
|
|
19
24
|
import 'package:kasy_kit/features/local_reminder/ui/reminder_page.dart';
|
|
20
25
|
import 'package:kasy_kit/features/onboarding/ui/onboarding_page.dart';
|
|
@@ -73,6 +78,61 @@ GoRouter generateRouter({
|
|
|
73
78
|
),
|
|
74
79
|
),
|
|
75
80
|
),
|
|
81
|
+
// Home showcase detail screens. These are TOP-LEVEL routes (siblings of
|
|
82
|
+
// '/', not children of the BottomMenu shell), so go_router renders them on
|
|
83
|
+
// the root navigator: full-screen, above the bottom bar, URL-addressable.
|
|
84
|
+
// Returning pops back to the tab with its menu intact.
|
|
85
|
+
GoRoute(
|
|
86
|
+
name: 'features',
|
|
87
|
+
path: '/features',
|
|
88
|
+
pageBuilder: (context, state) => kasyTransitionPage(
|
|
89
|
+
key: state.pageKey,
|
|
90
|
+
child: const HomeFeaturesPage(),
|
|
91
|
+
),
|
|
92
|
+
),
|
|
93
|
+
GoRoute(
|
|
94
|
+
name: 'design_system',
|
|
95
|
+
path: '/design-system',
|
|
96
|
+
pageBuilder: (context, state) => kasyTransitionPage(
|
|
97
|
+
key: state.pageKey,
|
|
98
|
+
child: const DesignSystemPage(),
|
|
99
|
+
),
|
|
100
|
+
),
|
|
101
|
+
GoRoute(
|
|
102
|
+
name: 'components',
|
|
103
|
+
path: '/components',
|
|
104
|
+
pageBuilder: (context, state) => kasyTransitionPage(
|
|
105
|
+
key: state.pageKey,
|
|
106
|
+
child: const HomeComponentsPage(),
|
|
107
|
+
),
|
|
108
|
+
routes: [
|
|
109
|
+
// /components/:name — a single component's preview, looked up from the
|
|
110
|
+
// registry so the URL alone restores the screen on web reload.
|
|
111
|
+
GoRoute(
|
|
112
|
+
name: 'component_preview',
|
|
113
|
+
path: ':name',
|
|
114
|
+
pageBuilder: (context, state) {
|
|
115
|
+
final ComponentPreviewDefinition? definition =
|
|
116
|
+
getComponentPreviewDefinition(
|
|
117
|
+
state.pathParameters['name'] ?? '',
|
|
118
|
+
);
|
|
119
|
+
if (definition == null) {
|
|
120
|
+
return kasyTransitionPage(
|
|
121
|
+
key: state.pageKey,
|
|
122
|
+
child: const PageNotFound(),
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
return kasyTransitionPage(
|
|
126
|
+
key: state.pageKey,
|
|
127
|
+
child: HomeComponentsPreviewPage(
|
|
128
|
+
title: definition.title,
|
|
129
|
+
variants: definition.variants,
|
|
130
|
+
),
|
|
131
|
+
);
|
|
132
|
+
},
|
|
133
|
+
),
|
|
134
|
+
],
|
|
135
|
+
),
|
|
76
136
|
GoRoute(
|
|
77
137
|
name: 'onboarding',
|
|
78
138
|
path: '/onboarding',
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import 'package:flutter/material.dart';
|
|
2
|
+
import 'package:flutter_test/flutter_test.dart';
|
|
3
|
+
import 'package:go_router/go_router.dart';
|
|
4
|
+
|
|
5
|
+
/// Regression test for the Features/Components navigation.
|
|
6
|
+
///
|
|
7
|
+
/// These detail screens are TOP-LEVEL go_router routes (siblings of the home
|
|
8
|
+
/// shell, not children of it), so pushing them renders full-screen on the root
|
|
9
|
+
/// navigator: the bottom bar is gone while the detail is open, and it returns
|
|
10
|
+
/// intact when the user pops back to the tab.
|
|
11
|
+
void main() {
|
|
12
|
+
testWidgets('a top-level route opens full-screen over the bottom bar, '
|
|
13
|
+
'and the bar returns on pop', (tester) async {
|
|
14
|
+
final router = GoRouter(
|
|
15
|
+
routes: [
|
|
16
|
+
// Home shell — owns the bottom bar (like BottomMenu).
|
|
17
|
+
GoRoute(
|
|
18
|
+
path: '/',
|
|
19
|
+
builder: (context, state) => Scaffold(
|
|
20
|
+
bottomNavigationBar: const Text('MENU'),
|
|
21
|
+
body: Center(
|
|
22
|
+
child: ElevatedButton(
|
|
23
|
+
onPressed: () => context.push('/features'),
|
|
24
|
+
child: const Text('open'),
|
|
25
|
+
),
|
|
26
|
+
),
|
|
27
|
+
),
|
|
28
|
+
),
|
|
29
|
+
// Detail screen — sibling of '/', so it renders on the root navigator.
|
|
30
|
+
GoRoute(
|
|
31
|
+
path: '/features',
|
|
32
|
+
builder: (context, state) => const Scaffold(
|
|
33
|
+
body: Center(child: Text('DETAIL')),
|
|
34
|
+
),
|
|
35
|
+
),
|
|
36
|
+
],
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
await tester.pumpWidget(MaterialApp.router(routerConfig: router));
|
|
40
|
+
|
|
41
|
+
// Home tab: bottom bar visible, no detail.
|
|
42
|
+
expect(find.text('MENU'), findsOneWidget);
|
|
43
|
+
expect(find.text('DETAIL'), findsNothing);
|
|
44
|
+
|
|
45
|
+
// Open Features.
|
|
46
|
+
await tester.tap(find.text('open'));
|
|
47
|
+
await tester.pumpAndSettle();
|
|
48
|
+
expect(find.text('DETAIL'), findsOneWidget);
|
|
49
|
+
expect(find.text('MENU'), findsNothing); // covered — no bottom bar here
|
|
50
|
+
|
|
51
|
+
// Back: the bottom bar is intact.
|
|
52
|
+
Navigator.of(tester.element(find.text('DETAIL'))).maybePop();
|
|
53
|
+
await tester.pumpAndSettle();
|
|
54
|
+
expect(find.text('DETAIL'), findsNothing);
|
|
55
|
+
expect(find.text('MENU'), findsOneWidget);
|
|
56
|
+
});
|
|
57
|
+
}
|