kasy-cli 1.19.3 → 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/bin/kasy.js +1 -0
- package/lib/commands/new.js +87 -37
- package/lib/commands/run.js +14 -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/firebase/setup-from-scratch.js +91 -2
- 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/engine.js +5 -0
- package/lib/scaffold/generate.js +23 -3
- package/lib/scaffold/shared/generator-utils.js +303 -56
- package/lib/scaffold/shared/post-build.js +11 -0
- package/lib/utils/i18n/messages-en.js +6 -1
- package/lib/utils/i18n/messages-es.js +6 -1
- package/lib/utils/i18n/messages-pt.js +6 -1
- package/package.json +1 -1
- package/templates/firebase/android/app/src/main/res/drawable/background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night/background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-v21/background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-v21/background.png +0 -0
- package/templates/firebase/android/app/src/main/res/values-night-v31/styles.xml +1 -1
- package/templates/firebase/android/app/src/main/res/values-v31/styles.xml +1 -1
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png +0 -0
- package/templates/firebase/lib/components/kasy_date_picker.dart +14 -8
- package/templates/firebase/lib/components/kasy_sidebar_pro.dart +1150 -0
- package/templates/firebase/lib/components/kasy_tabs.dart +156 -43
- package/templates/firebase/lib/components/kasy_text_field.dart +37 -34
- package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +13 -82
- package/templates/firebase/lib/core/bottom_menu/bottom_router.dart +6 -102
- package/templates/firebase/lib/core/bottom_menu/kasy_bottom_bar_factory.dart +8 -1
- package/templates/firebase/lib/core/dev_inspector/dev_inspector.dart +433 -243
- package/templates/firebase/lib/core/dev_inspector/dev_inspector_service.dart +198 -83
- package/templates/firebase/lib/core/icons/kasy_icons.dart +1 -0
- package/templates/firebase/lib/core/states/user_state_notifier.dart +8 -10
- package/templates/firebase/lib/core/theme/colors.dart +6 -2
- package/templates/firebase/lib/core/web_device_preview/web_device_preview.dart +119 -19
- package/templates/firebase/lib/core/widgets/kasy_hover.dart +68 -27
- package/templates/firebase/lib/features/home/home_components_page.dart +11 -14
- package/templates/firebase/lib/features/home/home_components_preview_registry.dart +121 -66
- package/templates/firebase/lib/features/home/home_page.dart +7 -8
- package/templates/firebase/lib/features/settings/settings_page.dart +27 -146
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_bottom_sheet.dart +16 -3
- package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +22 -5
- package/templates/firebase/lib/i18n/en.i18n.json +3 -1
- package/templates/firebase/lib/i18n/es.i18n.json +3 -1
- package/templates/firebase/lib/i18n/pt.i18n.json +3 -1
- package/templates/firebase/lib/router.dart +60 -0
- package/templates/firebase/pubspec.yaml +6 -4
- package/templates/firebase/test/core/bottom_menu/detail_route_menu_test.dart +57 -0
- package/templates/firebase/web/index.html +7 -17
- 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
- package/templates/firebase/lib/firebase_options.dart +0 -75
|
@@ -429,171 +429,52 @@ class HapticFeedbackSwitcher extends ConsumerWidget {
|
|
|
429
429
|
class ThemeSwitcher extends StatelessWidget {
|
|
430
430
|
const ThemeSwitcher({super.key});
|
|
431
431
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
case ThemeMode.light:
|
|
439
|
-
return KasyIcons.lightMode;
|
|
440
|
-
}
|
|
441
|
-
}
|
|
432
|
+
// Tab order mirrors the trailing pill: system, light, dark.
|
|
433
|
+
static const List<ThemeMode> _modes = <ThemeMode>[
|
|
434
|
+
ThemeMode.system,
|
|
435
|
+
ThemeMode.light,
|
|
436
|
+
ThemeMode.dark,
|
|
437
|
+
];
|
|
442
438
|
|
|
443
439
|
@override
|
|
444
440
|
Widget build(BuildContext context) {
|
|
445
441
|
final tr = context.t.settings;
|
|
446
442
|
final theme = ThemeProvider.of(context);
|
|
447
|
-
final
|
|
448
|
-
(
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
label: tr.theme_option_system,
|
|
443
|
+
final items = <KasyTabItem>[
|
|
444
|
+
KasyTabItem(
|
|
445
|
+
icon: KasyIcons.monitor,
|
|
446
|
+
semanticLabel: tr.theme_option_system,
|
|
452
447
|
),
|
|
453
|
-
(
|
|
454
|
-
mode: ThemeMode.light,
|
|
448
|
+
KasyTabItem(
|
|
455
449
|
icon: KasyIcons.lightMode,
|
|
456
|
-
|
|
450
|
+
semanticLabel: tr.theme_option_light,
|
|
457
451
|
),
|
|
458
|
-
(
|
|
459
|
-
mode: ThemeMode.dark,
|
|
452
|
+
KasyTabItem(
|
|
460
453
|
icon: KasyIcons.darkMode,
|
|
461
|
-
|
|
454
|
+
semanticLabel: tr.theme_option_dark,
|
|
462
455
|
),
|
|
463
456
|
];
|
|
464
457
|
return Padding(
|
|
465
458
|
padding: const EdgeInsets.symmetric(vertical: KasySpacing.sm),
|
|
466
|
-
child: Column(
|
|
467
|
-
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
468
|
-
children: [
|
|
469
|
-
Row(
|
|
470
|
-
children: [
|
|
471
|
-
Icon(
|
|
472
|
-
_iconFor(theme.mode),
|
|
473
|
-
size: 21,
|
|
474
|
-
color: context.colors.onSurface,
|
|
475
|
-
),
|
|
476
|
-
const SizedBox(width: KasySpacing.sm),
|
|
477
|
-
Expanded(
|
|
478
|
-
child: Text(
|
|
479
|
-
tr.theme_title,
|
|
480
|
-
style: context.textTheme.titleMedium?.copyWith(
|
|
481
|
-
color: context.colors.onSurface,
|
|
482
|
-
),
|
|
483
|
-
),
|
|
484
|
-
),
|
|
485
|
-
],
|
|
486
|
-
),
|
|
487
|
-
const SizedBox(height: KasySpacing.sm),
|
|
488
|
-
_ThemeModeSegmented(
|
|
489
|
-
current: theme.mode,
|
|
490
|
-
options: options,
|
|
491
|
-
onChanged: theme.setMode,
|
|
492
|
-
),
|
|
493
|
-
],
|
|
494
|
-
),
|
|
495
|
-
);
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
class _ThemeModeSegmented extends StatelessWidget {
|
|
500
|
-
const _ThemeModeSegmented({
|
|
501
|
-
required this.current,
|
|
502
|
-
required this.options,
|
|
503
|
-
required this.onChanged,
|
|
504
|
-
});
|
|
505
|
-
|
|
506
|
-
final ThemeMode current;
|
|
507
|
-
final List<({ThemeMode mode, IconData icon, String label})> options;
|
|
508
|
-
final ValueChanged<ThemeMode> onChanged;
|
|
509
|
-
|
|
510
|
-
@override
|
|
511
|
-
Widget build(BuildContext context) {
|
|
512
|
-
final trackColor = context.colors.onSurface.withValues(alpha: 0.06);
|
|
513
|
-
return Container(
|
|
514
|
-
padding: const EdgeInsets.all(3),
|
|
515
|
-
decoration: BoxDecoration(
|
|
516
|
-
color: trackColor,
|
|
517
|
-
borderRadius: BorderRadius.circular(10),
|
|
518
|
-
),
|
|
519
459
|
child: Row(
|
|
520
460
|
children: [
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
461
|
+
Icon(KasyIcons.palette, size: 21, color: context.colors.onSurface),
|
|
462
|
+
const SizedBox(width: KasySpacing.sm),
|
|
463
|
+
Expanded(
|
|
464
|
+
child: Text(
|
|
465
|
+
tr.theme_title,
|
|
466
|
+
style: context.textTheme.titleMedium?.copyWith(
|
|
467
|
+
color: context.colors.onSurface,
|
|
528
468
|
),
|
|
529
469
|
),
|
|
530
|
-
],
|
|
531
|
-
),
|
|
532
|
-
);
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
class _ThemeModeSegment extends StatelessWidget {
|
|
537
|
-
const _ThemeModeSegment({
|
|
538
|
-
required this.icon,
|
|
539
|
-
required this.label,
|
|
540
|
-
required this.selected,
|
|
541
|
-
required this.onTap,
|
|
542
|
-
});
|
|
543
|
-
|
|
544
|
-
final IconData icon;
|
|
545
|
-
final String label;
|
|
546
|
-
final bool selected;
|
|
547
|
-
final VoidCallback onTap;
|
|
548
|
-
|
|
549
|
-
@override
|
|
550
|
-
Widget build(BuildContext context) {
|
|
551
|
-
final selectedBg = context.colors.surface;
|
|
552
|
-
final selectedFg = context.colors.onSurface;
|
|
553
|
-
final unselectedFg = context.colors.muted;
|
|
554
|
-
return Semantics(
|
|
555
|
-
button: true,
|
|
556
|
-
selected: selected,
|
|
557
|
-
label: label,
|
|
558
|
-
child: GestureDetector(
|
|
559
|
-
onTap: onTap,
|
|
560
|
-
behavior: HitTestBehavior.opaque,
|
|
561
|
-
child: AnimatedContainer(
|
|
562
|
-
duration: const Duration(milliseconds: 160),
|
|
563
|
-
curve: Curves.easeOut,
|
|
564
|
-
padding: const EdgeInsets.symmetric(vertical: 8),
|
|
565
|
-
decoration: BoxDecoration(
|
|
566
|
-
color: selected ? selectedBg : Colors.transparent,
|
|
567
|
-
borderRadius: BorderRadius.circular(8),
|
|
568
|
-
boxShadow: selected
|
|
569
|
-
? [
|
|
570
|
-
BoxShadow(
|
|
571
|
-
color: Colors.black.withValues(alpha: 0.06),
|
|
572
|
-
blurRadius: 4,
|
|
573
|
-
offset: const Offset(0, 1),
|
|
574
|
-
),
|
|
575
|
-
]
|
|
576
|
-
: const [],
|
|
577
470
|
),
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
size: 18,
|
|
584
|
-
color: selected ? selectedFg : unselectedFg,
|
|
585
|
-
),
|
|
586
|
-
const SizedBox(height: 2),
|
|
587
|
-
Text(
|
|
588
|
-
label,
|
|
589
|
-
style: context.textTheme.labelSmall?.copyWith(
|
|
590
|
-
color: selected ? selectedFg : unselectedFg,
|
|
591
|
-
fontWeight: selected ? FontWeight.w600 : FontWeight.w500,
|
|
592
|
-
),
|
|
593
|
-
),
|
|
594
|
-
],
|
|
471
|
+
const SizedBox(width: KasySpacing.sm),
|
|
472
|
+
KasyTabs.items(
|
|
473
|
+
items: items,
|
|
474
|
+
selectedIndex: _modes.indexOf(theme.mode),
|
|
475
|
+
onTabSelected: (i) => theme.setMode(_modes[i]),
|
|
595
476
|
),
|
|
596
|
-
|
|
477
|
+
],
|
|
597
478
|
),
|
|
598
479
|
);
|
|
599
480
|
}
|
package/templates/firebase/lib/features/settings/ui/components/admin/admin_bottom_sheet.dart
CHANGED
|
@@ -75,16 +75,26 @@ class _AdminSheet extends ConsumerWidget {
|
|
|
75
75
|
child: Wrap(
|
|
76
76
|
children: [
|
|
77
77
|
ValueListenableBuilder<bool>(
|
|
78
|
-
valueListenable:
|
|
78
|
+
valueListenable: devInspectorEnabledNotifier,
|
|
79
79
|
builder: (context, enabled, _) {
|
|
80
80
|
return SettingsSwitchTile(
|
|
81
81
|
icon: KasyIcons.widgets,
|
|
82
82
|
title: t.settings.admin.inspector_fab_title,
|
|
83
|
+
subtitle:
|
|
84
|
+
'${t.settings.admin.inspector_fab_subtitle_prefix} '
|
|
85
|
+
'${devInspectorShortcutLabel()}',
|
|
83
86
|
value: enabled,
|
|
84
87
|
onChanged: (v) async {
|
|
85
88
|
final p = await SharedPreferences.getInstance();
|
|
86
|
-
await p.setBool(
|
|
87
|
-
|
|
89
|
+
await p.setBool(devInspectorEnabledPrefKey, v);
|
|
90
|
+
devInspectorEnabledNotifier.value = v;
|
|
91
|
+
// Turning the inspector ON closes the admin
|
|
92
|
+
// sheet automatically — otherwise the sheet
|
|
93
|
+
// would absorb taps and the user would be
|
|
94
|
+
// stuck. Turning OFF leaves the sheet open.
|
|
95
|
+
if (v && context.mounted) {
|
|
96
|
+
Navigator.of(context).maybePop();
|
|
97
|
+
}
|
|
88
98
|
},
|
|
89
99
|
);
|
|
90
100
|
},
|
|
@@ -97,6 +107,9 @@ class _AdminSheet extends ConsumerWidget {
|
|
|
97
107
|
return SettingsSwitchTile(
|
|
98
108
|
icon: KasyIcons.phoneAndroid,
|
|
99
109
|
title: t.settings.admin.device_preview_title,
|
|
110
|
+
subtitle:
|
|
111
|
+
'${t.settings.admin.inspector_fab_subtitle_prefix} '
|
|
112
|
+
'${webDevicePreviewShortcutLabel()}',
|
|
100
113
|
value: enabled,
|
|
101
114
|
onChanged: (v) async {
|
|
102
115
|
final navigator = Navigator.of(
|
|
@@ -77,11 +77,13 @@ class SettingsSwitchTile extends StatelessWidget {
|
|
|
77
77
|
required this.title,
|
|
78
78
|
required this.value,
|
|
79
79
|
required this.onChanged,
|
|
80
|
+
this.subtitle,
|
|
80
81
|
this.iconBackgroundColor,
|
|
81
82
|
});
|
|
82
83
|
|
|
83
84
|
final IconData icon;
|
|
84
85
|
final String title;
|
|
86
|
+
final String? subtitle;
|
|
85
87
|
final bool value;
|
|
86
88
|
final ValueChanged<bool> onChanged;
|
|
87
89
|
final Color? iconBackgroundColor;
|
|
@@ -98,11 +100,26 @@ class SettingsSwitchTile extends StatelessWidget {
|
|
|
98
100
|
Icon(icon, size: 21, color: context.colors.onSurface),
|
|
99
101
|
const SizedBox(width: KasySpacing.sm),
|
|
100
102
|
Expanded(
|
|
101
|
-
child:
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
child: Column(
|
|
104
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
105
|
+
mainAxisSize: MainAxisSize.min,
|
|
106
|
+
children: <Widget>[
|
|
107
|
+
Text(
|
|
108
|
+
title,
|
|
109
|
+
style: context.textTheme.titleMedium?.copyWith(
|
|
110
|
+
color: context.colors.onSurface,
|
|
111
|
+
),
|
|
112
|
+
),
|
|
113
|
+
if (subtitle != null && subtitle!.isNotEmpty) ...[
|
|
114
|
+
const SizedBox(height: 2),
|
|
115
|
+
Text(
|
|
116
|
+
subtitle!,
|
|
117
|
+
style: context.textTheme.bodySmall?.copyWith(
|
|
118
|
+
color: context.colors.onSurface.withValues(alpha: 0.55),
|
|
119
|
+
),
|
|
120
|
+
),
|
|
121
|
+
],
|
|
122
|
+
],
|
|
106
123
|
),
|
|
107
124
|
),
|
|
108
125
|
Transform.scale(
|
|
@@ -400,6 +400,7 @@
|
|
|
400
400
|
"home_widgets_panel": "Home Widgets panel",
|
|
401
401
|
"home_widgets_title": "Home Widgets Panel",
|
|
402
402
|
"inspector_fab_title": "Widget inspector",
|
|
403
|
+
"inspector_fab_subtitle_prefix": "Global shortcut:",
|
|
403
404
|
"update_mywidget_title": "📱 Update MyWidget Widget",
|
|
404
405
|
"update_mywidget_desc": "Call manual update for MyWidget widget",
|
|
405
406
|
"paywalls_title": "Paywalls Admin Panel",
|
|
@@ -494,7 +495,8 @@
|
|
|
494
495
|
"deactivate": "Deactivate widget inspector",
|
|
495
496
|
"copyForAi": "Copy for AI",
|
|
496
497
|
"selectWidgetFirst": "Select a widget on screen first, then tap Copy.",
|
|
497
|
-
"inspectorHint": "Tap the UI to select a widget (purple outline). Then copy for your AI prompt."
|
|
498
|
+
"inspectorHint": "Tap the UI to select a widget (purple outline). Then copy for your AI prompt.",
|
|
499
|
+
"statusActive": "Inspecting"
|
|
498
500
|
},
|
|
499
501
|
"webDevicePreview": {
|
|
500
502
|
"frame": "Frame",
|
|
@@ -400,6 +400,7 @@
|
|
|
400
400
|
"home_widgets_panel": "Panel de Home Widgets",
|
|
401
401
|
"home_widgets_title": "Panel de Home Widgets",
|
|
402
402
|
"inspector_fab_title": "Inspector de widgets",
|
|
403
|
+
"inspector_fab_subtitle_prefix": "Atajo global:",
|
|
403
404
|
"update_mywidget_title": "📱 Actualizar Widget MyWidget",
|
|
404
405
|
"update_mywidget_desc": "Llamar a la actualización manual para el widget MyWidget",
|
|
405
406
|
"paywalls_title": "Panel de Admin de Paywalls",
|
|
@@ -494,7 +495,8 @@
|
|
|
494
495
|
"deactivate": "Desactivar inspector de widgets",
|
|
495
496
|
"copyForAi": "Copiar para IA",
|
|
496
497
|
"selectWidgetFirst": "Selecciona un widget en pantalla primero y luego toca Copiar.",
|
|
497
|
-
"inspectorHint": "Toca la interfaz para seleccionar (borde morado). Luego copia para tu prompt de IA."
|
|
498
|
+
"inspectorHint": "Toca la interfaz para seleccionar (borde morado). Luego copia para tu prompt de IA.",
|
|
499
|
+
"statusActive": "Inspeccionando"
|
|
498
500
|
},
|
|
499
501
|
"webDevicePreview": {
|
|
500
502
|
"frame": "Frame",
|
|
@@ -400,6 +400,7 @@
|
|
|
400
400
|
"home_widgets_panel": "Painel de Home Widgets",
|
|
401
401
|
"home_widgets_title": "Painel de Home Widgets",
|
|
402
402
|
"inspector_fab_title": "Inspector de widgets",
|
|
403
|
+
"inspector_fab_subtitle_prefix": "Atalho global:",
|
|
403
404
|
"update_mywidget_title": "📱 Atualizar Widget MyWidget",
|
|
404
405
|
"update_mywidget_desc": "Chamar atualização manual para o widget MyWidget",
|
|
405
406
|
"paywalls_title": "Painel Admin de Paywalls",
|
|
@@ -494,7 +495,8 @@
|
|
|
494
495
|
"deactivate": "Desativar inspetor de widgets",
|
|
495
496
|
"copyForAi": "Copiar para IA",
|
|
496
497
|
"selectWidgetFirst": "Selecione um widget na tela primeiro e depois toque em Copiar.",
|
|
497
|
-
"inspectorHint": "Toque na interface para selecionar (contorno roxo). Depois copie para usar na IA."
|
|
498
|
+
"inspectorHint": "Toque na interface para selecionar (contorno roxo). Depois copie para usar na IA.",
|
|
499
|
+
"statusActive": "Inspecionando"
|
|
498
500
|
},
|
|
499
501
|
"webDevicePreview": {
|
|
500
502
|
"frame": "Frame",
|
|
@@ -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',
|
|
@@ -139,16 +139,18 @@ flutter_launcher_icons:
|
|
|
139
139
|
background_color: "#01171f"
|
|
140
140
|
theme_color: "#01171f"
|
|
141
141
|
flutter_native_splash:
|
|
142
|
-
|
|
143
|
-
|
|
142
|
+
# Match KasyColors.background (light / dark) so the splash → app transition
|
|
143
|
+
# has zero white/black flash on web, iOS and Android.
|
|
144
|
+
color: "#F7F7F7"
|
|
145
|
+
color_dark: "#060608"
|
|
144
146
|
fullscreen: true
|
|
145
147
|
ios: true
|
|
146
148
|
android: true
|
|
147
149
|
image: assets/images/splash_logo_light.png
|
|
148
150
|
image_dark: assets/images/splash_logo_dark.png
|
|
149
151
|
android_12:
|
|
150
|
-
color: "#
|
|
151
|
-
color_dark: "#
|
|
152
|
+
color: "#F7F7F7"
|
|
153
|
+
color_dark: "#060608"
|
|
152
154
|
image: assets/images/splash_logo_light_android12.png
|
|
153
155
|
image_dark: assets/images/splash_logo_dark_android12.png
|
|
154
156
|
|
|
@@ -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
|
+
}
|
|
@@ -46,24 +46,20 @@
|
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
|
|
49
|
+
|
|
50
|
+
|
|
49
51
|
<style id="splash-screen-style">
|
|
50
52
|
html {
|
|
51
|
-
height: 100
|
|
52
|
-
background-color: #FFFFFF;
|
|
53
|
-
color-scheme: light dark;
|
|
53
|
+
height: 100%
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
body {
|
|
57
57
|
margin: 0;
|
|
58
58
|
min-height: 100%;
|
|
59
|
-
background-color: #
|
|
59
|
+
background-color: #F7F7F7;
|
|
60
60
|
background-size: 100% 100%;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
flutter-view, flt-glass-pane, flt-scene-host {
|
|
64
|
-
background-color: #FFFFFF;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
63
|
.center {
|
|
68
64
|
margin: 0;
|
|
69
65
|
position: absolute;
|
|
@@ -111,23 +107,16 @@
|
|
|
111
107
|
}
|
|
112
108
|
|
|
113
109
|
@media (prefers-color-scheme: dark) {
|
|
114
|
-
html {
|
|
115
|
-
background-color: #000000;
|
|
116
|
-
}
|
|
117
110
|
body {
|
|
118
|
-
background-color: #
|
|
111
|
+
background-color: #060608;
|
|
119
112
|
}
|
|
120
|
-
flutter-view, flt-glass-pane, flt-scene-host {
|
|
121
|
-
background-color: #000000;
|
|
122
|
-
}
|
|
123
113
|
}
|
|
124
114
|
</style>
|
|
125
115
|
<script id="splash-screen-script">
|
|
126
116
|
function removeSplashFromWeb() {
|
|
127
117
|
document.getElementById("splash")?.remove();
|
|
128
118
|
document.getElementById("splash-branding")?.remove();
|
|
129
|
-
|
|
130
|
-
document.body.style.backgroundColor = isDark ? "#000000" : "#FFFFFF";
|
|
119
|
+
document.body.style.background = "transparent";
|
|
131
120
|
}
|
|
132
121
|
</script>
|
|
133
122
|
</head>
|
|
@@ -145,6 +134,7 @@
|
|
|
145
134
|
|
|
146
135
|
|
|
147
136
|
|
|
137
|
+
|
|
148
138
|
<script src="flutter_bootstrap.js" async=""></script>
|
|
149
139
|
<script src="./local_notifications.js"></script>
|
|
150
140
|
|