kasy-cli 1.31.9 → 1.31.10
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/commands/new.js +7 -10
- package/lib/scaffold/CHANGELOG.json +9 -0
- package/lib/scaffold/backends/supabase/config.toml +39 -2
- package/lib/scaffold/backends/supabase/deploy.js +14 -16
- package/lib/scaffold/catalog.js +24 -0
- package/package.json +2 -2
- package/templates/firebase/assets/images/premium-bg.jpg +0 -0
- package/templates/firebase/assets/images/premium-switch-header.png +0 -0
- package/templates/firebase/lib/components/kasy_app_bar.dart +107 -1
- package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +26 -7
- package/templates/firebase/lib/core/bottom_menu/kasy_bottom_bar_factory.dart +22 -33
- package/templates/firebase/lib/core/chrome/chrome_visibility.dart +119 -0
- package/templates/firebase/lib/core/shared_preferences/shared_preferences.dart +12 -0
- package/templates/firebase/lib/core/web_device_preview/web_device_preview.dart +25 -3
- package/templates/firebase/lib/features/home/home_feed.dart +7 -1
- package/templates/firebase/lib/features/home/home_page.dart +6 -8
- package/templates/firebase/lib/features/notifications/ui/notifications_page.dart +1 -0
- package/templates/firebase/lib/features/settings/settings_page.dart +26 -0
- package/templates/firebase/lib/features/subscriptions/ui/component/paywall_minimal.dart +169 -90
- package/templates/firebase/lib/features/subscriptions/ui/component/paywall_row.dart +219 -202
- package/templates/firebase/lib/features/subscriptions/ui/component/paywall_with_switch.dart +67 -30
- package/templates/firebase/lib/features/subscriptions/ui/component/premium_content.dart +16 -6
- package/templates/firebase/lib/i18n/en.i18n.json +1 -0
- package/templates/firebase/lib/i18n/es.i18n.json +1 -0
- package/templates/firebase/lib/i18n/pt.i18n.json +1 -0
|
@@ -533,7 +533,9 @@ class _DeviceSwitchBridgeState extends State<_DeviceSwitchBridge> {
|
|
|
533
533
|
void _onFrameVisibleChanged() {
|
|
534
534
|
if (!mounted) return;
|
|
535
535
|
final store = Provider.of<DevicePreviewStore>(context, listen: false);
|
|
536
|
-
|
|
536
|
+
final data = _readData(store);
|
|
537
|
+
if (data == null) return;
|
|
538
|
+
if (data.isFrameVisible != widget.frameVisibleNotifier.value) {
|
|
537
539
|
store.toggleFrame();
|
|
538
540
|
}
|
|
539
541
|
}
|
|
@@ -543,11 +545,31 @@ class _DeviceSwitchBridgeState extends State<_DeviceSwitchBridge> {
|
|
|
543
545
|
void _syncOrientation() {
|
|
544
546
|
if (!mounted) return;
|
|
545
547
|
final store = Provider.of<DevicePreviewStore>(context, listen: false);
|
|
548
|
+
final data = _readData(store);
|
|
549
|
+
if (data == null) {
|
|
550
|
+
// DevicePreview initializes asynchronously (it loads saved preferences), so
|
|
551
|
+
// on the first web frame the store can still be uninitialized and reading
|
|
552
|
+
// store.data throws "Not initialized". Retry next frame instead of surfacing
|
|
553
|
+
// a scary (and harmless) exception in the console.
|
|
554
|
+
WidgetsBinding.instance.addPostFrameCallback((_) => _syncOrientation());
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
546
557
|
final target = widget.landscapeNotifier.value
|
|
547
558
|
? Orientation.landscape
|
|
548
559
|
: Orientation.portrait;
|
|
549
|
-
if (
|
|
550
|
-
store.data =
|
|
560
|
+
if (data.orientation != target) {
|
|
561
|
+
store.data = data.copyWith(orientation: target);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/// [DevicePreviewStore.data] throws while the store is still finishing its async
|
|
566
|
+
/// initialization. Returns null instead of throwing so callers can skip or retry
|
|
567
|
+
/// cleanly (see _syncOrientation).
|
|
568
|
+
DevicePreviewData? _readData(DevicePreviewStore store) {
|
|
569
|
+
try {
|
|
570
|
+
return store.data;
|
|
571
|
+
} catch (_) {
|
|
572
|
+
return null;
|
|
551
573
|
}
|
|
552
574
|
}
|
|
553
575
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import 'package:flutter/material.dart';
|
|
2
|
+
import 'package:kasy_kit/components/kasy_app_bar.dart';
|
|
2
3
|
import 'package:kasy_kit/components/kasy_card.dart';
|
|
3
4
|
import 'package:kasy_kit/core/haptics/kasy_haptics.dart';
|
|
4
5
|
import 'package:kasy_kit/core/theme/theme.dart';
|
|
@@ -213,7 +214,12 @@ class _HomeFeedState extends State<HomeFeed> {
|
|
|
213
214
|
child: Column(
|
|
214
215
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
215
216
|
children: <Widget>[
|
|
216
|
-
|
|
217
|
+
// App bar inset lives INSIDE the scroll so it scrolls away and the
|
|
218
|
+
// feed slides under the frosted bar (overlay pattern).
|
|
219
|
+
SizedBox(
|
|
220
|
+
height: kasyAppBarBodyTopOverlap(context) +
|
|
221
|
+
KasySpacing.belowChromeContentGap,
|
|
222
|
+
),
|
|
217
223
|
_FilterRow(selected: _selected, onSelect: _select),
|
|
218
224
|
const SizedBox(height: KasySpacing.lg),
|
|
219
225
|
Padding(
|
|
@@ -31,14 +31,11 @@ class HomePage extends ConsumerWidget {
|
|
|
31
31
|
color: context.colors.background,
|
|
32
32
|
child: Stack(
|
|
33
33
|
children: [
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
child: const HomeFeed(),
|
|
40
|
-
),
|
|
41
|
-
),
|
|
34
|
+
// True overlay: the feed fills the whole height and scrolls UNDER
|
|
35
|
+
// the frosted app bar (the bar's top inset lives inside the feed's
|
|
36
|
+
// scroll, so it scrolls away). This way, when the bar hides on
|
|
37
|
+
// scroll, content already fills the top — no empty app-bar band.
|
|
38
|
+
const Positioned.fill(child: HomeFeed()),
|
|
42
39
|
Positioned(
|
|
43
40
|
top: 0,
|
|
44
41
|
left: 0,
|
|
@@ -46,6 +43,7 @@ class HomePage extends ConsumerWidget {
|
|
|
46
43
|
child: KasyAppBar(
|
|
47
44
|
title: t.home.dashboard.brand,
|
|
48
45
|
style: KasyAppBarStyle.rootTab,
|
|
46
|
+
hideOnScroll: true,
|
|
49
47
|
onThemeToggle: () {
|
|
50
48
|
KasyHaptics.light(context);
|
|
51
49
|
ThemeProvider.of(context).toggle();
|
|
@@ -105,6 +105,7 @@ class _NotificationsPageState extends ConsumerState<NotificationsPage> {
|
|
|
105
105
|
return KasyOverlayScaffold(
|
|
106
106
|
title: t.notifications.title,
|
|
107
107
|
appBarStyle: KasyAppBarStyle.rootTab,
|
|
108
|
+
hideAppBarOnScroll: true,
|
|
108
109
|
scrollController: _scrollController,
|
|
109
110
|
trailing: Builder(
|
|
110
111
|
builder: (ctx) => Row(
|
|
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
|
|
3
3
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
4
4
|
import 'package:go_router/go_router.dart';
|
|
5
5
|
import 'package:kasy_kit/components/components.dart';
|
|
6
|
+
import 'package:kasy_kit/core/chrome/chrome_visibility.dart';
|
|
6
7
|
import 'package:kasy_kit/core/config/features.dart';
|
|
7
8
|
import 'package:kasy_kit/core/data/models/user.dart';
|
|
8
9
|
import 'package:kasy_kit/core/haptics/haptic_feedback_notifier.dart';
|
|
@@ -41,6 +42,7 @@ class SettingsPage extends ConsumerWidget {
|
|
|
41
42
|
return KasyOverlayScaffold(
|
|
42
43
|
title: tr.title,
|
|
43
44
|
appBarStyle: KasyAppBarStyle.rootTab,
|
|
45
|
+
hideAppBarOnScroll: true,
|
|
44
46
|
trailing: Builder(
|
|
45
47
|
builder: (ctx) => KasyChromeOrbIconButton(
|
|
46
48
|
icon: KasyIcons.logout,
|
|
@@ -118,6 +120,10 @@ class SettingsPage extends ConsumerWidget {
|
|
|
118
120
|
const SettingsDivider(),
|
|
119
121
|
const HapticFeedbackSwitcher(),
|
|
120
122
|
const SettingsDivider(),
|
|
123
|
+
if (kShowHideChromeOnScrollSetting) ...[
|
|
124
|
+
const HideChromeOnScrollSwitcher(),
|
|
125
|
+
const SettingsDivider(),
|
|
126
|
+
],
|
|
121
127
|
const LanguageSwitcher(),
|
|
122
128
|
if (withLocalReminders) ...[
|
|
123
129
|
const SettingsDivider(),
|
|
@@ -641,6 +647,10 @@ class _DesktopDetail extends ConsumerWidget {
|
|
|
641
647
|
const SettingsDivider(),
|
|
642
648
|
const HapticFeedbackSwitcher(),
|
|
643
649
|
const SettingsDivider(),
|
|
650
|
+
if (kShowHideChromeOnScrollSetting) ...[
|
|
651
|
+
const HideChromeOnScrollSwitcher(),
|
|
652
|
+
const SettingsDivider(),
|
|
653
|
+
],
|
|
644
654
|
const LanguageSwitcher(),
|
|
645
655
|
if (withLocalReminders) ...[
|
|
646
656
|
const SettingsDivider(),
|
|
@@ -953,6 +963,22 @@ class HapticFeedbackSwitcher extends ConsumerWidget {
|
|
|
953
963
|
}
|
|
954
964
|
}
|
|
955
965
|
|
|
966
|
+
class HideChromeOnScrollSwitcher extends ConsumerWidget {
|
|
967
|
+
const HideChromeOnScrollSwitcher({super.key});
|
|
968
|
+
|
|
969
|
+
@override
|
|
970
|
+
Widget build(BuildContext context, WidgetRef ref) {
|
|
971
|
+
final isEnabled = ref.watch(hideChromeOnScrollProvider);
|
|
972
|
+
return SettingsSwitchTile(
|
|
973
|
+
icon: KasyIcons.eyeOff,
|
|
974
|
+
title: context.t.settings.hide_chrome_on_scroll_title,
|
|
975
|
+
value: isEnabled,
|
|
976
|
+
onChanged: (_) =>
|
|
977
|
+
ref.read(hideChromeOnScrollProvider.notifier).toggle(),
|
|
978
|
+
);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
|
|
956
982
|
class ThemeSwitcher extends StatelessWidget {
|
|
957
983
|
const ThemeSwitcher({super.key});
|
|
958
984
|
|
|
@@ -4,6 +4,7 @@ import 'package:google_fonts/google_fonts.dart';
|
|
|
4
4
|
import 'package:kasy_kit/components/components.dart';
|
|
5
5
|
import 'package:kasy_kit/core/data/models/subscription.dart';
|
|
6
6
|
import 'package:kasy_kit/core/theme/theme.dart';
|
|
7
|
+
import 'package:kasy_kit/core/widgets/responsive_layout.dart';
|
|
7
8
|
import 'package:kasy_kit/features/subscriptions/ui/component/premium_page_factory.dart';
|
|
8
9
|
import 'package:kasy_kit/features/subscriptions/ui/widgets/paywall_empty_state.dart';
|
|
9
10
|
import 'package:kasy_kit/features/subscriptions/ui/widgets/premium_background_gradient.dart';
|
|
@@ -15,6 +16,10 @@ import 'package:kasy_kit/i18n/translations.g.dart';
|
|
|
15
16
|
|
|
16
17
|
/// Minimal paywall: benefits list + single CTA.
|
|
17
18
|
/// Best for apps with one plan or when you want a clean, focused layout.
|
|
19
|
+
///
|
|
20
|
+
/// Responsive: on phones (small) it fills the screen with the CTA pinned to the
|
|
21
|
+
/// bottom. On tablet/desktop (medium+) the content is centered in a max-width
|
|
22
|
+
/// column so it never stretches edge-to-edge or leaves a big empty gap.
|
|
18
23
|
class PaywallMinimal extends StatelessWidget {
|
|
19
24
|
final List<SubscriptionProduct> offers;
|
|
20
25
|
final SubscriptionProduct? selectedOffer;
|
|
@@ -23,6 +28,10 @@ class PaywallMinimal extends StatelessWidget {
|
|
|
23
28
|
final OnTap? onTapRestore;
|
|
24
29
|
final OnTap? onSkip;
|
|
25
30
|
|
|
31
|
+
/// Max content width on tablet/desktop. Keeps the paywall readable and
|
|
32
|
+
/// centered instead of stretched across a wide viewport.
|
|
33
|
+
static const double _maxContentWidth = 460;
|
|
34
|
+
|
|
26
35
|
const PaywallMinimal({
|
|
27
36
|
super.key,
|
|
28
37
|
required this.offers,
|
|
@@ -42,105 +51,175 @@ class PaywallMinimal extends StatelessWidget {
|
|
|
42
51
|
return PaywallEmptyState(onTapRestore: onTapRestore, onSkip: onSkip);
|
|
43
52
|
}
|
|
44
53
|
|
|
54
|
+
return Scaffold(
|
|
55
|
+
backgroundColor: context.colors.primary,
|
|
56
|
+
body: PremiumBackgroundGradient(
|
|
57
|
+
child: SafeArea(
|
|
58
|
+
child: LayoutBuilder(
|
|
59
|
+
builder: (context, constraints) {
|
|
60
|
+
final bool compact =
|
|
61
|
+
DeviceType.fromWidth(constraints.maxWidth) == DeviceType.small;
|
|
62
|
+
return compact ? _compactLayout(context) : _wideLayout(context);
|
|
63
|
+
},
|
|
64
|
+
),
|
|
65
|
+
),
|
|
66
|
+
),
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// --- Phone: full-height column, CTA pinned to the bottom. -----------------
|
|
71
|
+
Widget _compactLayout(BuildContext context) {
|
|
72
|
+
return Padding(
|
|
73
|
+
padding: const EdgeInsets.symmetric(
|
|
74
|
+
horizontal: KasySpacing.pageHorizontalGutter,
|
|
75
|
+
),
|
|
76
|
+
child: Column(
|
|
77
|
+
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
78
|
+
children: [
|
|
79
|
+
Align(alignment: Alignment.topRight, child: _closeButton()),
|
|
80
|
+
const SizedBox(height: KasySpacing.lg),
|
|
81
|
+
_title(context),
|
|
82
|
+
const SizedBox(height: KasySpacing.lg),
|
|
83
|
+
..._features(context),
|
|
84
|
+
const Spacer(),
|
|
85
|
+
_price(context),
|
|
86
|
+
const SizedBox(height: KasySpacing.smd),
|
|
87
|
+
_cta(context),
|
|
88
|
+
const SizedBox(height: KasySpacing.md),
|
|
89
|
+
_bottomMenu(context),
|
|
90
|
+
const SizedBox(height: KasySpacing.lg),
|
|
91
|
+
],
|
|
92
|
+
),
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// --- Tablet/Desktop: centered max-width column, close pinned to corner. ---
|
|
97
|
+
Widget _wideLayout(BuildContext context) {
|
|
98
|
+
return Stack(
|
|
99
|
+
children: [
|
|
100
|
+
Center(
|
|
101
|
+
child: ConstrainedBox(
|
|
102
|
+
constraints: const BoxConstraints(maxWidth: _maxContentWidth),
|
|
103
|
+
child: SingleChildScrollView(
|
|
104
|
+
padding: const EdgeInsets.symmetric(
|
|
105
|
+
horizontal: KasySpacing.lg,
|
|
106
|
+
vertical: KasySpacing.xxl,
|
|
107
|
+
),
|
|
108
|
+
child: Column(
|
|
109
|
+
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
110
|
+
mainAxisSize: MainAxisSize.min,
|
|
111
|
+
children: [
|
|
112
|
+
_title(context),
|
|
113
|
+
const SizedBox(height: KasySpacing.xl),
|
|
114
|
+
..._features(context),
|
|
115
|
+
const SizedBox(height: KasySpacing.xl),
|
|
116
|
+
_price(context),
|
|
117
|
+
const SizedBox(height: KasySpacing.smd),
|
|
118
|
+
_cta(context),
|
|
119
|
+
const SizedBox(height: KasySpacing.md),
|
|
120
|
+
_bottomMenu(context),
|
|
121
|
+
],
|
|
122
|
+
),
|
|
123
|
+
),
|
|
124
|
+
),
|
|
125
|
+
),
|
|
126
|
+
Positioned(
|
|
127
|
+
top: KasySpacing.sm,
|
|
128
|
+
right: KasySpacing.md,
|
|
129
|
+
child: _closeButton(),
|
|
130
|
+
),
|
|
131
|
+
],
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// --- Shared content blocks (same widgets across breakpoints). -------------
|
|
136
|
+
|
|
137
|
+
Widget _closeButton() => AppCloseButton(onTap: () => onSkip?.call());
|
|
138
|
+
|
|
139
|
+
Widget _title(BuildContext context) {
|
|
140
|
+
return Animate(
|
|
141
|
+
effects: const [
|
|
142
|
+
FadeEffect(
|
|
143
|
+
delay: Duration(milliseconds: 100),
|
|
144
|
+
duration: Duration(milliseconds: 300),
|
|
145
|
+
),
|
|
146
|
+
],
|
|
147
|
+
child: Text(
|
|
148
|
+
Translations.of(context).premium.title_1,
|
|
149
|
+
textAlign: TextAlign.center,
|
|
150
|
+
style: GoogleFonts.albertSans(
|
|
151
|
+
fontSize: 28,
|
|
152
|
+
fontWeight: FontWeight.w700,
|
|
153
|
+
color: context.colors.onPrimary,
|
|
154
|
+
letterSpacing: -0.8,
|
|
155
|
+
),
|
|
156
|
+
),
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
List<Widget> _features(BuildContext context) {
|
|
45
161
|
final translations = Translations.of(context).premium;
|
|
46
162
|
final features = [
|
|
47
163
|
translations.feature_1,
|
|
48
164
|
translations.feature_2,
|
|
49
165
|
translations.feature_3,
|
|
50
166
|
];
|
|
167
|
+
return [
|
|
168
|
+
...AnimateList(
|
|
169
|
+
interval: 120.ms,
|
|
170
|
+
delay: 300.ms,
|
|
171
|
+
effects: [FadeEffect(duration: 300.ms)],
|
|
172
|
+
children: features.map((text) => PremiumFeature(text: text)).toList(),
|
|
173
|
+
),
|
|
174
|
+
];
|
|
175
|
+
}
|
|
51
176
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
letterSpacing: -0.8,
|
|
83
|
-
),
|
|
84
|
-
),
|
|
85
|
-
),
|
|
86
|
-
const SizedBox(height: KasySpacing.lg),
|
|
87
|
-
...AnimateList(
|
|
88
|
-
interval: 120.ms,
|
|
89
|
-
delay: 300.ms,
|
|
90
|
-
effects: [FadeEffect(duration: 300.ms)],
|
|
91
|
-
children: features
|
|
92
|
-
.map((text) => PremiumFeature(text: text))
|
|
93
|
-
.toList(),
|
|
94
|
-
),
|
|
95
|
-
const Spacer(),
|
|
96
|
-
Animate(
|
|
97
|
-
effects: const [
|
|
98
|
-
FadeEffect(
|
|
99
|
-
delay: Duration(milliseconds: 400),
|
|
100
|
-
duration: Duration(milliseconds: 300),
|
|
101
|
-
),
|
|
102
|
-
ScaleEffect(
|
|
103
|
-
delay: Duration(milliseconds: 400),
|
|
104
|
-
duration: Duration(milliseconds: 500),
|
|
105
|
-
curve: Curves.easeOut,
|
|
106
|
-
),
|
|
107
|
-
],
|
|
108
|
-
child: Text(
|
|
109
|
-
_effectiveOffer.formattedPrice(context),
|
|
110
|
-
textAlign: TextAlign.center,
|
|
111
|
-
style: context.textTheme.titleLarge?.copyWith(
|
|
112
|
-
fontWeight: FontWeight.w600,
|
|
113
|
-
color: context.colors.onPrimary,
|
|
114
|
-
),
|
|
115
|
-
),
|
|
116
|
-
),
|
|
117
|
-
const SizedBox(height: KasySpacing.smd),
|
|
118
|
-
Animate(
|
|
119
|
-
effects: const [
|
|
120
|
-
FadeEffect(
|
|
121
|
-
delay: Duration(milliseconds: 500),
|
|
122
|
-
duration: Duration(milliseconds: 300),
|
|
123
|
-
),
|
|
124
|
-
],
|
|
125
|
-
child: KasyButton(
|
|
126
|
-
label: translations.action_button,
|
|
127
|
-
variant: KasyButtonVariant.inverse,
|
|
128
|
-
isLoading: onTap == null,
|
|
129
|
-
expand: true,
|
|
130
|
-
onPressed: onTap,
|
|
131
|
-
),
|
|
132
|
-
),
|
|
133
|
-
const SizedBox(height: KasySpacing.md),
|
|
134
|
-
BottomPremiumMenu(
|
|
135
|
-
textColor: context.colors.onPrimary,
|
|
136
|
-
onTapRestore: onTapRestore,
|
|
137
|
-
),
|
|
138
|
-
const SizedBox(height: KasySpacing.lg),
|
|
139
|
-
],
|
|
140
|
-
),
|
|
141
|
-
),
|
|
177
|
+
Widget _price(BuildContext context) {
|
|
178
|
+
return Animate(
|
|
179
|
+
effects: const [
|
|
180
|
+
FadeEffect(
|
|
181
|
+
delay: Duration(milliseconds: 400),
|
|
182
|
+
duration: Duration(milliseconds: 300),
|
|
183
|
+
),
|
|
184
|
+
ScaleEffect(
|
|
185
|
+
delay: Duration(milliseconds: 400),
|
|
186
|
+
duration: Duration(milliseconds: 500),
|
|
187
|
+
curve: Curves.easeOut,
|
|
188
|
+
),
|
|
189
|
+
],
|
|
190
|
+
child: Text(
|
|
191
|
+
_effectiveOffer.formattedPrice(context),
|
|
192
|
+
textAlign: TextAlign.center,
|
|
193
|
+
style: context.textTheme.titleLarge?.copyWith(
|
|
194
|
+
fontWeight: FontWeight.w600,
|
|
195
|
+
color: context.colors.onPrimary,
|
|
196
|
+
),
|
|
197
|
+
),
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
Widget _cta(BuildContext context) {
|
|
202
|
+
return Animate(
|
|
203
|
+
effects: const [
|
|
204
|
+
FadeEffect(
|
|
205
|
+
delay: Duration(milliseconds: 500),
|
|
206
|
+
duration: Duration(milliseconds: 300),
|
|
142
207
|
),
|
|
208
|
+
],
|
|
209
|
+
child: KasyButton(
|
|
210
|
+
label: Translations.of(context).premium.action_button,
|
|
211
|
+
variant: KasyButtonVariant.inverse,
|
|
212
|
+
isLoading: onTap == null,
|
|
213
|
+
expand: true,
|
|
214
|
+
onPressed: onTap,
|
|
143
215
|
),
|
|
144
216
|
);
|
|
145
217
|
}
|
|
218
|
+
|
|
219
|
+
Widget _bottomMenu(BuildContext context) {
|
|
220
|
+
return BottomPremiumMenu(
|
|
221
|
+
textColor: context.colors.onPrimary,
|
|
222
|
+
onTapRestore: onTapRestore,
|
|
223
|
+
);
|
|
224
|
+
}
|
|
146
225
|
}
|