kasy-cli 1.12.1 → 1.13.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 (138) hide show
  1. package/bin/kasy.js +21 -0
  2. package/lib/commands/splash.js +220 -0
  3. package/lib/scaffold/CHANGELOG.json +9 -0
  4. package/lib/scaffold/backends/api/patch/lib/main.dart +29 -10
  5. package/lib/scaffold/backends/supabase/patch/lib/main.dart +29 -10
  6. package/lib/scaffold/features/README.md +15 -139
  7. package/lib/scaffold/shared/generator-utils.js +16 -15
  8. package/lib/utils/i18n.js +78 -0
  9. package/package.json +2 -2
  10. package/templates/firebase/android/app/src/main/res/drawable-hdpi/android12splash.png +0 -0
  11. package/templates/firebase/android/app/src/main/res/drawable-hdpi/splash.png +0 -0
  12. package/templates/firebase/android/app/src/main/res/drawable-mdpi/android12splash.png +0 -0
  13. package/templates/firebase/android/app/src/main/res/drawable-mdpi/splash.png +0 -0
  14. package/templates/firebase/android/app/src/main/res/drawable-night/background.png +0 -0
  15. package/templates/firebase/android/app/src/main/res/drawable-night/launch_background.xml +9 -0
  16. package/templates/firebase/android/app/src/main/res/drawable-night-hdpi/android12splash.png +0 -0
  17. package/templates/firebase/android/app/src/main/res/drawable-night-hdpi/splash.png +0 -0
  18. package/templates/firebase/android/app/src/main/res/drawable-night-mdpi/android12splash.png +0 -0
  19. package/templates/firebase/android/app/src/main/res/drawable-night-mdpi/splash.png +0 -0
  20. package/templates/firebase/android/app/src/main/res/drawable-night-v21/background.png +0 -0
  21. package/templates/firebase/android/app/src/main/res/drawable-night-v21/launch_background.xml +9 -0
  22. package/templates/firebase/android/app/src/main/res/drawable-night-xhdpi/android12splash.png +0 -0
  23. package/templates/firebase/android/app/src/main/res/drawable-night-xhdpi/splash.png +0 -0
  24. package/templates/firebase/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png +0 -0
  25. package/templates/firebase/android/app/src/main/res/drawable-night-xxhdpi/splash.png +0 -0
  26. package/templates/firebase/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png +0 -0
  27. package/templates/firebase/android/app/src/main/res/drawable-night-xxxhdpi/splash.png +0 -0
  28. package/templates/firebase/android/app/src/main/res/drawable-xhdpi/android12splash.png +0 -0
  29. package/templates/firebase/android/app/src/main/res/drawable-xhdpi/splash.png +0 -0
  30. package/templates/firebase/android/app/src/main/res/drawable-xxhdpi/android12splash.png +0 -0
  31. package/templates/firebase/android/app/src/main/res/drawable-xxhdpi/splash.png +0 -0
  32. package/templates/firebase/android/app/src/main/res/drawable-xxxhdpi/android12splash.png +0 -0
  33. package/templates/firebase/android/app/src/main/res/drawable-xxxhdpi/splash.png +0 -0
  34. package/templates/firebase/android/app/src/main/res/values-night-v31/styles.xml +2 -1
  35. package/templates/firebase/android/app/src/main/res/values-v31/styles.xml +1 -0
  36. package/templates/firebase/assets/images/splash_logo_dark.png +0 -0
  37. package/templates/firebase/assets/images/splash_logo_light.png +0 -0
  38. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json +9 -8
  39. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png +0 -0
  40. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json +33 -0
  41. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png +0 -0
  42. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png +0 -0
  43. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png +0 -0
  44. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark.png +0 -0
  45. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png +0 -0
  46. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@3x.png +0 -0
  47. package/templates/firebase/ios/Runner/Base.lproj/LaunchScreen.storyboard +1 -1
  48. package/templates/firebase/lib/core/theme/providers/theme_provider.dart +48 -24
  49. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_features.dart +4 -0
  50. package/templates/firebase/lib/features/onboarding/ui/onboarding_page.dart +1 -0
  51. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_feature.dart +13 -0
  52. package/templates/firebase/lib/features/settings/settings_page.dart +158 -18
  53. package/templates/firebase/lib/i18n/en.i18n.json +6 -2
  54. package/templates/firebase/lib/i18n/es.i18n.json +6 -2
  55. package/templates/firebase/lib/i18n/pt.i18n.json +6 -2
  56. package/templates/firebase/lib/main.dart +29 -10
  57. package/templates/firebase/pubspec.yaml +4 -5
  58. package/templates/firebase/test/core/data/repositories/user_repository_test.dart +1 -1
  59. package/templates/firebase/web/index.html +47 -39
  60. package/templates/firebase/web/splash/img/dark-1x.png +0 -0
  61. package/templates/firebase/web/splash/img/dark-2x.png +0 -0
  62. package/templates/firebase/web/splash/img/dark-3x.png +0 -0
  63. package/templates/firebase/web/splash/img/dark-4x.png +0 -0
  64. package/templates/firebase/web/splash/img/light-1x.png +0 -0
  65. package/templates/firebase/web/splash/img/light-2x.png +0 -0
  66. package/templates/firebase/web/splash/img/light-3x.png +0 -0
  67. package/templates/firebase/web/splash/img/light-4x.png +0 -0
  68. package/lib/scaffold/features/analytics/lib/core/data/api/analytics_api.dart +0 -124
  69. package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/BUG.es.md +0 -35
  70. package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/BUG.md +0 -35
  71. package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/BUG.pt.md +0 -35
  72. package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/feature_request.es.md +0 -12
  73. package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/feature_request.md +0 -12
  74. package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/feature_request.pt.md +0 -12
  75. package/lib/scaffold/features/ci/.github/PULL_REQUEST_TEMPLATE.es.md +0 -17
  76. package/lib/scaffold/features/ci/.github/PULL_REQUEST_TEMPLATE.md +0 -17
  77. package/lib/scaffold/features/ci/.github/PULL_REQUEST_TEMPLATE.pt.md +0 -17
  78. package/lib/scaffold/features/ci/.github/dependabot.yml +0 -16
  79. package/lib/scaffold/features/ci/.github/workflows/app.yml +0 -20
  80. package/lib/scaffold/features/ci/.gitlab/templates/deploy.yaml +0 -14
  81. package/lib/scaffold/features/ci/.gitlab/templates/dropbox.yaml +0 -19
  82. package/lib/scaffold/features/ci/.gitlab/templates/flutter.yaml +0 -163
  83. package/lib/scaffold/features/ci/.gitlab/templates/mailgun.yaml +0 -28
  84. package/lib/scaffold/features/ci/.gitlab-ci.yml +0 -37
  85. package/lib/scaffold/features/ci/codemagic.yaml +0 -157
  86. package/lib/scaffold/features/facebook/lib/core/data/api/tracking_api.dart +0 -111
  87. package/lib/scaffold/features/feedback/lib/features/feedbacks/api/entities/feature_request_entity.dart +0 -27
  88. package/lib/scaffold/features/feedback/lib/features/feedbacks/api/entities/feature_vote_entity.dart +0 -27
  89. package/lib/scaffold/features/feedback/lib/features/feedbacks/api/feature_request_api.dart +0 -50
  90. package/lib/scaffold/features/feedback/lib/features/feedbacks/api/feature_vote_api.dart +0 -79
  91. package/lib/scaffold/features/feedback/lib/features/feedbacks/models/feature_requests.dart +0 -48
  92. package/lib/scaffold/features/feedback/lib/features/feedbacks/models/feedback_state.dart +0 -42
  93. package/lib/scaffold/features/feedback/lib/features/feedbacks/providers/feedback_page_notifier.dart +0 -147
  94. package/lib/scaffold/features/feedback/lib/features/feedbacks/repositories/feature_request_repository.dart +0 -95
  95. package/lib/scaffold/features/feedback/lib/features/feedbacks/ui/component/add_feature_form.dart +0 -199
  96. package/lib/scaffold/features/feedback/lib/features/feedbacks/ui/feedback_page.dart +0 -175
  97. package/lib/scaffold/features/feedback/lib/features/feedbacks/ui/widgets/add_feature_button.dart +0 -76
  98. package/lib/scaffold/features/feedback/lib/features/feedbacks/ui/widgets/feature_card.dart +0 -279
  99. package/lib/scaffold/features/ios-release/.kasy/apple.env.example +0 -8
  100. package/lib/scaffold/features/ios-release/.kasy/codemagic.env.example +0 -7
  101. package/lib/scaffold/features/ios-release/docs/codemagic-release.en.md +0 -50
  102. package/lib/scaffold/features/ios-release/docs/codemagic-release.es.md +0 -50
  103. package/lib/scaffold/features/ios-release/docs/codemagic-release.pt.md +0 -50
  104. package/lib/scaffold/features/ios-release/docs/ios-release.en.md +0 -41
  105. package/lib/scaffold/features/ios-release/docs/ios-release.es.md +0 -41
  106. package/lib/scaffold/features/ios-release/docs/ios-release.pt.md +0 -41
  107. package/lib/scaffold/features/ios-release/scripts/bump-ios-version.js +0 -38
  108. package/lib/scaffold/features/ios-release/scripts/release-ios.sh +0 -137
  109. package/lib/scaffold/features/llm_chat/lib/features/llm_chat/llm_chat_page.dart +0 -301
  110. package/lib/scaffold/features/local_notifications/lib/features/local_reminder/providers/reminder_notifier.dart +0 -81
  111. package/lib/scaffold/features/local_notifications/lib/features/local_reminder/repositories/reminder_preferences.dart +0 -76
  112. package/lib/scaffold/features/local_notifications/lib/features/local_reminder/ui/reminder_page.dart +0 -282
  113. package/lib/scaffold/features/onboarding/lib/features/onboarding/api/entities/user_info_entity.dart +0 -24
  114. package/lib/scaffold/features/onboarding/lib/features/onboarding/api/user_infos_api.dart +0 -71
  115. package/lib/scaffold/features/onboarding/lib/features/onboarding/models/user_info.dart +0 -92
  116. package/lib/scaffold/features/onboarding/lib/features/onboarding/providers/onboarding_model.dart +0 -15
  117. package/lib/scaffold/features/onboarding/lib/features/onboarding/providers/onboarding_provider.dart +0 -78
  118. package/lib/scaffold/features/onboarding/lib/features/onboarding/repositories/user_infos_repository.dart +0 -29
  119. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/animations/page_transitions.dart +0 -30
  120. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/components/onboarding_att_setup.dart +0 -66
  121. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/components/onboarding_features.dart +0 -72
  122. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/components/onboarding_loader.dart +0 -92
  123. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +0 -73
  124. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/components/onboarding_questions.dart +0 -89
  125. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/onboarding_page.dart +0 -94
  126. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_background.dart +0 -80
  127. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_feature.dart +0 -139
  128. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_illustration_scaffold.dart +0 -110
  129. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_progress.dart +0 -84
  130. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +0 -173
  131. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_reassurance.dart +0 -45
  132. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_sticky_footer.dart +0 -77
  133. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/selectable_row_tile.dart +0 -392
  134. package/lib/scaffold/features/revenuecat/lib/core/data/api/tracking_api.dart +0 -116
  135. package/lib/scaffold/features/revenuecat/lib/core/data/models/subscription.dart +0 -322
  136. package/lib/scaffold/features/revenuecat/lib/core/home_widgets/home_widget_background_task.dart +0 -41
  137. package/lib/scaffold/features/revenuecat/lib/core/states/user_state_notifier.dart +0 -305
  138. package/templates/firebase/assets/images/splashscreen.png +0 -0
