kasy-cli 1.31.13 → 1.32.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.
Files changed (101) hide show
  1. package/lib/commands/new.js +15 -1
  2. package/lib/scaffold/CHANGELOG.json +9 -0
  3. package/lib/scaffold/backends/api/patch/README.md +87 -2
  4. package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api.dart +34 -0
  5. package/lib/scaffold/backends/api/patch/lib/features/subscriptions/api/stripe_backend_api.dart +12 -2
  6. package/lib/scaffold/backends/firebase/setup-from-scratch.js +22 -0
  7. package/lib/scaffold/backends/supabase/deploy.js +5 -0
  8. package/lib/scaffold/backends/supabase/edge-functions/send-push-notification/README.md +26 -22
  9. package/lib/scaffold/backends/supabase/edge-functions/send-push-notification/index.ts +8 -11
  10. package/lib/scaffold/backends/supabase/edge-functions/stripe-create-checkout-session/index.ts +3 -1
  11. package/lib/scaffold/backends/supabase/edge-functions/stripe-create-portal-session/index.ts +60 -3
  12. package/lib/scaffold/backends/supabase/patch/lib/features/authentication/api/authentication_api.dart +69 -17
  13. package/lib/scaffold/backends/supabase/patch/lib/features/subscriptions/api/stripe_backend_api.dart +12 -2
  14. package/lib/scaffold/backends/supabase/pubspec.yaml.tpl +6 -0
  15. package/lib/scaffold/generate.js +1 -1
  16. package/lib/scaffold/shared/generator-utils.js +22 -3
  17. package/lib/utils/i18n/messages-en.js +2 -0
  18. package/lib/utils/i18n/messages-es.js +2 -0
  19. package/lib/utils/i18n/messages-pt.js +2 -0
  20. package/package.json +2 -2
  21. package/templates/firebase/docs/auth-setup.en.md +7 -1
  22. package/templates/firebase/docs/auth-setup.es.md +7 -1
  23. package/templates/firebase/docs/auth-setup.pt.md +7 -1
  24. package/templates/firebase/functions/src/subscriptions/stripe_functions.ts +79 -5
  25. package/templates/firebase/lib/components/kasy_accordion.dart +2 -2
  26. package/templates/firebase/lib/components/kasy_alert.dart +1 -1
  27. package/templates/firebase/lib/components/kasy_app_bar.dart +3 -3
  28. package/templates/firebase/lib/components/kasy_bottom_sheet.dart +1 -1
  29. package/templates/firebase/lib/components/kasy_button.dart +8 -8
  30. package/templates/firebase/lib/components/kasy_chip.dart +1 -1
  31. package/templates/firebase/lib/components/kasy_date_picker.dart +26 -21
  32. package/templates/firebase/lib/components/kasy_dialog.dart +2 -2
  33. package/templates/firebase/lib/components/kasy_sidebar.dart +62 -11
  34. package/templates/firebase/lib/components/kasy_tabs.dart +2 -2
  35. package/templates/firebase/lib/components/kasy_text_area.dart +37 -5
  36. package/templates/firebase/lib/components/kasy_text_field.dart +77 -16
  37. package/templates/firebase/lib/components/kasy_toast.dart +1 -1
  38. package/templates/firebase/lib/components/kasy_web_header.dart +4 -3
  39. package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +6 -0
  40. package/templates/firebase/lib/core/bottom_menu/notification_bottom_item.dart +16 -37
  41. package/templates/firebase/lib/core/config/features.dart +13 -0
  42. package/templates/firebase/lib/core/dev_inspector/dev_inspector.dart +21 -0
  43. package/templates/firebase/lib/core/rating/widgets/rate_banner.dart +1 -1
  44. package/templates/firebase/lib/core/rating/widgets/review_popup.dart +1 -1
  45. package/templates/firebase/lib/core/theme/icon_sizes.dart +47 -0
  46. package/templates/firebase/lib/core/theme/shadows.dart +13 -0
  47. package/templates/firebase/lib/core/theme/texts.dart +32 -0
  48. package/templates/firebase/lib/core/theme/theme.dart +2 -0
  49. package/templates/firebase/lib/core/web_device_preview/web_device_preview.dart +3 -0
  50. package/templates/firebase/lib/core/web_viewport_scale.dart +23 -4
  51. package/templates/firebase/lib/core/widgets/update_bottom_sheet.dart +1 -1
  52. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_tile.dart +1 -1
  53. package/templates/firebase/lib/features/authentication/ui/components/otp_verification.dart +1 -1
  54. package/templates/firebase/lib/features/authentication/ui/components/phone_input.dart +2 -1
  55. package/templates/firebase/lib/features/authentication/ui/recover_password_page.dart +3 -1
  56. package/templates/firebase/lib/features/authentication/ui/signin_page.dart +36 -14
  57. package/templates/firebase/lib/features/authentication/ui/signup_page.dart +27 -11
  58. package/templates/firebase/lib/features/authentication/ui/widgets/recover_password_result.dart +1 -1
  59. package/templates/firebase/lib/features/feedbacks/ui/feedback_page.dart +1 -1
  60. package/templates/firebase/lib/features/feedbacks/ui/widgets/add_feature_button.dart +1 -1
  61. package/templates/firebase/lib/features/feedbacks/ui/widgets/feature_card.dart +1 -1
  62. package/templates/firebase/lib/features/home/home_components_preview_registry.dart +22 -3
  63. package/templates/firebase/lib/features/home/home_image_grid.dart +1 -1
  64. package/templates/firebase/lib/features/local_reminders/ui/reminder_page.dart +2 -2
  65. package/templates/firebase/lib/features/notifications/providers/unread_notifications_count_provider.dart +17 -0
  66. package/templates/firebase/lib/features/notifications/ui/widgets/empty_notifications.dart +6 -1
  67. package/templates/firebase/lib/features/notifications/ui/widgets/notification_tile.dart +35 -38
  68. package/templates/firebase/lib/features/notifications/ui/widgets/permission_request_view.dart +1 -1
  69. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_loader.dart +1 -1
  70. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_module_mockups.dart +3 -3
  71. package/templates/firebase/lib/features/onboarding/ui/widgets/selectable_row_tile.dart +1 -1
  72. package/templates/firebase/lib/features/settings/settings_page.dart +264 -307
  73. package/templates/firebase/lib/features/settings/ui/components/avatar_component.dart +2 -2
  74. package/templates/firebase/lib/features/settings/ui/components/delete_user_component.dart +13 -6
  75. package/templates/firebase/lib/features/settings/ui/components/edit_name_sheet.dart +115 -0
  76. package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +2 -2
  77. package/templates/firebase/lib/features/settings/ui/widgets/settings_bottom_sheet_option_tile.dart +1 -1
  78. package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +13 -5
  79. package/templates/firebase/lib/features/subscriptions/api/stripe_backend_api.dart +12 -2
  80. package/templates/firebase/lib/features/subscriptions/api/stripe_payment_api.dart +7 -1
  81. package/templates/firebase/lib/features/subscriptions/providers/premium_page_provider.dart +11 -3
  82. package/templates/firebase/lib/features/subscriptions/ui/component/paywall_row.dart +1 -1
  83. package/templates/firebase/lib/features/subscriptions/ui/widgets/comparison_table.dart +1 -1
  84. package/templates/firebase/lib/features/subscriptions/ui/widgets/feature_line.dart +2 -2
  85. package/templates/firebase/lib/features/subscriptions/ui/widgets/premium_close_button.dart +1 -1
  86. package/templates/firebase/lib/features/subscriptions/ui/widgets/premium_feature.dart +1 -1
  87. package/templates/firebase/lib/features/subscriptions/ui/widgets/selectable_col.dart +3 -3
  88. package/templates/firebase/lib/features/subscriptions/ui/widgets/selectable_row.dart +1 -1
  89. package/templates/firebase/lib/i18n/en.i18n.json +10 -1
  90. package/templates/firebase/lib/i18n/es.i18n.json +10 -1
  91. package/templates/firebase/lib/i18n/pt.i18n.json +10 -1
  92. package/templates/firebase/pubspec.yaml +0 -1
  93. package/templates/firebase/web/stripe_success.html +64 -26
  94. package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/animations/movefade_anim.dart +0 -34
  95. package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/components/onboarding_att_setup.dart +0 -67
  96. package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +0 -183
  97. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/animations/movefade_anim.dart +0 -34
  98. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/components/onboarding_att_setup.dart +0 -67
  99. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +0 -183
  100. package/templates/firebase/lib/features/authentication/ui/components/facebook_signin.dart +0 -19
  101. package/templates/firebase/login-redesign-preview.png +0 -0
