kasy-cli 1.39.0 → 1.40.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/lib/scaffold/CHANGELOG.json +23 -0
- package/package.json +1 -1
- package/templates/firebase/.firebase/hosting.YnVpbGQvd2Vi.cache +27 -27
- package/templates/firebase/AGENTS.md +2 -2
- package/templates/firebase/DESIGN_SYSTEM.md +3 -2
- package/templates/firebase/lib/components/components.dart +6 -1
- package/templates/firebase/lib/components/kasy_app_bar.dart +11 -1
- package/templates/firebase/lib/components/kasy_bottom_sheet.dart +25 -7
- package/templates/firebase/lib/components/kasy_menu.dart +902 -0
- package/templates/firebase/lib/components/kasy_popover.dart +267 -0
- package/templates/firebase/lib/components/kasy_sidebar.dart +20 -10
- package/templates/firebase/lib/core/bottom_menu/web_content_wrapper.dart +23 -7
- package/templates/firebase/lib/core/chrome/app_bar_config.dart +1 -1
- package/templates/firebase/lib/core/navigation/kasy_route_observer.dart +8 -0
- package/templates/firebase/lib/core/rating/widgets/review_popup.dart +96 -14
- package/templates/firebase/lib/core/theme/texts.dart +25 -0
- package/templates/firebase/lib/core/web_viewport_scale.dart +8 -7
- package/templates/firebase/lib/features/home/home_components_page.dart +23 -4
- package/templates/firebase/lib/features/home/home_components_preview_registry.dart +363 -13
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_home_widgets.dart +4 -0
- package/templates/firebase/lib/features/settings/ui/components/avatar_component.dart +38 -94
- package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +32 -107
- package/templates/firebase/lib/features/subscriptions/ui/widgets/comparison_table.dart +21 -21
- package/templates/firebase/lib/features/subscriptions/ui/widgets/premium_close_button.dart +35 -27
- package/templates/firebase/lib/features/subscriptions/ui/widgets/selectable_col.dart +22 -17
- package/templates/firebase/lib/features/subscriptions/ui/widgets/selectable_row.dart +12 -7
- package/templates/firebase/lib/i18n/en.i18n.json +6 -2
- package/templates/firebase/lib/i18n/es.i18n.json +6 -2
- package/templates/firebase/lib/i18n/pt.i18n.json +6 -2
- package/templates/firebase/lib/router.dart +2 -0
- package/templates/firebase/pubspec.yaml +1 -1
- package/templates/firebase/test/app_bar_config_test.dart +70 -0
- package/templates/firebase/lib/features/subscriptions/ui/widgets/premium_banner.dart +0 -81
|
@@ -2,7 +2,8 @@ import 'dart:async';
|
|
|
2
2
|
|
|
3
3
|
import 'package:flutter/material.dart';
|
|
4
4
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
5
|
-
import 'package:kasy_kit/components/
|
|
5
|
+
import 'package:kasy_kit/components/kasy_menu.dart';
|
|
6
|
+
import 'package:kasy_kit/components/kasy_popover.dart';
|
|
6
7
|
import 'package:kasy_kit/core/home_widgets/home_widget_mywidget_service.dart';
|
|
7
8
|
import 'package:kasy_kit/core/shared_preferences/shared_preferences.dart';
|
|
8
9
|
import 'package:kasy_kit/core/theme/theme.dart';
|
|
@@ -63,118 +64,42 @@ class LanguageSwitcher extends ConsumerWidget {
|
|
|
63
64
|
WidgetRef ref,
|
|
64
65
|
AppLocale current,
|
|
65
66
|
) {
|
|
66
|
-
|
|
67
|
-
showKasyBottomSheet<void>(
|
|
67
|
+
showKasyMenu<void>(
|
|
68
68
|
context: context,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
sheetTitle,
|
|
84
|
-
style: sheetContext.textTheme.titleMedium?.copyWith(
|
|
85
|
-
color: sheetContext.colors.onSurface,
|
|
86
|
-
),
|
|
87
|
-
),
|
|
69
|
+
anchorContext: context,
|
|
70
|
+
// Desktop: hang the menu off the right edge of the settings row, where the
|
|
71
|
+
// current value sits — a native desktop menu, not a centered modal.
|
|
72
|
+
desktopAlign: KasyPopoverAlign.end,
|
|
73
|
+
sections: [
|
|
74
|
+
KasyMenuSection(
|
|
75
|
+
label: context.t.settings.language_title,
|
|
76
|
+
selectionMode: KasyMenuSelectionMode.single,
|
|
77
|
+
items: AppLocale.values
|
|
78
|
+
.map(
|
|
79
|
+
(AppLocale locale) => KasyMenuItem(
|
|
80
|
+
title: locale.nativeName,
|
|
81
|
+
selected: locale == current,
|
|
82
|
+
onTap: () => _applyLocale(ref, locale),
|
|
88
83
|
),
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
...AppLocale.values.map((AppLocale locale) {
|
|
92
|
-
final bool isSelected = locale == current;
|
|
93
|
-
return _LocaleOptionTile(
|
|
94
|
-
locale: locale,
|
|
95
|
-
isSelected: isSelected,
|
|
96
|
-
onTap: () async {
|
|
97
|
-
// Close the sheet FIRST. Awaiting work before pop
|
|
98
|
-
// races with the rebuild triggered by setLocale and
|
|
99
|
-
// crashed Navigator.pop with !_debugLocked.
|
|
100
|
-
Navigator.pop(sheetContext);
|
|
101
|
-
// Await setLocale so the locale bundle finishes loading
|
|
102
|
-
// BEFORE the widget tries to read its translations.
|
|
103
|
-
// Slang lazy-loads non-base locales and its loadLocale
|
|
104
|
-
// returns immediately when another caller (this setLocale)
|
|
105
|
-
// is already loading — so a parallel updateForLocale would
|
|
106
|
-
// see translations not yet in the map and silently fall
|
|
107
|
-
// back to the base locale (English). That was the root
|
|
108
|
-
// cause of "first language tap doesn't update the widget".
|
|
109
|
-
await LocaleSettings.setLocale(locale);
|
|
110
|
-
unawaited(
|
|
111
|
-
ref
|
|
112
|
-
.read(sharedPreferencesProvider)
|
|
113
|
-
.setAppLocale(locale.languageCode),
|
|
114
|
-
);
|
|
115
|
-
unawaited(
|
|
116
|
-
ref
|
|
117
|
-
.read(myWidgetHomeWidgetProvider.notifier)
|
|
118
|
-
.updateForLocale(locale),
|
|
119
|
-
);
|
|
120
|
-
},
|
|
121
|
-
);
|
|
122
|
-
}),
|
|
123
|
-
],
|
|
124
|
-
),
|
|
84
|
+
)
|
|
85
|
+
.toList(),
|
|
125
86
|
),
|
|
126
|
-
|
|
87
|
+
],
|
|
127
88
|
);
|
|
128
89
|
}
|
|
129
|
-
}
|
|
130
90
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
Widget build(BuildContext context) {
|
|
144
|
-
final Color primary = context.colors.primary;
|
|
145
|
-
final Color fg = isSelected ? primary : context.colors.onSurface;
|
|
146
|
-
|
|
147
|
-
return KasyHover(
|
|
148
|
-
onTap: onTap,
|
|
149
|
-
focusable: true,
|
|
150
|
-
// Full-bleed rectangular highlight (default radius): the sheet rounds its
|
|
151
|
-
// own corners, so options span edge-to-edge like a native menu/list.
|
|
152
|
-
padding: const EdgeInsets.symmetric(
|
|
153
|
-
horizontal: KasySpacing.md,
|
|
154
|
-
vertical: KasySpacing.smd,
|
|
155
|
-
),
|
|
156
|
-
child: Row(
|
|
157
|
-
children: <Widget>[
|
|
158
|
-
Expanded(
|
|
159
|
-
child: Text(
|
|
160
|
-
locale.nativeName,
|
|
161
|
-
style: context.textTheme.bodyLarge?.copyWith(
|
|
162
|
-
color: fg,
|
|
163
|
-
fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400,
|
|
164
|
-
),
|
|
165
|
-
),
|
|
166
|
-
),
|
|
167
|
-
if (isSelected)
|
|
168
|
-
Container(
|
|
169
|
-
width: 10,
|
|
170
|
-
height: 10,
|
|
171
|
-
decoration: BoxDecoration(
|
|
172
|
-
color: primary,
|
|
173
|
-
shape: BoxShape.circle,
|
|
174
|
-
),
|
|
175
|
-
),
|
|
176
|
-
],
|
|
177
|
-
),
|
|
91
|
+
// The menu closes itself before this runs (KasyMenuItem pops first), so the
|
|
92
|
+
// awaited setLocale never races the rebuild it triggers. setLocale is awaited
|
|
93
|
+
// so Slang finishes loading the locale bundle before any widget reads its
|
|
94
|
+
// translations — otherwise a parallel updateForLocale sees an unloaded map and
|
|
95
|
+
// silently falls back to English (the old "first tap doesn't update" bug).
|
|
96
|
+
Future<void> _applyLocale(WidgetRef ref, AppLocale locale) async {
|
|
97
|
+
await LocaleSettings.setLocale(locale);
|
|
98
|
+
unawaited(
|
|
99
|
+
ref.read(sharedPreferencesProvider).setAppLocale(locale.languageCode),
|
|
100
|
+
);
|
|
101
|
+
unawaited(
|
|
102
|
+
ref.read(myWidgetHomeWidgetProvider.notifier).updateForLocale(locale),
|
|
178
103
|
);
|
|
179
104
|
}
|
|
180
105
|
}
|
|
@@ -137,30 +137,30 @@ class _ComparisonTable extends StatelessWidget {
|
|
|
137
137
|
return const SizedBox.shrink();
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
|
|
140
|
+
// Flat bordered surface (no elevation), clipped to the rounded shape so the
|
|
141
|
+
// tinted header corners follow the radius. No Material wrapper: the Container
|
|
142
|
+
// paints the surface, border and radius itself.
|
|
143
|
+
return ClipRRect(
|
|
141
144
|
borderRadius: KasyRadius.lgBorderRadius,
|
|
142
|
-
child:
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
border: Border.all(
|
|
149
|
-
color: context.colors.onBackground.withValues(alpha: 0.3),
|
|
150
|
-
),
|
|
145
|
+
child: Container(
|
|
146
|
+
decoration: BoxDecoration(
|
|
147
|
+
color: context.colors.surface,
|
|
148
|
+
borderRadius: KasyRadius.lgBorderRadius,
|
|
149
|
+
border: Border.all(
|
|
150
|
+
color: context.colors.onBackground.withValues(alpha: 0.3),
|
|
151
151
|
),
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
152
|
+
),
|
|
153
|
+
child: Column(
|
|
154
|
+
children: [
|
|
155
|
+
_buildHeader(context),
|
|
156
|
+
...rows.asMap().entries.map(
|
|
157
|
+
(entry) => _buildRow(
|
|
158
|
+
context,
|
|
159
|
+
entry.value,
|
|
160
|
+
isLast: entry.key == rows.length - 1,
|
|
161
161
|
),
|
|
162
|
-
|
|
163
|
-
|
|
162
|
+
),
|
|
163
|
+
],
|
|
164
164
|
),
|
|
165
165
|
),
|
|
166
166
|
);
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import 'package:flutter/material.dart';
|
|
2
2
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
3
3
|
import 'package:kasy_kit/core/theme/theme.dart';
|
|
4
|
-
import 'package:kasy_kit/core/widgets/
|
|
4
|
+
import 'package:kasy_kit/core/widgets/kasy_pressable_depth.dart';
|
|
5
|
+
import 'package:kasy_kit/i18n/translations.g.dart';
|
|
5
6
|
|
|
6
7
|
class AppCloseButtonComponent extends ConsumerWidget {
|
|
7
8
|
final ValueNotifier<bool> showCloseBtn;
|
|
@@ -36,34 +37,41 @@ class AppCloseButton extends StatelessWidget {
|
|
|
36
37
|
@override
|
|
37
38
|
Widget build(BuildContext context) {
|
|
38
39
|
final VoidCallback? onClose = onTap;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
decoration: BoxDecoration(
|
|
54
|
-
shape: BoxShape.circle,
|
|
55
|
-
color: context.colors.onBackground.withValues(alpha: 0.6),
|
|
56
|
-
),
|
|
57
|
-
child: Center(
|
|
58
|
-
child: Icon(
|
|
59
|
-
KasyIcons.close,
|
|
60
|
-
color: context.colors.background,
|
|
61
|
-
size: KasyIconSize.lg,
|
|
62
|
-
),
|
|
63
|
-
),
|
|
64
|
-
),
|
|
40
|
+
|
|
41
|
+
// Dark translucent scrim keeps the glyph readable over any hero image.
|
|
42
|
+
final Widget circle = Container(
|
|
43
|
+
width: 32,
|
|
44
|
+
height: 32,
|
|
45
|
+
decoration: BoxDecoration(
|
|
46
|
+
shape: BoxShape.circle,
|
|
47
|
+
color: context.colors.onBackground.withValues(alpha: 0.6),
|
|
48
|
+
),
|
|
49
|
+
child: Center(
|
|
50
|
+
child: Icon(
|
|
51
|
+
KasyIcons.close,
|
|
52
|
+
color: context.colors.background,
|
|
53
|
+
size: KasyIconSize.lg,
|
|
65
54
|
),
|
|
66
55
|
),
|
|
67
56
|
);
|
|
57
|
+
|
|
58
|
+
// Disabled (no handler): render the inert scrim, no interaction.
|
|
59
|
+
if (onClose == null) {
|
|
60
|
+
return circle;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// KasyPressableDepth gives the kit's press-in depth, a web hover highlight
|
|
64
|
+
// and a keyboard focus ring (no Material ripple), plus a 44x44 tap target
|
|
65
|
+
// around the 32px visual. A light veil lifts the dark scrim on hover/press.
|
|
66
|
+
final BorderRadius pill = BorderRadius.circular(999);
|
|
67
|
+
return KasyPressableDepth(
|
|
68
|
+
onPressed: onClose,
|
|
69
|
+
semanticLabel: t.common.close,
|
|
70
|
+
focusable: true,
|
|
71
|
+
clipBorderRadius: pill,
|
|
72
|
+
focusBorderRadius: pill,
|
|
73
|
+
pressOverlayColor: context.colors.background.withValues(alpha: 0.2),
|
|
74
|
+
child: circle,
|
|
75
|
+
);
|
|
68
76
|
}
|
|
69
77
|
}
|
|
@@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
|
|
|
4
4
|
import 'package:flutter_animate/flutter_animate.dart';
|
|
5
5
|
import 'package:kasy_kit/core/haptics/kasy_haptics.dart';
|
|
6
6
|
import 'package:kasy_kit/core/theme/theme.dart';
|
|
7
|
-
import 'package:kasy_kit/core/widgets/
|
|
7
|
+
import 'package:kasy_kit/core/widgets/kasy_hover.dart';
|
|
8
8
|
import 'package:kasy_kit/features/subscriptions/ui/widgets/selectable_row.dart';
|
|
9
9
|
|
|
10
10
|
|
|
@@ -65,24 +65,29 @@ class _SelectableColGroupState<T> extends State<SelectableColGroup<T>> {
|
|
|
65
65
|
children: [
|
|
66
66
|
for (var i = 0; i < widget.items.length; i++)
|
|
67
67
|
Expanded(
|
|
68
|
-
child:
|
|
69
|
-
|
|
68
|
+
child: KasyHover(
|
|
69
|
+
onTap: () => _selectIndex(i),
|
|
70
|
+
focusable: true,
|
|
70
71
|
borderRadius: KasyRadius.lgBorderRadius,
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
selected: selectedIndex == i,
|
|
84
|
-
brightness: widget.brightness,
|
|
72
|
+
// The card paints its own animated selection border, so suppress
|
|
73
|
+
// the hover/press fill: KasyHover only adds the web click cursor
|
|
74
|
+
// and the keyboard focus ring. The heavy selection haptic is
|
|
75
|
+
// fired by _selectIndex, so opt out of the default tap haptic.
|
|
76
|
+
hapticEnabled: false,
|
|
77
|
+
hoverEnabled: false,
|
|
78
|
+
pressEnabled: false,
|
|
79
|
+
child: Animate(
|
|
80
|
+
effects: [
|
|
81
|
+
FadeEffect(
|
|
82
|
+
delay: Duration(milliseconds: 100 + 100 * i),
|
|
83
|
+
duration: const Duration(milliseconds: 500),
|
|
85
84
|
),
|
|
85
|
+
],
|
|
86
|
+
child: SelectableCol.fromOption(
|
|
87
|
+
key: ValueKey('item_$i'),
|
|
88
|
+
widget.items[i],
|
|
89
|
+
selected: selectedIndex == i,
|
|
90
|
+
brightness: widget.brightness,
|
|
86
91
|
),
|
|
87
92
|
),
|
|
88
93
|
),
|
|
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
|
|
2
2
|
import 'package:flutter_animate/flutter_animate.dart';
|
|
3
3
|
import 'package:kasy_kit/core/haptics/kasy_haptics.dart';
|
|
4
4
|
import 'package:kasy_kit/core/theme/theme.dart';
|
|
5
|
-
import 'package:kasy_kit/core/widgets/
|
|
5
|
+
import 'package:kasy_kit/core/widgets/kasy_hover.dart';
|
|
6
6
|
|
|
7
7
|
typedef OnSelectItem<T> = void Function(T data);
|
|
8
8
|
|
|
@@ -51,12 +51,18 @@ class _SelectableRowGroupState<T> extends State<SelectableRowGroup<T>> {
|
|
|
51
51
|
return Column(
|
|
52
52
|
children: [
|
|
53
53
|
for (var i = 0; i < widget.items.length; i++)
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
KasyHover(
|
|
55
|
+
onTap: () => _selectIndex(i),
|
|
56
|
+
focusable: true,
|
|
56
57
|
borderRadius: KasyRadius.mdBorderRadius,
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
// The card paints its own animated selection border, so suppress the
|
|
59
|
+
// hover/press fill: KasyHover only contributes the web click cursor
|
|
60
|
+
// and the keyboard focus ring. The heavy selection haptic is fired
|
|
61
|
+
// by _selectIndex, so opt out of the default tap haptic.
|
|
62
|
+
hapticEnabled: false,
|
|
63
|
+
hoverEnabled: false,
|
|
64
|
+
pressEnabled: false,
|
|
65
|
+
child: Padding(
|
|
60
66
|
padding: EdgeInsets.only(
|
|
61
67
|
bottom: i == widget.items.length - 1 ? 0 : KasySpacing.sm,
|
|
62
68
|
),
|
|
@@ -75,7 +81,6 @@ class _SelectableRowGroupState<T> extends State<SelectableRowGroup<T>> {
|
|
|
75
81
|
),
|
|
76
82
|
),
|
|
77
83
|
),
|
|
78
|
-
),
|
|
79
84
|
),
|
|
80
85
|
],
|
|
81
86
|
);
|
|
@@ -460,8 +460,12 @@
|
|
|
460
460
|
"cancel_button": "Cancel"
|
|
461
461
|
},
|
|
462
462
|
"review_popup": {
|
|
463
|
-
"
|
|
464
|
-
"
|
|
463
|
+
"question_title": "Enjoying the app?",
|
|
464
|
+
"question_description": "Your answer helps us improve.",
|
|
465
|
+
"question_positive": "Yes, I am",
|
|
466
|
+
"question_negative": "Could be better",
|
|
467
|
+
"title": "Glad you like it!",
|
|
468
|
+
"description": "A store review makes all the difference. It takes a few seconds and really helps us grow.",
|
|
465
469
|
"rate_button": "Write a review"
|
|
466
470
|
},
|
|
467
471
|
"navigation": {
|
|
@@ -460,8 +460,12 @@
|
|
|
460
460
|
"cancel_button": "Cancelar"
|
|
461
461
|
},
|
|
462
462
|
"review_popup": {
|
|
463
|
-
"
|
|
464
|
-
"
|
|
463
|
+
"question_title": "¿Te gusta la app?",
|
|
464
|
+
"question_description": "Tu respuesta nos ayuda a mejorar.",
|
|
465
|
+
"question_positive": "Sí, me gusta",
|
|
466
|
+
"question_negative": "Podría mejorar",
|
|
467
|
+
"title": "¡Qué bueno que te guste!",
|
|
468
|
+
"description": "Una reseña en la tienda marca la diferencia. Toma unos segundos y nos ayuda mucho a crecer.",
|
|
465
469
|
"rate_button": "Escribir una reseña"
|
|
466
470
|
},
|
|
467
471
|
"navigation": {
|
|
@@ -460,8 +460,12 @@
|
|
|
460
460
|
"cancel_button": "Cancelar"
|
|
461
461
|
},
|
|
462
462
|
"review_popup": {
|
|
463
|
-
"
|
|
464
|
-
"
|
|
463
|
+
"question_title": "Está gostando do app?",
|
|
464
|
+
"question_description": "Sua resposta ajuda a gente a melhorar.",
|
|
465
|
+
"question_positive": "Sim, estou gostando",
|
|
466
|
+
"question_negative": "Poderia melhorar",
|
|
467
|
+
"title": "Que bom que curtiu!",
|
|
468
|
+
"description": "Uma avaliação na loja faz toda a diferença. Leva poucos segundos e ajuda demais a gente a crescer.",
|
|
465
469
|
"rate_button": "Escrever uma avaliação"
|
|
466
470
|
},
|
|
467
471
|
"navigation": {
|
|
@@ -9,6 +9,7 @@ import 'package:kasy_kit/core/data/api/analytics_api.dart';
|
|
|
9
9
|
import 'package:kasy_kit/core/data/models/user.dart';
|
|
10
10
|
import 'package:kasy_kit/core/navigation/kasy_navigation_config.dart';
|
|
11
11
|
import 'package:kasy_kit/core/navigation/kasy_page_transition.dart';
|
|
12
|
+
import 'package:kasy_kit/core/navigation/kasy_route_observer.dart';
|
|
12
13
|
import 'package:kasy_kit/core/security/biometric_guard.dart';
|
|
13
14
|
import 'package:kasy_kit/core/shared_preferences/shared_preferences.dart';
|
|
14
15
|
import 'package:kasy_kit/core/states/user_state_notifier.dart';
|
|
@@ -156,6 +157,7 @@ GoRouter generateRouter({
|
|
|
156
157
|
observers: [
|
|
157
158
|
AnalyticsObserver(analyticsApi: MixpanelAnalyticsApi.instance()),
|
|
158
159
|
KasyChromeVisibilityObserver(),
|
|
160
|
+
kasyRouteObserver,
|
|
159
161
|
|
|
160
162
|
...?observers,
|
|
161
163
|
],
|
|
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|
|
16
16
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
|
17
17
|
# In Windows, build-name is used as the major, minor, and patch parts
|
|
18
18
|
# of the product and file versions while build-number is used as the build suffix.
|
|
19
|
-
version: 1.0.0+
|
|
19
|
+
version: 1.0.0+70
|
|
20
20
|
|
|
21
21
|
environment:
|
|
22
22
|
sdk: ^3.11.0
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import 'package:flutter_test/flutter_test.dart';
|
|
2
|
+
import 'package:kasy_kit/components/components.dart';
|
|
3
|
+
|
|
4
|
+
/// The per-screen desktop bar override is just data: a screen transforms the
|
|
5
|
+
/// shell default into its own [KasyAppBarConfig]. These lock that logic.
|
|
6
|
+
void main() {
|
|
7
|
+
group('KasyAppBarConfig', () {
|
|
8
|
+
test('bare config shows every element (mirrors the shell default)', () {
|
|
9
|
+
const KasyAppBarConfig c = KasyAppBarConfig();
|
|
10
|
+
expect(c.showSearch, isTrue);
|
|
11
|
+
expect(c.showThemeToggle, isTrue);
|
|
12
|
+
expect(c.showNotifications, isTrue);
|
|
13
|
+
expect(c.showCreate, isTrue);
|
|
14
|
+
expect(c.showAvatar, isTrue);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('only() shows just the listed elements but keeps their wiring', () {
|
|
18
|
+
void bell() {}
|
|
19
|
+
void toggle() {}
|
|
20
|
+
final KasyAppBarConfig base = KasyAppBarConfig(
|
|
21
|
+
onNotifications: bell,
|
|
22
|
+
onToggleTheme: toggle,
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
final KasyAppBarConfig cfg = base.only(notifications: true, themeToggle: true);
|
|
26
|
+
|
|
27
|
+
expect(cfg.showNotifications, isTrue);
|
|
28
|
+
expect(cfg.showThemeToggle, isTrue);
|
|
29
|
+
expect(cfg.showSearch, isFalse);
|
|
30
|
+
expect(cfg.showCreate, isFalse);
|
|
31
|
+
expect(cfg.showAvatar, isFalse);
|
|
32
|
+
// The shell's bell/theme callbacks survive the override.
|
|
33
|
+
expect(cfg.onNotifications, same(bell));
|
|
34
|
+
expect(cfg.onToggleTheme, same(toggle));
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('search preset = search + theme only', () {
|
|
38
|
+
const KasyAppBarConfig cfg = KasyAppBarConfig.search();
|
|
39
|
+
expect(cfg.showSearch, isTrue);
|
|
40
|
+
expect(cfg.showThemeToggle, isTrue);
|
|
41
|
+
expect(cfg.showNotifications, isFalse);
|
|
42
|
+
expect(cfg.showCreate, isFalse);
|
|
43
|
+
expect(cfg.showAvatar, isFalse);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('minimal preset = theme toggle only', () {
|
|
47
|
+
const KasyAppBarConfig cfg = KasyAppBarConfig.minimal();
|
|
48
|
+
expect(cfg.showThemeToggle, isTrue);
|
|
49
|
+
expect(cfg.showSearch, isFalse);
|
|
50
|
+
expect(cfg.showNotifications, isFalse);
|
|
51
|
+
expect(cfg.showCreate, isFalse);
|
|
52
|
+
expect(cfg.showAvatar, isFalse);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('copyWith with no changes is value-equal (no churn on the bar)', () {
|
|
56
|
+
const KasyAppBarConfig a = KasyAppBarConfig();
|
|
57
|
+
final KasyAppBarConfig b = a.copyWith();
|
|
58
|
+
expect(a, equals(b));
|
|
59
|
+
expect(a.hashCode, equals(b.hashCode));
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('copyWith changes only the named field', () {
|
|
63
|
+
const KasyAppBarConfig a = KasyAppBarConfig();
|
|
64
|
+
final KasyAppBarConfig b = a.copyWith(showSearch: false);
|
|
65
|
+
expect(b.showSearch, isFalse);
|
|
66
|
+
expect(b.showCreate, isTrue);
|
|
67
|
+
expect(a == b, isFalse);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
}
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import 'package:flutter/material.dart';
|
|
2
|
-
import 'package:kasy_kit/core/theme/theme.dart';
|
|
3
|
-
import 'package:kasy_kit/core/widgets/kasy_focus_ring.dart';
|
|
4
|
-
|
|
5
|
-
typedef PremiumWideBannerOnTap = void Function();
|
|
6
|
-
|
|
7
|
-
class PremiumWideBanner extends StatelessWidget {
|
|
8
|
-
final Color bgColor;
|
|
9
|
-
final String imagePath;
|
|
10
|
-
final String smallText;
|
|
11
|
-
final String title;
|
|
12
|
-
final PremiumWideBannerOnTap onTap;
|
|
13
|
-
|
|
14
|
-
const PremiumWideBanner({
|
|
15
|
-
super.key,
|
|
16
|
-
required this.bgColor,
|
|
17
|
-
required this.imagePath,
|
|
18
|
-
required this.smallText,
|
|
19
|
-
required this.title,
|
|
20
|
-
required this.onTap,
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
@override
|
|
24
|
-
Widget build(BuildContext context) {
|
|
25
|
-
return LayoutBuilder(builder: (context, constraints) {
|
|
26
|
-
return Material(
|
|
27
|
-
color: Colors.transparent,
|
|
28
|
-
child: Ink(
|
|
29
|
-
// height: 116,
|
|
30
|
-
width: constraints.maxWidth,
|
|
31
|
-
decoration: BoxDecoration(
|
|
32
|
-
color: bgColor,
|
|
33
|
-
// border: Border.all(color: borderColor),
|
|
34
|
-
),
|
|
35
|
-
child: KasyFocusRing(
|
|
36
|
-
onActivate: () => onTap(),
|
|
37
|
-
borderRadius: BorderRadius.zero,
|
|
38
|
-
child: InkWell(
|
|
39
|
-
canRequestFocus: false,
|
|
40
|
-
onTap: () => onTap(),
|
|
41
|
-
child: Row(
|
|
42
|
-
children: [
|
|
43
|
-
Image.asset(
|
|
44
|
-
imagePath,
|
|
45
|
-
width: constraints.maxWidth * 0.32,
|
|
46
|
-
// height: 116,
|
|
47
|
-
fit: BoxFit.cover,
|
|
48
|
-
),
|
|
49
|
-
const SizedBox(width: KasySpacing.lg),
|
|
50
|
-
Expanded(
|
|
51
|
-
child: Column(
|
|
52
|
-
crossAxisAlignment: CrossAxisAlignment.start,
|
|
53
|
-
mainAxisAlignment: MainAxisAlignment.center,
|
|
54
|
-
children: [
|
|
55
|
-
Text(
|
|
56
|
-
smallText.toUpperCase(),
|
|
57
|
-
style: context.kasyTextTheme.sectionLabel.copyWith(
|
|
58
|
-
color: context.colors.premiumBannerText,
|
|
59
|
-
),
|
|
60
|
-
),
|
|
61
|
-
const SizedBox(height: KasySpacing.sm),
|
|
62
|
-
Text(
|
|
63
|
-
title,
|
|
64
|
-
style: context.textTheme.titleLarge?.copyWith(
|
|
65
|
-
color: context.colors.onPrimary,
|
|
66
|
-
),
|
|
67
|
-
overflow: TextOverflow.clip,
|
|
68
|
-
),
|
|
69
|
-
],
|
|
70
|
-
),
|
|
71
|
-
),
|
|
72
|
-
const SizedBox(width: KasySpacing.lg),
|
|
73
|
-
],
|
|
74
|
-
),
|
|
75
|
-
),
|
|
76
|
-
),
|
|
77
|
-
),
|
|
78
|
-
);
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
}
|