kasy-cli 1.34.0 → 1.35.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/README.md +1 -1
- package/bin/kasy.js +24 -2
- package/docs/cli-reference.md +7 -7
- package/lib/commands/new.js +11 -9
- package/lib/commands/release-version.js +234 -0
- package/lib/commands/update.js +27 -0
- package/lib/scaffold/CHANGELOG.json +9 -0
- package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api.dart +24 -0
- package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api_interface.dart +15 -0
- package/lib/scaffold/backends/api/patch/lib/features/authentication/repositories/authentication_repository.dart +27 -0
- package/lib/scaffold/backends/api/patch/lib/features/subscriptions/api/entities/subscription_entity.dart +1 -0
- package/lib/scaffold/backends/firebase/setup-from-scratch.js +35 -21
- package/lib/scaffold/backends/patch-base-hashes.json +66 -0
- package/lib/scaffold/backends/supabase/edge-functions/stripe-webhook/index.ts +2 -0
- package/lib/scaffold/backends/supabase/migrations/20240101000007_welcome_notification.sql +36 -23
- package/lib/scaffold/backends/supabase/migrations/20240101000014_subscription_trial_end.sql +6 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/authentication/api/authentication_api.dart +82 -3
- package/lib/scaffold/backends/supabase/patch/lib/features/authentication/repositories/authentication_repository.dart +36 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/subscriptions/api/entities/subscription_entity.dart +1 -0
- package/lib/scaffold/generate.js +53 -4
- package/lib/utils/i18n/messages-en.js +23 -0
- package/lib/utils/i18n/messages-es.js +23 -0
- package/lib/utils/i18n/messages-pt.js +23 -0
- package/package.json +5 -2
- package/templates/firebase/.firebase/hosting.YnVpbGQvd2Vi.cache +73 -0
- package/templates/firebase/AGENTS.md +83 -0
- package/templates/firebase/DESIGN_SYSTEM.md +37 -2
- package/templates/firebase/docs/auth-setup.en.md +2 -0
- package/templates/firebase/docs/auth-setup.es.md +2 -0
- package/templates/firebase/docs/auth-setup.pt.md +2 -0
- package/templates/firebase/firebase.json +56 -1
- package/templates/firebase/functions/src/authentication/triggers.ts +10 -6
- package/templates/firebase/functions/src/core/data/entities/subscription_entity.ts +5 -0
- package/templates/firebase/functions/src/core/data/entities/user_entity.ts +9 -1
- package/templates/firebase/functions/src/core/data/repositories/user_repository.ts +27 -0
- package/templates/firebase/functions/src/subscriptions/models/subscriptions.ts +3 -0
- package/templates/firebase/functions/src/subscriptions/stripe_functions.ts +4 -0
- package/templates/firebase/lib/components/kasy_alert.dart +0 -1
- package/templates/firebase/lib/components/kasy_app_bar.dart +31 -16
- package/templates/firebase/lib/components/kasy_bottom_sheet.dart +0 -1
- package/templates/firebase/lib/components/kasy_date_picker.dart +0 -5
- package/templates/firebase/lib/components/kasy_dialog.dart +0 -1
- package/templates/firebase/lib/components/kasy_otp_verification_bottom_sheet.dart +1 -1
- package/templates/firebase/lib/components/kasy_sidebar.dart +189 -120
- package/templates/firebase/lib/components/kasy_text_area.dart +0 -1
- package/templates/firebase/lib/components/kasy_text_field.dart +0 -1
- package/templates/firebase/lib/components/kasy_text_field_otp.dart +0 -1
- package/templates/firebase/lib/components/kasy_toast.dart +107 -41
- package/templates/firebase/lib/core/app_update/app_update_repository.dart +54 -0
- package/templates/firebase/lib/core/app_update/app_update_status.dart +14 -0
- package/templates/firebase/lib/core/app_update/update_available_sheet.dart +70 -0
- package/templates/firebase/lib/core/bottom_menu/active_tab_notifier.dart +40 -3
- package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +82 -34
- package/templates/firebase/lib/core/bottom_menu/web_content_wrapper.dart +6 -6
- package/templates/firebase/lib/core/bottom_menu/web_url.dart +20 -0
- package/templates/firebase/lib/core/data/api/remote_config_api.dart +38 -1
- package/templates/firebase/lib/core/guards/guard.dart +16 -2
- package/templates/firebase/lib/core/icons/kasy_icons.dart +3 -0
- package/templates/firebase/lib/core/rating/widgets/rate_banner.dart +2 -5
- package/templates/firebase/lib/core/rating/widgets/review_popup.dart +5 -3
- package/templates/firebase/lib/core/shared_preferences/shared_preferences.dart +14 -0
- package/templates/firebase/lib/core/states/components/maybe_show_update_available.dart +32 -0
- package/templates/firebase/lib/core/states/logout_action.dart +5 -1
- package/templates/firebase/lib/core/states/user_state_notifier.dart +85 -14
- package/templates/firebase/lib/core/theme/responsive_text_theme.dart +69 -0
- package/templates/firebase/lib/core/theme/texts.dart +90 -57
- package/templates/firebase/lib/core/theme/type_scale.dart +77 -0
- package/templates/firebase/lib/core/theme/web_background_sync_web.dart +20 -6
- package/templates/firebase/lib/core/utils/image_bytes_loader.dart +12 -0
- package/templates/firebase/lib/core/utils/image_bytes_loader_io.dart +28 -0
- package/templates/firebase/lib/core/utils/image_bytes_loader_web.dart +56 -0
- package/templates/firebase/lib/core/web_screen_width.dart +15 -0
- package/templates/firebase/lib/core/web_screen_width_io.dart +3 -0
- package/templates/firebase/lib/core/web_screen_width_web.dart +10 -0
- package/templates/firebase/lib/core/web_viewport_scale.dart +61 -25
- package/templates/firebase/lib/core/widgets/focus_visibility.dart +89 -0
- package/templates/firebase/lib/features/ai_chat/ai_chat_page.dart +1 -2
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_chat_conversation_view.dart +1 -2
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_list.dart +2 -3
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_tile.dart +1 -2
- package/templates/firebase/lib/features/authentication/api/auth_web_support.dart +12 -0
- package/templates/firebase/lib/features/authentication/api/auth_web_support_web.dart +25 -0
- package/templates/firebase/lib/features/authentication/api/authentication_api.dart +205 -0
- package/templates/firebase/lib/features/authentication/api/authentication_api_interface.dart +22 -0
- package/templates/firebase/lib/features/authentication/navigation/post_login_navigation.dart +32 -0
- package/templates/firebase/lib/features/authentication/providers/phone_auth_notifier.dart +7 -7
- package/templates/firebase/lib/features/authentication/providers/signin_state_provider.dart +34 -10
- package/templates/firebase/lib/features/authentication/providers/signup_state_provider.dart +2 -2
- package/templates/firebase/lib/features/authentication/repositories/authentication_repository.dart +37 -0
- package/templates/firebase/lib/features/authentication/repositories/exceptions/authentication_exceptions.dart +8 -0
- package/templates/firebase/lib/features/authentication/ui/components/otp_verification.dart +2 -2
- package/templates/firebase/lib/features/authentication/ui/components/phone_input.dart +2 -2
- package/templates/firebase/lib/features/authentication/ui/signin_page.dart +59 -0
- package/templates/firebase/lib/features/authentication/ui/widgets/auth_card_scaffold.dart +2 -2
- package/templates/firebase/lib/features/authentication/ui/widgets/recover_password_result.dart +2 -2
- package/templates/firebase/lib/features/feedbacks/ui/widgets/add_feature_button.dart +0 -1
- package/templates/firebase/lib/features/home/design_system_page.dart +134 -67
- package/templates/firebase/lib/features/home/home_components_page.dart +4 -3
- package/templates/firebase/lib/features/home/home_components_preview_page.dart +1 -3
- package/templates/firebase/lib/features/home/home_components_preview_registry.dart +154 -56
- package/templates/firebase/lib/features/home/home_page.dart +4 -0
- package/templates/firebase/lib/features/local_reminders/ui/reminder_page.dart +8 -3
- package/templates/firebase/lib/features/notifications/providers/notifications_provider.dart +10 -0
- package/templates/firebase/lib/features/notifications/ui/notifications_page.dart +3 -1
- package/templates/firebase/lib/features/notifications/ui/widgets/notification_tile.dart +8 -3
- package/templates/firebase/lib/features/onboarding/providers/onboarding_model.dart +11 -0
- package/templates/firebase/lib/features/onboarding/providers/onboarding_provider.dart +30 -3
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_loader.dart +13 -4
- package/templates/firebase/lib/features/settings/settings_page.dart +152 -11
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_page.dart +43 -15
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_tab.dart +12 -12
- package/templates/firebase/lib/features/settings/ui/components/admin/send_push_notification_page.dart +1 -4
- package/templates/firebase/lib/features/settings/ui/components/create_password_sheet.dart +141 -0
- package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +0 -1
- package/templates/firebase/lib/features/settings/ui/widgets/admin_card.dart +42 -38
- package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +17 -2
- package/templates/firebase/lib/features/subscriptions/api/entities/subscription_entity.dart +3 -0
- package/templates/firebase/lib/features/subscriptions/api/stripe_product.dart +12 -11
- package/templates/firebase/lib/features/subscriptions/repositories/subscription_repository.dart +25 -2
- package/templates/firebase/lib/features/subscriptions/ui/component/active_premium_content.dart +319 -143
- package/templates/firebase/lib/features/subscriptions/ui/component/paywall_minimal.dart +1 -5
- package/templates/firebase/lib/features/subscriptions/ui/component/paywall_row.dart +1 -5
- package/templates/firebase/lib/features/subscriptions/ui/component/paywall_with_switch.dart +2 -5
- package/templates/firebase/lib/features/subscriptions/ui/component/premium_content.dart +1 -4
- package/templates/firebase/lib/features/subscriptions/ui/widgets/feature_line.dart +1 -2
- package/templates/firebase/lib/features/subscriptions/ui/widgets/premium_banner.dart +2 -7
- package/templates/firebase/lib/features/subscriptions/ui/widgets/premium_feature.dart +2 -4
- package/templates/firebase/lib/features/subscriptions/ui/widgets/selectable_col.dart +1 -4
- package/templates/firebase/lib/features/subscriptions/ui/widgets/selectable_row.dart +0 -3
- package/templates/firebase/lib/i18n/en.i18n.json +49 -3
- package/templates/firebase/lib/i18n/es.i18n.json +49 -3
- package/templates/firebase/lib/i18n/pt.i18n.json +49 -3
- package/templates/firebase/lib/main.dart +11 -2
- package/templates/firebase/lib/router.dart +92 -13
- package/templates/firebase/pubspec.yaml +1 -1
- package/templates/firebase/test/core/data/api/fake_remote_config_api.dart +14 -0
- package/templates/firebase/test/core/states/user_state_notifier_test.dart +47 -3
- package/templates/firebase/test/core/web_viewport_scale_test.dart +68 -0
- package/templates/firebase/test/features/authentication/data/api/auth_api_fake.dart +15 -0
- package/templates/firebase/web/index.html +162 -14
- package/templates/firebase/lib/core/guards/user_info_guard.dart +0 -61
package/templates/firebase/lib/features/subscriptions/ui/component/active_premium_content.dart
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import 'package:flutter/material.dart';
|
|
2
2
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
3
|
-
import 'package:
|
|
3
|
+
import 'package:go_router/go_router.dart';
|
|
4
|
+
import 'package:intl/intl.dart';
|
|
4
5
|
import 'package:kasy_kit/components/components.dart';
|
|
6
|
+
import 'package:kasy_kit/core/data/models/subscription.dart';
|
|
5
7
|
import 'package:kasy_kit/core/states/user_state_notifier.dart';
|
|
6
8
|
import 'package:kasy_kit/core/theme/theme.dart';
|
|
9
|
+
import 'package:kasy_kit/features/settings/ui/widgets/settings_tile.dart';
|
|
10
|
+
import 'package:kasy_kit/features/subscriptions/providers/premium_page_provider.dart';
|
|
7
11
|
import 'package:kasy_kit/features/subscriptions/shared/subscription_management.dart';
|
|
8
|
-
import 'package:kasy_kit/features/subscriptions/ui/widgets/premium_background.dart';
|
|
9
|
-
import 'package:kasy_kit/features/subscriptions/ui/widgets/premium_card.dart';
|
|
10
|
-
import 'package:kasy_kit/features/subscriptions/ui/widgets/unsubscribe_feedback_popup.dart';
|
|
11
12
|
import 'package:kasy_kit/i18n/translations.g.dart';
|
|
12
13
|
|
|
13
14
|
typedef OnTapUnSubscribe = void Function(String reason);
|
|
14
15
|
typedef OnExit = void Function();
|
|
15
16
|
|
|
16
17
|
class ActivePremiumContent extends ConsumerStatefulWidget {
|
|
18
|
+
// kept for API compatibility — the manage flow no longer uses a feedback
|
|
19
|
+
// reason collected here; PremiumPage still wires this for legacy callers.
|
|
17
20
|
final OnTapUnSubscribe? onTap;
|
|
18
21
|
final OnExit? onExit;
|
|
19
22
|
|
|
@@ -25,171 +28,344 @@ class ActivePremiumContent extends ConsumerStatefulWidget {
|
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
class _ActivePremiumContentState extends ConsumerState<ActivePremiumContent> {
|
|
28
|
-
bool
|
|
31
|
+
bool _isManaging = false;
|
|
32
|
+
bool _isRestoring = false;
|
|
29
33
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
void _goBack() {
|
|
35
|
+
if (widget.onExit != null) {
|
|
36
|
+
widget.onExit!();
|
|
37
|
+
} else if (context.canPop()) {
|
|
38
|
+
context.pop();
|
|
39
|
+
} else {
|
|
40
|
+
context.go('/');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// Opens the subscription management interface for the current platform.
|
|
45
|
+
/// On iOS/Android with RevenueCat → App Store / Play Store subscriptions page.
|
|
46
|
+
/// On web with Stripe → Stripe Customer Portal.
|
|
47
|
+
/// Cross-platform mismatch → informational dialog (subscription managed elsewhere).
|
|
48
|
+
Future<void> _handleManageTap() async {
|
|
35
49
|
final store = ref.read(userStateNotifierProvider).subscription?.store;
|
|
50
|
+
final t = Translations.of(context).activePremium;
|
|
51
|
+
|
|
36
52
|
if (resolveManageLocation(store) == ManageLocation.elsewhere) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
53
|
+
await showKasyBlurDialog<void>(
|
|
54
|
+
context: context,
|
|
55
|
+
builder: (dialogCtx) => KasyDialog(
|
|
56
|
+
leadingIcon: KasyIcons.payment,
|
|
57
|
+
iconTone: KasyDialogIconTone.info,
|
|
58
|
+
title: t.managed_elsewhere_title,
|
|
59
|
+
message: t.managed_elsewhere_description,
|
|
60
|
+
showCloseButton: false,
|
|
61
|
+
confirmLabel: t.cancel_button,
|
|
62
|
+
onConfirm: () => Navigator.of(dialogCtx).pop(),
|
|
63
|
+
),
|
|
47
64
|
);
|
|
48
65
|
return;
|
|
49
66
|
}
|
|
50
|
-
|
|
67
|
+
|
|
68
|
+
if (_isManaging) return;
|
|
69
|
+
setState(() => _isManaging = true);
|
|
70
|
+
try {
|
|
71
|
+
await ref.read(premiumStateProvider.notifier).unsubscribe();
|
|
72
|
+
} finally {
|
|
73
|
+
if (mounted) setState(() => _isManaging = false);
|
|
74
|
+
}
|
|
51
75
|
}
|
|
52
76
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
77
|
+
/// Re-syncs the subscription from RevenueCat / Stripe backend.
|
|
78
|
+
Future<void> _handleRestoreTap() async {
|
|
79
|
+
if (_isRestoring) return;
|
|
80
|
+
setState(() => _isRestoring = true);
|
|
56
81
|
try {
|
|
57
|
-
await
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
82
|
+
await ref
|
|
83
|
+
.read(userStateNotifierProvider.notifier)
|
|
84
|
+
.refreshSubscription();
|
|
85
|
+
if (!mounted) return;
|
|
86
|
+
showKasyToast(
|
|
87
|
+
context,
|
|
88
|
+
title: Translations.of(context).premium.restore_success_title,
|
|
89
|
+
tone: KasyToastTone.success,
|
|
64
90
|
);
|
|
65
91
|
} finally {
|
|
66
|
-
if (mounted) setState(() =>
|
|
92
|
+
if (mounted) setState(() => _isRestoring = false);
|
|
67
93
|
}
|
|
68
94
|
}
|
|
69
95
|
|
|
70
96
|
@override
|
|
71
97
|
Widget build(BuildContext context) {
|
|
72
98
|
final userState = ref.watch(userStateNotifierProvider);
|
|
99
|
+
final t = Translations.of(context).activePremium;
|
|
100
|
+
final sub = userState.subscription;
|
|
101
|
+
final activeData = sub is SubscriptionStateData ? sub : null;
|
|
102
|
+
final planName = activeData?.activeOffer?.title ?? t.plan_fallback;
|
|
103
|
+
final price = activeData?.activeOffer?.price;
|
|
104
|
+
final currency = activeData?.activeOffer?.currency;
|
|
105
|
+
final entitlement = activeData?.entitlements?.firstOrNull;
|
|
106
|
+
final expirationDate = entitlement?.expirationDate;
|
|
107
|
+
final willRenew = entitlement?.willRenew ?? false;
|
|
108
|
+
final isInTrial = entitlement?.isInTrial ?? false;
|
|
109
|
+
|
|
110
|
+
return KasyOverlayScaffold(
|
|
111
|
+
title: t.billing_title,
|
|
112
|
+
onBack: _goBack,
|
|
113
|
+
slivers: [
|
|
114
|
+
SliverToBoxAdapter(
|
|
115
|
+
child: Padding(
|
|
116
|
+
// Horizontal gutter already comes from KasyOverlayScaffold; keep
|
|
117
|
+
// only vertical spacing here so the side padding matches the
|
|
118
|
+
// Notifications screen (a single gutter, not doubled).
|
|
119
|
+
padding: const EdgeInsets.fromLTRB(
|
|
120
|
+
0,
|
|
121
|
+
KasySpacing.sm,
|
|
122
|
+
0,
|
|
123
|
+
KasySpacing.xxl,
|
|
124
|
+
),
|
|
125
|
+
child: Column(
|
|
126
|
+
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
127
|
+
children: [
|
|
128
|
+
// ── Plan info ─────────────────────────────────────────────
|
|
129
|
+
_SectionLabel(t.plan_label),
|
|
130
|
+
const SizedBox(height: KasySpacing.xs),
|
|
131
|
+
_PlanCard(
|
|
132
|
+
planName: planName,
|
|
133
|
+
price: price,
|
|
134
|
+
currency: currency,
|
|
135
|
+
expirationDate: expirationDate,
|
|
136
|
+
willRenew: willRenew,
|
|
137
|
+
isLifetime: sub?.isLifetime ?? false,
|
|
138
|
+
isInTrial: isInTrial,
|
|
139
|
+
),
|
|
140
|
+
|
|
141
|
+
const SizedBox(height: KasySpacing.xl),
|
|
142
|
+
|
|
143
|
+
// ── Actions ───────────────────────────────────────────────
|
|
144
|
+
_ActionsCard(
|
|
145
|
+
onManage: _isManaging ? null : _handleManageTap,
|
|
146
|
+
onRestore: _handleRestoreTap,
|
|
147
|
+
isManaging: _isManaging,
|
|
148
|
+
isRestoring: _isRestoring,
|
|
149
|
+
manageLabel: t.manage_subscription,
|
|
150
|
+
restoreLabel: t.restore_purchases,
|
|
151
|
+
),
|
|
152
|
+
],
|
|
153
|
+
),
|
|
154
|
+
),
|
|
155
|
+
),
|
|
156
|
+
],
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ─── Private sub-widgets ──────────────────────────────────────────────────────
|
|
73
162
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
foregroundColor: context.colors.onPrimary,
|
|
87
|
-
onPressed: widget.onExit,
|
|
88
|
-
semanticLabel:
|
|
89
|
-
MaterialLocalizations.of(context).closeButtonTooltip,
|
|
90
|
-
)
|
|
91
|
-
: null,
|
|
92
|
-
automaticallyImplyLeading: widget.onExit == null,
|
|
163
|
+
class _SectionLabel extends StatelessWidget {
|
|
164
|
+
final String label;
|
|
165
|
+
const _SectionLabel(this.label);
|
|
166
|
+
|
|
167
|
+
@override
|
|
168
|
+
Widget build(BuildContext context) {
|
|
169
|
+
return Padding(
|
|
170
|
+
padding: const EdgeInsets.only(left: KasySpacing.xs),
|
|
171
|
+
child: Text(
|
|
172
|
+
label,
|
|
173
|
+
style: context.kasyTextTheme.sectionLabel.copyWith(
|
|
174
|
+
color: context.colors.muted,
|
|
93
175
|
),
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
176
|
+
),
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
class _PlanCard extends StatelessWidget {
|
|
182
|
+
final String planName;
|
|
183
|
+
final double? price;
|
|
184
|
+
final String? currency;
|
|
185
|
+
final DateTime? expirationDate;
|
|
186
|
+
final bool willRenew;
|
|
187
|
+
final bool isLifetime;
|
|
188
|
+
final bool isInTrial;
|
|
189
|
+
|
|
190
|
+
const _PlanCard({
|
|
191
|
+
required this.planName,
|
|
192
|
+
required this.price,
|
|
193
|
+
required this.currency,
|
|
194
|
+
required this.expirationDate,
|
|
195
|
+
required this.willRenew,
|
|
196
|
+
required this.isLifetime,
|
|
197
|
+
required this.isInTrial,
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
String? _formattedPrice(BuildContext context) {
|
|
201
|
+
if (price == null || currency == null || isLifetime) return null;
|
|
202
|
+
final locale = Localizations.localeOf(context).toString();
|
|
203
|
+
return NumberFormat.simpleCurrency(locale: locale, name: currency)
|
|
204
|
+
.format(price);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
String? _dateLabel(BuildContext context, {String? formattedPrice}) {
|
|
208
|
+
if (isLifetime || expirationDate == null) return null;
|
|
209
|
+
final locale = Localizations.localeOf(context).toString();
|
|
210
|
+
final formatted = DateFormat.yMMMd(locale).format(expirationDate!);
|
|
211
|
+
final t = context.t.activePremium;
|
|
212
|
+
if (isInTrial) {
|
|
213
|
+
if (formattedPrice == null) return t.trial_until(date: formatted);
|
|
214
|
+
return t.charges_from(price: formattedPrice, date: formatted);
|
|
215
|
+
}
|
|
216
|
+
return willRenew ? t.renews_on(date: formatted) : t.expires_on(date: formatted);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
@override
|
|
220
|
+
Widget build(BuildContext context) {
|
|
221
|
+
final t = context.t.activePremium;
|
|
222
|
+
final formattedPrice = _formattedPrice(context);
|
|
223
|
+
final dateLabel = _dateLabel(context, formattedPrice: formattedPrice);
|
|
224
|
+
return KasyCard(
|
|
225
|
+
borderRadius: BorderRadius.circular(KasyRadius.lg),
|
|
226
|
+
padding: const EdgeInsets.symmetric(
|
|
227
|
+
horizontal: KasySpacing.md,
|
|
228
|
+
vertical: KasySpacing.xs,
|
|
229
|
+
),
|
|
230
|
+
child: Column(
|
|
231
|
+
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
232
|
+
children: [
|
|
233
|
+
Padding(
|
|
234
|
+
padding: const EdgeInsets.symmetric(vertical: KasySpacing.sm),
|
|
235
|
+
child: Row(
|
|
236
|
+
children: [
|
|
237
|
+
Icon(
|
|
238
|
+
KasyIcons.payment,
|
|
239
|
+
size: KasyIconSize.rowLeading,
|
|
240
|
+
color: context.colors.onSurface,
|
|
241
|
+
),
|
|
242
|
+
const SizedBox(width: KasySpacing.sm),
|
|
243
|
+
Expanded(
|
|
244
|
+
child: Text(
|
|
245
|
+
planName,
|
|
246
|
+
style: context.textTheme.titleSmall?.copyWith(
|
|
247
|
+
color: context.colors.onSurface,
|
|
117
248
|
),
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
249
|
+
),
|
|
250
|
+
),
|
|
251
|
+
if (isInTrial)
|
|
252
|
+
_TrialBadge(label: t.trial_label)
|
|
253
|
+
else if (formattedPrice != null)
|
|
254
|
+
Text(
|
|
255
|
+
formattedPrice,
|
|
256
|
+
style: context.textTheme.bodyMedium?.copyWith(
|
|
257
|
+
color: context.colors.muted,
|
|
126
258
|
),
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
// ),
|
|
142
|
-
const SizedBox(height: KasySpacing.xl),
|
|
143
|
-
if (userState.subscription?.isLifetime == true)
|
|
144
|
-
Padding(
|
|
145
|
-
padding: const EdgeInsets.all(KasySpacing.lg),
|
|
146
|
-
child: Text(
|
|
147
|
-
Translations.of(
|
|
148
|
-
context,
|
|
149
|
-
).activePremium.lifetime_user_description,
|
|
150
|
-
style: context.textTheme.bodyMedium?.copyWith(
|
|
151
|
-
fontWeight: FontWeight.w700,
|
|
152
|
-
),
|
|
153
|
-
textAlign: TextAlign.center,
|
|
154
|
-
),
|
|
155
|
-
),
|
|
156
|
-
// SliverToBoxAdapter(
|
|
157
|
-
// child: Padding(
|
|
158
|
-
// padding: hPadding,
|
|
159
|
-
// child: ComparisonTable(
|
|
160
|
-
// rows: ComparisonTableRow.getComparisonRows(
|
|
161
|
-
// context,
|
|
162
|
-
// ref.read(environmentProvider),
|
|
163
|
-
// ),
|
|
164
|
-
// freeColumnTitle:
|
|
165
|
-
// ComparisonTableRow.getFreeColumnTitle(context),
|
|
166
|
-
// premiumColumnTitle:
|
|
167
|
-
// ComparisonTableRow.getPremiumColumnTitle(context),
|
|
168
|
-
// ),
|
|
169
|
-
// ),
|
|
170
|
-
// ),
|
|
171
|
-
if (userState.subscription?.isLifetime == false)
|
|
172
|
-
Padding(
|
|
173
|
-
padding: const EdgeInsets.fromLTRB(
|
|
174
|
-
KasySpacing.pageHorizontalGutter,
|
|
175
|
-
72,
|
|
176
|
-
KasySpacing.pageHorizontalGutter,
|
|
177
|
-
KasySpacing.lg,
|
|
178
|
-
),
|
|
179
|
-
child: KasyButton(
|
|
180
|
-
label: Translations.of(context).activePremium.unsubscribe_button,
|
|
181
|
-
variant: KasyButtonVariant.soft,
|
|
182
|
-
expand: true,
|
|
183
|
-
onPressed: () => _handleManageTap(context),
|
|
184
|
-
),
|
|
185
|
-
),
|
|
186
|
-
],
|
|
259
|
+
),
|
|
260
|
+
],
|
|
261
|
+
),
|
|
262
|
+
),
|
|
263
|
+
if (dateLabel != null)
|
|
264
|
+
Padding(
|
|
265
|
+
padding: const EdgeInsets.only(
|
|
266
|
+
left: KasyIconSize.rowLeading + KasySpacing.sm,
|
|
267
|
+
bottom: KasySpacing.sm,
|
|
268
|
+
),
|
|
269
|
+
child: Text(
|
|
270
|
+
dateLabel,
|
|
271
|
+
style: context.textTheme.bodySmall?.copyWith(
|
|
272
|
+
color: context.colors.muted,
|
|
187
273
|
),
|
|
188
274
|
),
|
|
189
275
|
),
|
|
190
|
-
|
|
276
|
+
],
|
|
277
|
+
),
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
class _TrialBadge extends StatelessWidget {
|
|
283
|
+
final String label;
|
|
284
|
+
const _TrialBadge({required this.label});
|
|
285
|
+
|
|
286
|
+
@override
|
|
287
|
+
Widget build(BuildContext context) {
|
|
288
|
+
return Container(
|
|
289
|
+
padding: const EdgeInsets.symmetric(
|
|
290
|
+
horizontal: KasySpacing.sm,
|
|
291
|
+
vertical: 3,
|
|
292
|
+
),
|
|
293
|
+
decoration: BoxDecoration(
|
|
294
|
+
color: context.colors.accent.withValues(alpha: 0.12),
|
|
295
|
+
borderRadius: BorderRadius.circular(KasyRadius.sm),
|
|
296
|
+
),
|
|
297
|
+
child: Text(
|
|
298
|
+
label,
|
|
299
|
+
style: context.textTheme.labelSmall?.copyWith(
|
|
300
|
+
color: context.colors.accent,
|
|
301
|
+
fontWeight: FontWeight.w600,
|
|
191
302
|
),
|
|
192
303
|
),
|
|
193
304
|
);
|
|
194
305
|
}
|
|
195
306
|
}
|
|
307
|
+
|
|
308
|
+
class _ActionsCard extends StatelessWidget {
|
|
309
|
+
final VoidCallback? onManage;
|
|
310
|
+
final VoidCallback onRestore;
|
|
311
|
+
final bool isManaging;
|
|
312
|
+
final bool isRestoring;
|
|
313
|
+
final String manageLabel;
|
|
314
|
+
final String restoreLabel;
|
|
315
|
+
|
|
316
|
+
const _ActionsCard({
|
|
317
|
+
required this.onManage,
|
|
318
|
+
required this.onRestore,
|
|
319
|
+
required this.isManaging,
|
|
320
|
+
required this.isRestoring,
|
|
321
|
+
required this.manageLabel,
|
|
322
|
+
required this.restoreLabel,
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
@override
|
|
326
|
+
Widget build(BuildContext context) {
|
|
327
|
+
return KasyCard(
|
|
328
|
+
borderRadius: BorderRadius.circular(KasyRadius.lg),
|
|
329
|
+
padding: const EdgeInsets.symmetric(
|
|
330
|
+
horizontal: KasySpacing.md,
|
|
331
|
+
vertical: KasySpacing.xs,
|
|
332
|
+
),
|
|
333
|
+
child: Column(
|
|
334
|
+
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
335
|
+
children: [
|
|
336
|
+
SettingsTile(
|
|
337
|
+
icon: KasyIcons.payment,
|
|
338
|
+
title: manageLabel,
|
|
339
|
+
onTap: isManaging ? () {} : (onManage ?? () {}),
|
|
340
|
+
trailingWidget: isManaging
|
|
341
|
+
? SizedBox(
|
|
342
|
+
width: KasyIconSize.rowTrailing,
|
|
343
|
+
height: KasyIconSize.rowTrailing,
|
|
344
|
+
child: CircularProgressIndicator.adaptive(
|
|
345
|
+
strokeWidth: 2,
|
|
346
|
+
valueColor: AlwaysStoppedAnimation(context.colors.muted),
|
|
347
|
+
),
|
|
348
|
+
)
|
|
349
|
+
: null,
|
|
350
|
+
),
|
|
351
|
+
const SettingsDivider(),
|
|
352
|
+
SettingsTile(
|
|
353
|
+
icon: KasyIcons.refresh,
|
|
354
|
+
title: restoreLabel,
|
|
355
|
+
onTap: isRestoring ? () {} : onRestore,
|
|
356
|
+
trailingWidget: isRestoring
|
|
357
|
+
? SizedBox(
|
|
358
|
+
width: KasyIconSize.rowTrailing,
|
|
359
|
+
height: KasyIconSize.rowTrailing,
|
|
360
|
+
child: CircularProgressIndicator.adaptive(
|
|
361
|
+
strokeWidth: 2,
|
|
362
|
+
valueColor: AlwaysStoppedAnimation(context.colors.muted),
|
|
363
|
+
),
|
|
364
|
+
)
|
|
365
|
+
: null,
|
|
366
|
+
),
|
|
367
|
+
],
|
|
368
|
+
),
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import 'package:flutter/material.dart';
|
|
2
2
|
import 'package:flutter_animate/flutter_animate.dart';
|
|
3
|
-
import 'package:google_fonts/google_fonts.dart';
|
|
4
3
|
import 'package:kasy_kit/components/components.dart';
|
|
5
4
|
import 'package:kasy_kit/core/data/models/subscription.dart';
|
|
6
5
|
import 'package:kasy_kit/core/theme/theme.dart';
|
|
@@ -147,9 +146,7 @@ class PaywallMinimal extends StatelessWidget {
|
|
|
147
146
|
child: Text(
|
|
148
147
|
Translations.of(context).premium.title_1,
|
|
149
148
|
textAlign: TextAlign.center,
|
|
150
|
-
style:
|
|
151
|
-
fontSize: 28,
|
|
152
|
-
fontWeight: FontWeight.w700,
|
|
149
|
+
style: context.kasyTextTheme.pageTitle.copyWith(
|
|
153
150
|
color: context.colors.onPrimary,
|
|
154
151
|
letterSpacing: -0.8,
|
|
155
152
|
),
|
|
@@ -191,7 +188,6 @@ class PaywallMinimal extends StatelessWidget {
|
|
|
191
188
|
_effectiveOffer.formattedPrice(context),
|
|
192
189
|
textAlign: TextAlign.center,
|
|
193
190
|
style: context.textTheme.titleLarge?.copyWith(
|
|
194
|
-
fontWeight: FontWeight.w600,
|
|
195
191
|
color: context.colors.onPrimary,
|
|
196
192
|
),
|
|
197
193
|
),
|
|
@@ -123,8 +123,7 @@ class _BasicPaywallRowState extends ConsumerState<PaywallRow> {
|
|
|
123
123
|
child: Text(
|
|
124
124
|
translations.title_1,
|
|
125
125
|
textAlign: TextAlign.center,
|
|
126
|
-
style: context.
|
|
127
|
-
fontWeight: FontWeight.w700,
|
|
126
|
+
style: context.kasyTextTheme.pageTitle.copyWith(
|
|
128
127
|
color: context.colors.onBackground,
|
|
129
128
|
letterSpacing: -0.8,
|
|
130
129
|
),
|
|
@@ -215,7 +214,6 @@ class _BasicPaywallRowState extends ConsumerState<PaywallRow> {
|
|
|
215
214
|
child: Text(Translations.of(context).premium.comparison.title,
|
|
216
215
|
textAlign: TextAlign.center,
|
|
217
216
|
style: context.textTheme.headlineSmall?.copyWith(
|
|
218
|
-
fontWeight: FontWeight.w600,
|
|
219
217
|
color: context.colors.onBackground,
|
|
220
218
|
),
|
|
221
219
|
),
|
|
@@ -268,7 +266,6 @@ class _BasicPaywallRowState extends ConsumerState<PaywallRow> {
|
|
|
268
266
|
translations.payment_cancel_reassurance,
|
|
269
267
|
textAlign: TextAlign.center,
|
|
270
268
|
style: context.textTheme.bodyMedium?.copyWith(
|
|
271
|
-
fontWeight: FontWeight.w400,
|
|
272
269
|
color: context.colors.background.withValues(alpha: .9),
|
|
273
270
|
),
|
|
274
271
|
),
|
|
@@ -287,7 +284,6 @@ class _BasicPaywallRowState extends ConsumerState<PaywallRow> {
|
|
|
287
284
|
Text(
|
|
288
285
|
translations.payment_cancel_reassurance_free_trial,
|
|
289
286
|
style: context.textTheme.bodyMedium?.copyWith(
|
|
290
|
-
fontWeight: FontWeight.w400,
|
|
291
287
|
color: context.colors.background.withValues(alpha: .9),
|
|
292
288
|
),
|
|
293
289
|
),
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import 'package:flutter/material.dart';
|
|
2
2
|
import 'package:flutter_animate/flutter_animate.dart';
|
|
3
|
-
import 'package:google_fonts/google_fonts.dart';
|
|
4
3
|
import 'package:kasy_kit/components/components.dart';
|
|
5
4
|
import 'package:kasy_kit/core/data/models/subscription.dart';
|
|
6
5
|
import 'package:kasy_kit/core/theme/theme.dart';
|
|
@@ -114,9 +113,7 @@ class PaywallWithSwitch extends StatelessWidget {
|
|
|
114
113
|
child: Text(
|
|
115
114
|
title,
|
|
116
115
|
textAlign: TextAlign.left,
|
|
117
|
-
style:
|
|
118
|
-
fontSize: 24,
|
|
119
|
-
fontWeight: FontWeight.w700,
|
|
116
|
+
style: context.kasyTextTheme.pageTitle.copyWith(
|
|
120
117
|
color: context.colors.background,
|
|
121
118
|
letterSpacing: -0.8,
|
|
122
119
|
),
|
|
@@ -164,7 +161,7 @@ class PaywallWithSwitch extends StatelessWidget {
|
|
|
164
161
|
key: ValueKey(detailedPrice),
|
|
165
162
|
detailedPrice,
|
|
166
163
|
textAlign: TextAlign.center,
|
|
167
|
-
style: context.textTheme.
|
|
164
|
+
style: context.textTheme.bodyLarge?.copyWith(
|
|
168
165
|
fontWeight: FontWeight.w500,
|
|
169
166
|
color: context.colors.background,
|
|
170
167
|
),
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import 'package:flutter/foundation.dart';
|
|
2
2
|
import 'package:flutter/material.dart';
|
|
3
3
|
import 'package:flutter_animate/flutter_animate.dart';
|
|
4
|
-
import 'package:google_fonts/google_fonts.dart';
|
|
5
4
|
import 'package:kasy_kit/components/components.dart';
|
|
6
5
|
import 'package:kasy_kit/core/animations/movefade_anim.dart';
|
|
7
6
|
import 'package:kasy_kit/core/data/models/subscription.dart';
|
|
@@ -72,9 +71,7 @@ class BasicPaywall extends StatelessWidget {
|
|
|
72
71
|
child: Text(
|
|
73
72
|
Translations.of(context).premium.title_1,
|
|
74
73
|
textAlign: TextAlign.center,
|
|
75
|
-
style:
|
|
76
|
-
fontSize: 32,
|
|
77
|
-
fontWeight: FontWeight.w700,
|
|
74
|
+
style: context.kasyTextTheme.pageTitle.copyWith(
|
|
78
75
|
color: context.colors.onPrimary,
|
|
79
76
|
),
|
|
80
77
|
),
|
|
@@ -104,10 +104,9 @@ class FeatureWithTwoLines extends StatelessWidget {
|
|
|
104
104
|
Flexible(
|
|
105
105
|
child: Text(
|
|
106
106
|
title,
|
|
107
|
-
style: context.textTheme.
|
|
107
|
+
style: context.textTheme.titleMedium?.copyWith(
|
|
108
108
|
color: context.colors.onBackground,
|
|
109
109
|
decoration: crossed ? TextDecoration.lineThrough : null,
|
|
110
|
-
fontWeight: FontWeight.w900,
|
|
111
110
|
),
|
|
112
111
|
),
|
|
113
112
|
),
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import 'package:flutter/material.dart';
|
|
2
|
-
import 'package:google_fonts/google_fonts.dart';
|
|
3
2
|
import 'package:kasy_kit/core/theme/theme.dart';
|
|
4
3
|
import 'package:kasy_kit/core/widgets/kasy_focus_ring.dart';
|
|
5
4
|
|
|
@@ -55,18 +54,14 @@ class PremiumWideBanner extends StatelessWidget {
|
|
|
55
54
|
children: [
|
|
56
55
|
Text(
|
|
57
56
|
smallText.toUpperCase(),
|
|
58
|
-
style:
|
|
59
|
-
fontSize: 12,
|
|
60
|
-
fontWeight: FontWeight.w700,
|
|
57
|
+
style: context.kasyTextTheme.sectionLabel.copyWith(
|
|
61
58
|
color: context.colors.premiumBannerText,
|
|
62
59
|
),
|
|
63
60
|
),
|
|
64
61
|
const SizedBox(height: KasySpacing.sm),
|
|
65
62
|
Text(
|
|
66
63
|
title,
|
|
67
|
-
style:
|
|
68
|
-
fontSize: 20,
|
|
69
|
-
fontWeight: FontWeight.w400,
|
|
64
|
+
style: context.textTheme.titleLarge?.copyWith(
|
|
70
65
|
color: context.colors.onPrimary,
|
|
71
66
|
),
|
|
72
67
|
overflow: TextOverflow.clip,
|