@@ -4,84 +4,112 @@
4
4
 
5
5
  Stripe redirects the checkout tab here (success_url) after a successful
6
6
  payment. It is intentionally a tiny standalone page — NOT the full Flutter
7
- app — so the user does not end up with two heavy app tabs open. The original
8
- app tab keeps polling and flips to premium on its own (via the webhook), so
9
- here we just congratulate the user and let them close this tab / return.
7
+ app — so the user does not end up with two heavy app tabs open, and so the
8
+ "return to the app" button can reliably window.close() this tab (only a light
9
+ page opened via window.open is allowed to close itself). The original app tab
10
+ keeps polling and flips to premium on its own (via the webhook).
10
11
 
11
- Localized client-side from navigator.language (pt / es / en).
12
+ Styled to match the kit's design system (Inter + the same color tokens as the
13
+ Flutter app). Localized from the ?lang= the app appends (the language picked
14
+ IN the app), falling back to the browser language.
12
15
  -->
13
16
  <html lang="en">
14
17
  <head>
15
18
  <meta charset="UTF-8" />
16
19
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
17
20
  <title>Payment complete</title>
21
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
22
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
23
+ <link
24
+ href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
25
+ rel="stylesheet"
26
+ />
18
27
  <style>
28
+ /* Kit design tokens (mirror lib/core/theme/colors.dart). */
19
29
  :root {
20
30
  --accent: #0485F7;
21
- --success: #16A34A;
22
- --bg: #F6F8FB;
31
+ --accent-fg: #FFFFFF;
32
+ --success: #17C964;
33
+ --bg: #F5F5F5;
23
34
  --card: #FFFFFF;
24
- --text: #0B1524;
25
- --muted: #5B6776;
26
- --border: rgba(11, 21, 36, 0.08);
35
+ --text: #18181B;
36
+ --muted: #71717A;
37
+ --border: rgba(24, 24, 27, 0.10);
38
+ --shadow: 0 12px 40px rgba(24, 24, 27, 0.10);
27
39
  }