@@ -1,282 +0,0 @@
1
- import 'package:flutter/material.dart';
2
- import 'package:flutter_riverpod/flutter_riverpod.dart';
3
- import 'package:kasy_kit/core/theme/theme.dart';
4
- import 'package:kasy_kit/i18n/translations.g.dart';
5
- import 'package:kasy_kit/features/local_reminder/providers/reminder_notifier.dart';
6
- import 'package:kasy_kit/features/local_reminder/repositories/reminder_preferences.dart';
7
-
8
- class ReminderPage extends ConsumerWidget {
9
- const ReminderPage({super.key});
10
-
11
- @override
12
- Widget build(BuildContext context, WidgetRef ref) {
13
- final asyncState = ref.watch(reminderProvider);
14
- final tr = Translations.of(context).reminderPage;
15
-
16
- return Scaffold(
17
- appBar: AppBar(
18
- title: Text(tr.title),
19
- centerTitle: true,
20
- ),
21
- body: asyncState.when(
22
- loading: () => const Center(child: CircularProgressIndicator()),
23
- error: (e, _) => Center(child: Text(e.toString())),
24
- data: (state) => _ReminderForm(state: state),
25
- ),
26
- );
27
- }
28
- }
29
-
30
- class _ReminderForm extends ConsumerWidget {
31
- final ReminderState state;
32
-
33
- const _ReminderForm({required this.state});
34
-
35
- @override
36
- Widget build(BuildContext context, WidgetRef ref) {
37
- final tr = Translations.of(context).reminderPage;
38
- final notifier = ref.read(reminderProvider.notifier);
39
-
40
- return ListView(
41
- padding: const EdgeInsets.all(KasySpacing.md),
42
- children: [
43
- // Enable toggle
44
- SwitchListTile(
45
- title: Text(
46
- tr.toggleLabel,
47
- style: context.textTheme.bodyLarge,
48
- ),
49
- value: state.enabled,
50
- onChanged: notifier.setEnabled,
51
- ),
52
- if (state.enabled) ...[
53
- const Divider(),
54
- const SizedBox(height: KasySpacing.sm),
55
-
56
- // Type selector
57
- Text(
58
- tr.typeLabel,
59
- style: context.textTheme.labelLarge?.copyWith(
60
- color: context.colors.muted,
61
- ),
62
- ),
63
- const SizedBox(height: KasySpacing.sm),
64
- _TypeSelector(current: state.type, onChanged: notifier.setType),
65
- const SizedBox(height: KasySpacing.lg),
66
-
67
- // Time picker
68
- Text(
69
- tr.timeLabel,
70
- style: context.textTheme.labelLarge?.copyWith(
71
- color: context.colors.muted,
72
- ),
73
- ),
74
- const SizedBox(height: KasySpacing.sm),
75
- _TimeTile(
76
- hour: state.hour,
77
- minute: state.minute,
78
- onChanged: (h, m) => notifier.setTime(h, m),
79
- ),
80
-
81
- // Day of week (only for weekly)
82
- if (state.type == ReminderType.weekly) ...[
83
- const SizedBox(height: KasySpacing.lg),
84
- Text(
85
- tr.dayLabel,
86
- style: context.textTheme.labelLarge?.copyWith(
87
- color: context.colors.muted,
88
- ),
89
- ),
90
- const SizedBox(height: KasySpacing.sm),
91
- _DaySelector(current: state.dayOfWeek, onChanged: notifier.setDayOfWeek),
92
- ],
93
-
94
- // Date picker (only for specific date)
95
- if (state.type == ReminderType.specificDate) ...[
96
- const SizedBox(height: KasySpacing.lg),
97
- Text(
98
- tr.dateLabel,
99
- style: context.textTheme.labelLarge?.copyWith(
100
- color: context.colors.muted,
101
- ),
102
- ),
103
- const SizedBox(height: KasySpacing.sm),
104
- _DateTile(
105
- date: state.date,
106
- onChanged: notifier.setDate,
107
- ),
108
- ],
109
- ],
110
- ],
111
- );
112
- }
113
- }
114
-
115
- class _TypeSelector extends StatelessWidget {
116
- final ReminderType current;
117
- final ValueChanged<ReminderType> onChanged;
118
-
119
- const _TypeSelector({required this.current, required this.onChanged});
120
-
121
- @override
122
- Widget build(BuildContext context) {
123
- final tr = Translations.of(context).reminderPage;
124
- return SegmentedButton<ReminderType>(
125
- segments: [
126
- ButtonSegment(value: ReminderType.daily, label: Text(tr.daily)),
127
- ButtonSegment(value: ReminderType.weekly, label: Text(tr.weekly)),
128
- ButtonSegment(value: ReminderType.specificDate, label: Text(tr.specificDate)),
129
- ],
130
- selected: {current},
131
- onSelectionChanged: (s) => onChanged(s.first),
132
- style: ButtonStyle(
133
- textStyle: WidgetStateProperty.all(context.textTheme.labelSmall),
134
- ),
135
- );
136
- }
137
- }
138
-
139
- class _TimeTile extends StatelessWidget {
140
- final int hour;
141
- final int minute;
142
- final void Function(int hour, int minute) onChanged;
143
-
144
- const _TimeTile({required this.hour, required this.minute, required this.onChanged});
145
-
146
- String _pad(int v) => v.toString().padLeft(2, '0');
147
-
148
- @override
149
- Widget build(BuildContext context) {
150
- return InkWell(
151
- onTap: () async {
152
- final picked = await showTimePicker(
153
- context: context,
154
- initialTime: TimeOfDay(hour: hour, minute: minute),
155
- );
156
- if (picked != null) {
157
- onChanged(picked.hour, picked.minute);
158
- }
159
- },
160
- borderRadius: BorderRadius.circular(KasySpacing.sm),
161
- child: Container(
162
- padding: const EdgeInsets.symmetric(
163
- horizontal: KasySpacing.md,
164
- vertical: KasySpacing.smd,
165
- ),
166
- decoration: BoxDecoration(
167
- color: context.colors.surface,
168
- borderRadius: BorderRadius.circular(KasySpacing.sm),
169
- ),
170
- child: Row(
171
- children: [
172
- Icon(Icons.access_time, color: context.colors.primary),
173
- const SizedBox(width: KasySpacing.sm),
174
- Text(
175
- '${_pad(hour)}:${_pad(minute)}',
176
- style: context.textTheme.headlineMedium?.copyWith(
177
- color: context.colors.onSurface,
178
- fontWeight: FontWeight.bold,
179
- ),
180
- ),
181
- const Spacer(),
182
- Icon(Icons.arrow_forward_ios, size: 14, color: context.colors.muted),
183
- ],
184
- ),
185
- ),
186
- );
187
- }
188
- }
189
-
190
- class _DaySelector extends StatelessWidget {
191
- final int current;
192
- final ValueChanged<int> onChanged;
193
-
194
- const _DaySelector({required this.current, required this.onChanged});
195
-
196
- @override
197
- Widget build(BuildContext context) {
198
- // 1=Monday ... 7=Sunday (matches DateTime.weekday)
199
- final days = [
200
- 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb', 'Dom',
201
- ];
202
- return Wrap(
203
- spacing: KasySpacing.xs,
204
- children: List.generate(7, (i) {
205
- final dayIndex = i + 1;
206
- final selected = current == dayIndex;
207
- return ChoiceChip(
208
- label: Text(days[i]),
209
- selected: selected,
210
- onSelected: (_) => onChanged(dayIndex),
211
- );
212
- }),
213
- );
214
- }
215
- }
216
-
217
- class _DateTile extends StatelessWidget {
218
- final DateTime? date;
219
- final ValueChanged<DateTime> onChanged;
220
-
221
- const _DateTile({required this.date, required this.onChanged});
222
-
223
- @override
224
- Widget build(BuildContext context) {
225
- final label = date != null
226
- ? '${date!.day.toString().padLeft(2, '0')}/${date!.month.toString().padLeft(2, '0')}/${date!.year} ${date!.hour.toString().padLeft(2, '0')}:${date!.minute.toString().padLeft(2, '0')}'
227
- : Translations.of(context).reminderPage.selectDate;
228
-
229
- return InkWell(
230
- onTap: () async {
231
- final now = DateTime.now();
232
- final pickedDate = await showDatePicker(
233
- context: context,
234
- initialDate: date ?? now,
235
- firstDate: now,
236
- lastDate: now.add(const Duration(days: 365)),
237
- );
238
- if (pickedDate == null || !context.mounted) return;
239
- final pickedTime = await showTimePicker(
240
- context: context,
241
- initialTime: TimeOfDay(
242
- hour: date?.hour ?? 9,
243
- minute: date?.minute ?? 0,
244
- ),
245
- );
246
- if (pickedTime == null) return;
247
- onChanged(DateTime(
248
- pickedDate.year,
249
- pickedDate.month,
250
- pickedDate.day,
251
- pickedTime.hour,
252
- pickedTime.minute,
253
- ));
254
- },
255
- borderRadius: BorderRadius.circular(KasySpacing.sm),
256
- child: Container(
257
- padding: const EdgeInsets.symmetric(
258
- horizontal: KasySpacing.md,
259
- vertical: KasySpacing.smd,
260
- ),
261
- decoration: BoxDecoration(
262
- color: context.colors.surface,
263
- borderRadius: BorderRadius.circular(KasySpacing.sm),
264
- ),
265
- child: Row(
266
- children: [
267
- Icon(Icons.calendar_today, color: context.colors.primary),
268
- const SizedBox(width: KasySpacing.sm),
269
- Text(
270
- label,
271
- style: context.textTheme.bodyLarge?.copyWith(
272
- color: context.colors.onSurface,
273
- ),
274
- ),
275
- const Spacer(),
276
- Icon(Icons.arrow_forward_ios, size: 14, color: context.colors.muted),
277
- ],
278
- ),
279
- ),
280
- );
281
- }
282
- }
@@ -1,24 +0,0 @@
1
- import 'package:freezed_annotation/freezed_annotation.dart';
2
- // ignore: depend_on_referenced_packages
3
- // ignore_for_file: invalid_annotation_target
4
-
5
- import 'package:kasy_kit/core/data/entities/json_converters.dart';
6
-
7
- part 'user_info_entity.freezed.dart';
8
- part 'user_info_entity.g.dart';
9
-
10
-
11
- @freezed
12
- sealed class UserInfoEntity with _$UserInfoEntity {
13
- const factory UserInfoEntity({
14
- @JsonKey(includeIfNull: false, toJson: Converters.id) String? id,
15
- required String key,
16
- required String value,
17
- }) = UserInfoEntityData;
18
-
19
- factory UserInfoEntity.fromJson(String id, Map<String, dynamic> json) =>
20
- _$UserInfoEntityFromJson(json..['id'] = id);
21
- }
22
-
23
-
24
-
@@ -1,71 +0,0 @@
1
- import 'package:cloud_firestore/cloud_firestore.dart';
2
- import 'package:flutter_riverpod/flutter_riverpod.dart';
3
- import 'package:logger/logger.dart';
4
- import 'package:kasy_kit/features/onboarding/api/entities/user_info_entity.dart';
5
-
6
- final userInfosApiProvider = Provider<UserInfosApi>(
7
- (ref) => UserInfosApi(
8
- client: FirebaseFirestore.instance,
9
- ),
10
- );
11
-
12
- class UserInfosApi {
13
- final FirebaseFirestore _client;
14
- // ignore: unused_field
15
- final Logger _logger;
16
-
17
- UserInfosApi({
18
- required FirebaseFirestore client,
19
- }) : _client = client,
20
- _logger = Logger();
21
-
22
- CollectionReference<UserInfoEntity?> _collection(String userId) =>
23
- _client.collection('users').doc(userId).collection("infos").withConverter(
24
- fromFirestore: (snapshot, _) {
25
- if (snapshot.exists) {
26
- return UserInfoEntity.fromJson(snapshot.id, snapshot.data()!);
27
- }
28
- return null;
29
- },
30
- toFirestore: (data, _) => data!.toJson(),
31
- );
32
-
33
- Future<List<UserInfoEntity>> getAll(String userId) {
34
- return _collection(userId).get().then((value) {
35
- if (value.docs.isEmpty) {
36
- return [];
37
- }
38
- return value.docs.map((el) => el.data()!).toList();
39
- });
40
- }
41
-
42
- Future<UserInfoEntity?> get(String userId, String id) {
43
- return _collection(userId).doc(id).get().then((value) => value.data());
44
- }
45
-
46
- Future<UserInfoEntity?> getByKey(String userId, String key) {
47
- return _collection(userId)
48
- .where("key", isEqualTo: key)
49
- .get()
50
- .then((value) => value.docs.firstOrNull?.data());
51
- }
52
-
53
- Future<void> update(String userId, UserInfoEntity info) async {
54
- await _collection(userId).doc(info.id).set(info);
55
- }
56
-
57
- Future<void> delete(String userId, String id) async {
58
- await _collection(userId).doc(id).delete();
59
- }
60
-
61
- Future<void> deleteAll(String userId) async {
62
- final infos = await getAll(userId);
63
- for (final info in infos) {
64
- await delete(userId, info.id!);
65
- }
66
- }
67
-
68
- Future<void> create(String userId, UserInfoEntity info) async {
69
- await _collection(userId).add(info);
70
- }
71
- }
@@ -1,92 +0,0 @@
1
- import 'package:kasy_kit/features/onboarding/api/entities/user_info_entity.dart';
2
-
3
- enum UserInfoKeys {
4
- genre,
5
- age,
6
- }
7
-
8
- enum Genre {
9
- male,
10
- female,
11
- none,
12
- }
13
-
14
- enum AgeRange {
15
- age18_30,
16
- age31_40,
17
- age41_50,
18
- age51_60,
19
- none,
20
- }
21
-
22
- abstract class UserInfoDetail<T> {
23
- final T value;
24
-
25
- UserInfoDetail(this.value);
26
-
27
- UserInfoEntity toEntity();
28
-
29
-
30
- }
31
-
32
- /// ======================================
33
- /// user age info
34
- /// ======================================
35
- class UserAgeInfo extends UserInfoDetail<AgeRange> {
36
- UserAgeInfo(super.value);
37
-
38
- factory UserAgeInfo.fromEntity(UserInfoEntity entity) {
39
- final value = AgeRange.values.firstWhere(
40
- (element) => element.name == entity.value,
41
- orElse: () => AgeRange.none,
42
- );
43
- return UserAgeInfo(value);
44
- }
45
-
46
- factory UserAgeInfo.fromString(String value) {
47
- final sexe = AgeRange.values.firstWhere(
48
- (element) => element.name == value,
49
- orElse: () => AgeRange.none,
50
- );
51
- return UserAgeInfo(sexe);
52
- }
53
-
54
- @override
55
- UserInfoEntity toEntity() => UserInfoEntity(
56
- key: UserInfoKeys.age.name,
57
- value: value.name,
58
- );
59
-
60
-
61
- }
62
-
63
- /// ======================================
64
- /// user genre info
65
- /// ======================================
66
- class UserSexeInfo extends UserInfoDetail<Genre> {
67
- UserSexeInfo(super.value);
68
-
69
- factory UserSexeInfo.fromEntity(UserInfoEntity entity) {
70
- final value = Genre.values.firstWhere(
71
- (element) => element.name == entity.value,
72
- orElse: () => Genre.none,
73
- );
74
- return UserSexeInfo(value);
75
- }
76
-
77
- factory UserSexeInfo.fromString(String value) {
78
- final genre = Genre.values.firstWhere(
79
- (element) => element.name == value,
80
- orElse: () => Genre.none,
81
- );
82
- return UserSexeInfo(genre);
83
- }
84
-
85
- @override
86
- UserInfoEntity toEntity() => UserInfoEntity(
87
- key: UserInfoKeys.genre.name,
88
- value: value.name,
89
- );
90
-
91
-
92
- }
@@ -1,15 +0,0 @@
1
- class OnboardingState {
2
- DateTime? reminder;
3
-
4
- OnboardingState({
5
- this.reminder,
6
- });
7
-
8
- OnboardingState copyWith({
9
- DateTime? reminder,
10
- }) {
11
- return OnboardingState(
12
- reminder: reminder ?? this.reminder,
13
- );
14
- }
15
- }
@@ -1,78 +0,0 @@
1
- import 'dart:async';
2
-
3
- import 'package:flutter_riverpod/flutter_riverpod.dart';
4
- import 'package:kasy_kit/core/data/api/analytics_api.dart';
5
- import 'package:kasy_kit/core/states/user_state_notifier.dart';
6
- import 'package:kasy_kit/features/notifications/providers/models/notification.dart';
7
- import 'package:kasy_kit/features/notifications/repositories/notifications_repository.dart';
8
- import 'package:kasy_kit/features/onboarding/models/user_info.dart';
9
- import 'package:kasy_kit/features/onboarding/providers/onboarding_model.dart';
10
- import 'package:kasy_kit/features/onboarding/repositories/user_infos_repository.dart';
11
- import 'package:riverpod_annotation/riverpod_annotation.dart';
12
-
13
- part 'onboarding_provider.g.dart';
14
-
15
- extension OnboardingNotifierExt on WidgetRef {
16
- OnboardingNotifier get onboardingNotifier =>
17
- read(onboardingProvider.notifier);
18
-
19
- OnboardingState? get onboardingState$ => watch(onboardingProvider);
20
-
21
- OnboardingNotifier get onboardingState =>
22
- read(onboardingProvider.notifier);
23
- }
24
-
25
- @riverpod
26
- class OnboardingNotifier extends _$OnboardingNotifier {
27
- @override
28
- OnboardingState build() {
29
- return OnboardingState();
30
- }
31
-
32
- Future<void> onAnsweredQuestion(UserInfoDetail value) async {
33
- final userId = ref.read(userStateNotifierProvider).user.idOrNull;
34
- if (userId == null) return; // Guest: no account yet, skip Firestore write
35
- await ref.read(userInfosRepositoryProvider).save(userId, value);
36
- }
37
-
38
- Future<void> setupNotifications() async {
39
- final userStateNotifier = ref.read(userStateNotifierProvider.notifier);
40
- final notificationsRepository = ref.read(notificationRepositoryProvider);
41
-
42
- var permission = await notificationsRepository.getPermissionStatus();
43
- await permission.maybeAsk();
44
-
45
- // The system permission dialog causes the app to go to background.
46
- // The provider may have been disposed while waiting — bail out if so.
47
- if (!ref.mounted) return;
48
-
49
- permission = await notificationsRepository.getPermissionStatus();
50
-
51
- // save analytics event
52
- if (permission is NotificationPermissionGranted) {
53
- unawaited(
54
- ref
55
- .read(analyticsApiProvider)
56
- .logEvent('setup_notifications_accepted', {}),
57
- );
58
- }
59
-
60
- // Uncomment and create translations to schedule daily notification after user accept permissions
61
- // --
62
- // final translations = ref.read(translationsProvider);
63
- // await ref.read(localNotifierProvider).scheduleDailyAt(
64
- // title: translations.dailyNotification.title,
65
- // body: translations.dailyNotification.body,
66
- // date: state.reminder!,
67
- // );
68
-
69
- await userStateNotifier.refresh();
70
- }
71
-
72
- Future<void> onOnboardingCompleted() async {
73
- final userStateNotifier = ref.read(userStateNotifierProvider.notifier);
74
-
75
- await userStateNotifier.onOnboarded();
76
- await userStateNotifier.refresh();
77
- }
78
- }
@@ -1,29 +0,0 @@
1
- import 'package:flutter_riverpod/flutter_riverpod.dart';
2
- import 'package:kasy_kit/features/onboarding/api/user_infos_api.dart';
3
- import 'package:kasy_kit/features/onboarding/models/user_info.dart';
4
-
5
- final userInfosRepositoryProvider = Provider<UserInfosRepository>(
6
- (ref) => UserInfosRepository(
7
- userInfosApi: ref.read(userInfosApiProvider),
8
- ),
9
- );
10
-
11
- class UserInfosRepository {
12
- final UserInfosApi _userInfosApi;
13
-
14
- UserInfosRepository({
15
- required UserInfosApi userInfosApi,
16
- }) : _userInfosApi = userInfosApi;
17
-
18
- Future<void> save(String userId, UserInfoDetail info) async {
19
- final entity = info.toEntity();
20
-
21
- final alreadyExistingInfo =
22
- await _userInfosApi.getByKey(userId, entity.key);
23
- if (alreadyExistingInfo != null) {
24
- return _userInfosApi.update(userId, entity);
25
- }
26
-
27
- return _userInfosApi.create(userId, entity);
28
- }
29
- }
@@ -1,30 +0,0 @@
1
- import 'package:animations/animations.dart';
2
- import 'package:flutter/cupertino.dart';
3
- import 'package:kasy_kit/core/theme/theme.dart';
4
-
5
- class OnboardingRouteTransition extends CupertinoPageRoute {
6
- OnboardingRouteTransition({
7
- required super.builder,
8
- super.settings,
9
- });
10
-
11
- // OPTIONAL IF YOU WISH TO HAVE SOME EXTRA ANIMATION WHILE ROUTING
12
- @override
13
- Widget buildPage(
14
- BuildContext context,
15
- Animation<double> animation,
16
- Animation<double> secondaryAnimation,
17
- ) {
18
- // return FadeTransition(
19
- // opacity: animation,
20
- // child: builder(context),
21
- // );
22
- return SharedAxisTransition(
23
- animation: animation,
24
- secondaryAnimation: secondaryAnimation,
25
- transitionType: SharedAxisTransitionType.horizontal,
26
- fillColor: context.colors.background,
27
- child: builder(context),
28
- );
29
- }
30
- }
@@ -1,66 +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/subscription/repositories/subscription_repository.dart';
10
- import 'package:kasy_kit/i18n/translations.g.dart';
11
- import 'package:permission_handler/permission_handler.dart';
12
-
13
- /// ATT Permission Step
14
- /// ATT is only available on iOS
15
- /// This is the consent screen for iOS 14+ that asks the user to allow the app to access the ATT framework
16
- /// In a few words you need this to get the IDFA (Identifier for Advertisers) which is used for tracking purposes
17
- /// So you can create better facebook ads, google ads, etc...
18
- class AttPermissionStep extends ConsumerWidget {
19
- final String nextRoute;
20
-
21
- const AttPermissionStep({
22
- super.key,
23
- required this.nextRoute,
24
- });
25
-
26
- @override
27
- Widget build(BuildContext context, WidgetRef ref) {
28
- final translations = Translations.of(context).onboarding.att;
29
-
30
- return OnboardingBackground(
31
- child: OnboardingIllustrationScaffold(
32
- progress: 0.9,
33
- title: translations.title,
34
- description: translations.description,
35
- imageAsset: 'assets/images/onboarding/img3.jpg',
36
- footerActions: [
37
- KasyButton(
38
- label: translations.continue_button,
39
- expand: true,
40
- onPressed: () async {
41
- final Map<Permission, PermissionStatus> permission = await [
42
- Permission.appTrackingTransparency,
43
- ].request();
44
- final isGranted =
45
- permission.values.first == PermissionStatus.granted;
46
- ref.read(analyticsApiProvider).logEvent('att_request', {
47
- 'granted': isGranted,
48
- });
49
- final userId =
50
- ref.read(userStateNotifierProvider).user.idOrNull;
51
- if (userId != null) {
52
- ref
53
- .read(subscriptionRepositoryProvider)
54
- .initUser(userId)
55
- .ignore();
56
- ref.read(facebookEventApiProvider).initUser(userId).ignore();
57
- }
58
- if (!context.mounted) return;
59
- Navigator.of(context).pushNamed(nextRoute);
60
- },
61
- ),
62
- ],
63
- ),
64
- );
65
- }
66
- }