28
40
  @media (prefers-color-scheme: dark) {
29
41
  :root {
30
- --bg: #0B1220;
31
- --card: #131C2B;
32
- --text: #F3F6FB;
33
- --muted: #9AA7B8;
34
- --border: rgba(255, 255, 255, 0.10);
42
+ --bg: #060607;
43
+ --card: #18181B;
44
+ --text: #FCFCFC;
45
+ --muted: #A1A1AA;
46
+ --border: rgba(255, 255, 255, 0.08);
47
+ --shadow: 0 12px 40px rgba(0, 0, 0, 0.45);
35
48
  }
36
49
  }
37
50
  * { box-sizing: border-box; }
38
51
  html, body { height: 100%; margin: 0; }
39
52
  body {
40
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
41
- Helvetica, Arial, sans-serif;
53
+ font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI",
54
+ Roboto, Helvetica, Arial, sans-serif;
42
55
  background: var(--bg);
43
56
  color: var(--text);
44
57
  display: flex;
45
58
  align-items: center;
46
59
  justify-content: center;
47
60
  padding: 24px;
61
+ -webkit-font-smoothing: antialiased;
62
+ -moz-osx-font-smoothing: grayscale;
48
63
  }
49
64
  .card {
50
65
  width: 100%;
51
66
  max-width: 420px;
52
67
  background: var(--card);
53
68
  border: 1px solid var(--border);
54
- border-radius: 20px;
69
+ border-radius: 24px;
55
70
  padding: 40px 32px;
56
71
  text-align: center;
57
- box-shadow: 0 12px 40px rgba(11, 21, 36, 0.08);
72
+ box-shadow: var(--shadow);
58
73
  }
59
74
  .check {
60
75
  width: 72px;
61
76
  height: 72px;
62
77
  margin: 0 auto 24px;
63
78
  border-radius: 50%;
64
- background: rgba(22, 163, 74, 0.12);
79
+ background: rgba(23, 201, 100, 0.12);
65
80
  display: flex;
66
81
  align-items: center;
67
82
  justify-content: center;
68
83
  }
69
- .check svg { width: 38px; height: 38px; stroke: var(--success); }
70
- h1 { font-size: 22px; font-weight: 700; margin: 0 0 10px; letter-spacing: -0.4px; }
71
- p { font-size: 15px; line-height: 1.5; color: var(--muted); margin: 0 0 28px; }
84
+ .check svg { width: 36px; height: 36px; stroke: var(--success); }
85
+ h1 {
86
+ font-size: 24px;
87
+ font-weight: 700;
88
+ line-height: 32px;
89
+ letter-spacing: -0.2px;
90
+ margin: 0 0 10px;
91
+ }
92
+ p {
93
+ font-size: 16px;
94
+ line-height: 24px;
95
+ color: var(--muted);
96
+ margin: 0 0 28px;
97
+ }
72
98
  button {
73
99
  width: 100%;
74
100
  border: none;
75
- border-radius: 12px;
101
+ border-radius: 14px;
76
102
  padding: 14px 20px;
77
- font-size: 15px;
103
+ font-family: inherit;
104
+ font-size: 16px;
78
105
  font-weight: 600;
79
- color: #FFFFFF;
106
+ color: var(--accent-fg);
80
107
  background: var(--accent);
81
108
  cursor: pointer;
82
109
  transition: opacity 0.15s ease;
83
110
  }
84
111
  button:hover { opacity: 0.92; }
112
+ button:active { opacity: 0.85; }
85
113
  </style>
86
114
  </head>
87
115
  <body>
@@ -119,7 +147,17 @@
119
147
  },
120
148
  };
121
149
 
122
- var lang = (navigator.language || "en").slice(0, 2).toLowerCase();
150
+ // Prefer the language the app passed (?lang=xx) — the one the user picked
151
+ // IN the app — then fall back to the browser language.
152
+ function pickLang() {
153
+ try {
154
+ var q = new URLSearchParams(window.location.search).get("lang");
155
+ if (q) return q.slice(0, 2).toLowerCase();
156
+ } catch (e) {}
157
+ return (navigator.language || "en").slice(0, 2).toLowerCase();
158
+ }
159
+
160
+ var lang = pickLang();
123
161
  var t = I18N[lang] || I18N.en;
124
162
  document.documentElement.lang = lang in I18N ? lang : "en";
125
163
  document.title = t.doc;
@@ -1,34 +0,0 @@
1
- import 'package:flutter/material.dart';
2
- import 'package:flutter_animate/flutter_animate.dart';
3
-
4
- class MoveFadeAnim extends StatelessWidget {
5
- final int? delayInMs;
6
- final Widget child;
7
-
8
- const MoveFadeAnim({
9
- super.key,
10
- required this.child,
11
- this.delayInMs,
12
- });
13
-
14
- @override
15
- Widget build(BuildContext context) {
16
- return Animate(
17
- effects: [
18
- FadeEffect(
19
- delay: Duration(milliseconds: delayInMs ?? 0),
20
- duration: const Duration(milliseconds: 200),
21
- curve: Curves.easeIn,
22
- ),
23
- MoveEffect(
24
- delay: Duration(milliseconds: delayInMs ?? 0),
25
- duration: const Duration(milliseconds: 450),
26
- curve: Curves.easeOut,
27
- begin: const Offset(0, 50),
28
- end: Offset.zero,
29
- ),
30
- ],
31
- child: child,
32
- );
33
- }
34
- }
@@ -1,67 +0,0 @@
1
- import 'package:flutter/material.dart';
2
- import 'package:flutter_riverpod/flutter_riverpod.dart';
3
- import 'package:kasy_kit/components/components.dart';
4
- import 'package:kasy_kit/core/data/api/analytics_api.dart';
5
- import 'package:kasy_kit/core/data/api/tracking_api.dart';
6
- import 'package:kasy_kit/core/states/user_state_notifier.dart';
7
- import 'package:kasy_kit/features/onboarding/ui/widgets/onboarding_background.dart';
8
- import 'package:kasy_kit/features/onboarding/ui/widgets/onboarding_illustration_scaffold.dart';
9
- import 'package:kasy_kit/features/onboarding/ui/widgets/onboarding_module_mockups.dart';
10
- import 'package:kasy_kit/features/subscriptions/repositories/subscription_repository.dart';
11
- import 'package:kasy_kit/i18n/translations.g.dart';
12
- import 'package:permission_handler/permission_handler.dart';
13
-
14
- /// ATT Permission Step
15
- /// ATT is only available on iOS
16
- /// This is the consent screen for iOS 14+ that asks the user to allow the app to access the ATT framework
17
- /// In a few words you need this to get the IDFA (Identifier for Advertisers) which is used for tracking purposes
18
- /// So you can create better facebook ads, google ads, etc...
19
- class AttPermissionStep extends ConsumerWidget {
20
- final String nextRoute;
21
-
22
- const AttPermissionStep({
23
- super.key,
24
- required this.nextRoute,
25
- });
26
-
27
- @override
28
- Widget build(BuildContext context, WidgetRef ref) {
29
- final translations = Translations.of(context).onboarding.att;
30
-
31
- return OnboardingBackground(
32
- child: OnboardingIllustrationScaffold(
33
- step: 6,
34
- title: translations.title,
35
- description: translations.description,
36
- image: const TrackingPermissionMockup(),
37
- footerActions: [
38
- KasyButton(
39
- label: translations.continue_button,
40
- expand: true,
41
- onPressed: () async {
42
- final Map<Permission, PermissionStatus> permission = await [
43
- Permission.appTrackingTransparency,
44
- ].request();
45
- final isGranted =
46
- permission.values.first == PermissionStatus.granted;
47
- ref.read(analyticsApiProvider).logEvent('att_request', {
48
- 'granted': isGranted,
49
- });
50
- final userId =
51
- ref.read(userStateNotifierProvider).user.idOrNull;
52
- if (userId != null) {
53
- ref
54
- .read(subscriptionRepositoryProvider)
55
- .initUser(userId)
56
- .ignore();
57
- ref.read(facebookEventApiProvider).initUser(userId).ignore();
58
- }
59
- if (!context.mounted) return;
60
- Navigator.of(context).pushNamed(nextRoute);
61
- },
62
- ),
63
- ],
64
- ),
65
- );
66
- }
67
- }
@@ -1,183 +0,0 @@
1
- import 'package:flutter/material.dart';
2
- import 'package:flutter_animate/flutter_animate.dart';
3
- import 'package:flutter_riverpod/flutter_riverpod.dart';
4
- import 'package:kasy_kit/components/components.dart';
5
- import 'package:kasy_kit/core/animations/movefade_anim.dart';
6
- import 'package:kasy_kit/core/theme/theme.dart';
7
- import 'package:kasy_kit/core/widgets/responsive_layout.dart';
8
- import 'package:kasy_kit/features/onboarding/ui/widgets/onboarding_background.dart';
9
- import 'package:kasy_kit/features/onboarding/ui/widgets/onboarding_progress.dart';
10
- import 'package:kasy_kit/features/onboarding/ui/widgets/onboarding_step_header.dart';
11
- import 'package:kasy_kit/features/onboarding/ui/widgets/onboarding_sticky_footer.dart';
12
- import 'package:kasy_kit/features/onboarding/ui/widgets/selectable_row_tile.dart';
13
-
14
- typedef OptionBuilder = Widget Function(String key, bool selected);
15
-
16
- typedef ReassuranceBuilder = Widget? Function(String key);
17
-
18
- typedef OnOptionIdSelected = void Function(String id);
19
-
20
- typedef OnValidate = void Function(String? key);
21
-
22
- /// Single choice question with selectable tiles, matching the clean‑premium
23
- /// onboarding layout (shared header, left‑aligned title, sticky footer).
24
- class OnboardingRadioQuestion extends ConsumerStatefulWidget {
25
- final int step;
26
- final int totalSteps;
27
- final String title;
28
- final String description;
29
- final String btnText;
30
- final List<String> optionIds;
31
- final OptionBuilder optionBuilder;
32
- final ReassuranceBuilder? reassuranceBuilder;
33
- final OnOptionIdSelected? onOptionIdSelected;
34
- final OnValidate? onValidate;
35
-
36
- const OnboardingRadioQuestion({
37
- super.key,
38
- required this.title,
39
- required this.description,
40
- required this.btnText,
41
- required this.optionIds,
42
- required this.optionBuilder,
43
- required this.step,
44
- this.totalSteps = kOnboardingSteps,
45
- this.onOptionIdSelected,
46
- this.onValidate,
47
- this.reassuranceBuilder,
48
- });
49
-
50
- @override
51
- ConsumerState<OnboardingRadioQuestion> createState() =>
52
- _OnboardingRadioQuestionState();
53
- }
54
-
55
- class _OnboardingRadioQuestionState
56
- extends ConsumerState<OnboardingRadioQuestion> {
57
- String? selectedChoiceId;
58
-
59
- @override
60
- Widget build(BuildContext context) {
61
- const gutter = KasySpacing.lg;
62
-
63
- final scrollBody = Column(
64
- crossAxisAlignment: CrossAxisAlignment.start,
65
- children: [
66
- OnboardingStepHeader(step: widget.step, totalSteps: widget.totalSteps),
67
- Padding(
68
- padding: const EdgeInsets.fromLTRB(gutter, KasySpacing.lg, gutter, 0),
69
- child: MoveFadeAnim(
70
- delayInMs: 120,
71
- child: Text(
72
- widget.title,
73
- textAlign: TextAlign.start,
74
- style: context.textTheme.headlineMedium?.copyWith(
75
- color: context.colors.onBackground,
76
- fontSize: 26,
77
- fontWeight: FontWeight.w700,
78
- letterSpacing: -0.2,
79
- height: 1.2,
80
- ),
81
- ),
82
- ),
83
- ),
84
- Padding(
85
- padding: const EdgeInsets.fromLTRB(
86
- gutter,
87
- KasySpacing.smd,
88
- gutter,
89
- KasySpacing.lg,
90
- ),
91
- child: MoveFadeAnim(
92
- delayInMs: 200,
93
- child: Text(
94
- widget.description,
95
- textAlign: TextAlign.start,
96
- style: context.textTheme.bodyLarge?.copyWith(
97
- color: context.colors.muted,
98
- height: 1.45,
99
- ),
100
- ),
101
- ),
102
- ),
103
- Padding(
104
- padding: const EdgeInsets.symmetric(horizontal: gutter),
105
- child: OnboardingSelectableRowGroup(
106
- physics: const NeverScrollableScrollPhysics(),
107
- options: widget.optionIds.map(
108
- (e) {
109
- final index = widget.optionIds.indexOf(e);
110
- return Animate(
111
- effects: [
112
- FadeEffect(
113
- delay: Duration(milliseconds: 280 + index * 80),
114
- duration: const Duration(milliseconds: 450),
115
- curve: Curves.easeOut,
116
- ),
117
- MoveEffect(
118
- delay: Duration(milliseconds: 280 + index * 80),
119
- duration: const Duration(milliseconds: 450),
120
- curve: Curves.easeOut,
121
- begin: const Offset(0, 24),
122
- end: Offset.zero,
123
- ),
124
- ],
125
- child: widget.optionBuilder(e, e == selectedChoiceId),
126
- );
127
- },
128
- ).toList(),
129
- onSelect: (index, selected) {
130
- widget.onOptionIdSelected?.call(widget.optionIds[index]);
131
- setState(() {
132
- selectedChoiceId = widget.optionIds[index];
133
- });
134
- },
135
- onSelectInfoWidget: widget.optionIds
136
- .map((el) => widget.reassuranceBuilder?.call(el))
137
- .toList(),
138
- ),
139
- ),
140
- const SizedBox(height: KasySpacing.xl),
141
- ],
142
- );
143
-
144
- return OnboardingBackground(
145
- child: Column(
146
- crossAxisAlignment: CrossAxisAlignment.stretch,
147
- children: [
148
- Expanded(
149
- child: SafeArea(
150
- bottom: false,
151
- child: SingleChildScrollView(
152
- padding: const EdgeInsets.only(bottom: KasySpacing.md),
153
- child: ResponsiveBuilder(
154
- small: scrollBody,
155
- medium: Center(
156
- child: ConstrainedBox(
157
- constraints: const BoxConstraints(maxWidth: 600),
158
- child: scrollBody,
159
- ),
160
- ),
161
- ),
162
- ),
163
- ),
164
- ),
165
- DeviceSizeBuilder(
166
- builder: (device) => OnboardingStickyFooter(
167
- maxContentWidth: device == DeviceType.small ? null : 600,
168
- children: [
169
- KasyButton(
170
- label: widget.btnText,
171
- expand: true,
172
- onPressed: selectedChoiceId == null
173
- ? null
174
- : () => widget.onValidate?.call(selectedChoiceId),
175
- ),
176
- ],
177
- ),
178
- ),
179
- ],
180
- ),
181
- );
182
- }
183
- }
@@ -1,34 +0,0 @@
1
- import 'package:flutter/material.dart';
2
- import 'package:flutter_animate/flutter_animate.dart';
3
-
4
- class MoveFadeAnim extends StatelessWidget {
5
- final int? delayInMs;
6
- final Widget child;
7
-
8
- const MoveFadeAnim({
9
- super.key,
10
- required this.child,
11
- this.delayInMs,
12
- });
13
-
14
- @override
15
- Widget build(BuildContext context) {
16
- return Animate(
17
- effects: [
18
- FadeEffect(
19
- delay: Duration(milliseconds: delayInMs ?? 0),
20
- duration: const Duration(milliseconds: 200),
21
- curve: Curves.easeIn,
22
- ),
23
- MoveEffect(
24
- delay: Duration(milliseconds: delayInMs ?? 0),
25
- duration: const Duration(milliseconds: 450),
26
- curve: Curves.easeOut,
27
- begin: const Offset(0, 50),
28
- end: Offset.zero,
29
- ),
30
- ],
31
- child: child,
32
- );
33
- }
34
- }
@@ -1,67 +0,0 @@
1
- import 'package:flutter/material.dart';
2
- import 'package:flutter_riverpod/flutter_riverpod.dart';
3
- import 'package:kasy_kit/components/components.dart';
4
- import 'package:kasy_kit/core/data/api/analytics_api.dart';
5
- import 'package:kasy_kit/core/data/api/tracking_api.dart';
6
- import 'package:kasy_kit/core/states/user_state_notifier.dart';
7
- import 'package:kasy_kit/features/onboarding/ui/widgets/onboarding_background.dart';
8
- import 'package:kasy_kit/features/onboarding/ui/widgets/onboarding_illustration_scaffold.dart';
9
- import 'package:kasy_kit/features/onboarding/ui/widgets/onboarding_module_mockups.dart';
10
- import 'package:kasy_kit/features/subscriptions/repositories/subscription_repository.dart';
11
- import 'package:kasy_kit/i18n/translations.g.dart';
12
- import 'package:permission_handler/permission_handler.dart';
13
-
14
- /// ATT Permission Step
15
- /// ATT is only available on iOS
16
- /// This is the consent screen for iOS 14+ that asks the user to allow the app to access the ATT framework
17
- /// In a few words you need this to get the IDFA (Identifier for Advertisers) which is used for tracking purposes
18
- /// So you can create better facebook ads, google ads, etc...
19
- class AttPermissionStep extends ConsumerWidget {
20
- final String nextRoute;
21
-
22
- const AttPermissionStep({
23
- super.key,
24
- required this.nextRoute,
25
- });
26
-
27
- @override
28
- Widget build(BuildContext context, WidgetRef ref) {
29
- final translations = Translations.of(context).onboarding.att;
30
-
31
- return OnboardingBackground(
32
- child: OnboardingIllustrationScaffold(
33
- step: 6,
34
- title: translations.title,
35
- description: translations.description,
36
- image: const TrackingPermissionMockup(),
37
- footerActions: [
38
- KasyButton(
39
- label: translations.continue_button,
40
- expand: true,
41
- onPressed: () async {
42
- final Map<Permission, PermissionStatus> permission = await [
43
- Permission.appTrackingTransparency,
44
- ].request();
45
- final isGranted =
46
- permission.values.first == PermissionStatus.granted;
47
- ref.read(analyticsApiProvider).logEvent('att_request', {
48
- 'granted': isGranted,
49
- });
50
- final userId =
51
- ref.read(userStateNotifierProvider).user.idOrNull;
52
- if (userId != null) {
53
- ref
54
- .read(subscriptionRepositoryProvider)
55
- .initUser(userId)
56
- .ignore();
57
- ref.read(facebookEventApiProvider).initUser(userId).ignore();
58
- }
59
- if (!context.mounted) return;
60
- Navigator.of(context).pushNamed(nextRoute);
61
- },
62
- ),
63
- ],
64
- ),
65
- );
66
- }
67
- }