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.
- package/bin/kasy.js +21 -0
- package/lib/commands/splash.js +220 -0
- package/lib/scaffold/CHANGELOG.json +9 -0
- package/lib/scaffold/backends/api/patch/lib/main.dart +29 -10
- package/lib/scaffold/backends/supabase/patch/lib/main.dart +29 -10
- package/lib/scaffold/features/README.md +15 -139
- package/lib/scaffold/shared/generator-utils.js +16 -15
- package/lib/utils/i18n.js +78 -0
- package/package.json +2 -2
- package/templates/firebase/android/app/src/main/res/drawable-hdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-hdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-mdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-mdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night/background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night/launch_background.xml +9 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-hdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-hdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-mdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-mdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-v21/background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-v21/launch_background.xml +9 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xxhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xxxhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xxhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xxhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xxxhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xxxhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/values-night-v31/styles.xml +2 -1
- package/templates/firebase/android/app/src/main/res/values-v31/styles.xml +1 -0
- package/templates/firebase/assets/images/splash_logo_dark.png +0 -0
- package/templates/firebase/assets/images/splash_logo_light.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json +9 -8
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json +33 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@3x.png +0 -0
- package/templates/firebase/ios/Runner/Base.lproj/LaunchScreen.storyboard +1 -1
- package/templates/firebase/lib/core/theme/providers/theme_provider.dart +48 -24
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_features.dart +4 -0
- package/templates/firebase/lib/features/onboarding/ui/onboarding_page.dart +1 -0
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_feature.dart +13 -0
- package/templates/firebase/lib/features/settings/settings_page.dart +158 -18
- package/templates/firebase/lib/i18n/en.i18n.json +6 -2
- package/templates/firebase/lib/i18n/es.i18n.json +6 -2
- package/templates/firebase/lib/i18n/pt.i18n.json +6 -2
- package/templates/firebase/lib/main.dart +29 -10
- package/templates/firebase/pubspec.yaml +4 -5
- package/templates/firebase/test/core/data/repositories/user_repository_test.dart +1 -1
- package/templates/firebase/web/index.html +47 -39
- package/templates/firebase/web/splash/img/dark-1x.png +0 -0
- package/templates/firebase/web/splash/img/dark-2x.png +0 -0
- package/templates/firebase/web/splash/img/dark-3x.png +0 -0
- package/templates/firebase/web/splash/img/dark-4x.png +0 -0
- package/templates/firebase/web/splash/img/light-1x.png +0 -0
- package/templates/firebase/web/splash/img/light-2x.png +0 -0
- package/templates/firebase/web/splash/img/light-3x.png +0 -0
- package/templates/firebase/web/splash/img/light-4x.png +0 -0
- package/lib/scaffold/features/analytics/lib/core/data/api/analytics_api.dart +0 -124
- package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/BUG.es.md +0 -35
- package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/BUG.md +0 -35
- package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/BUG.pt.md +0 -35
- package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/feature_request.es.md +0 -12
- package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/feature_request.md +0 -12
- package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/feature_request.pt.md +0 -12
- package/lib/scaffold/features/ci/.github/PULL_REQUEST_TEMPLATE.es.md +0 -17
- package/lib/scaffold/features/ci/.github/PULL_REQUEST_TEMPLATE.md +0 -17
- package/lib/scaffold/features/ci/.github/PULL_REQUEST_TEMPLATE.pt.md +0 -17
- package/lib/scaffold/features/ci/.github/dependabot.yml +0 -16
- package/lib/scaffold/features/ci/.github/workflows/app.yml +0 -20
- package/lib/scaffold/features/ci/.gitlab/templates/deploy.yaml +0 -14
- package/lib/scaffold/features/ci/.gitlab/templates/dropbox.yaml +0 -19
- package/lib/scaffold/features/ci/.gitlab/templates/flutter.yaml +0 -163
- package/lib/scaffold/features/ci/.gitlab/templates/mailgun.yaml +0 -28
- package/lib/scaffold/features/ci/.gitlab-ci.yml +0 -37
- package/lib/scaffold/features/ci/codemagic.yaml +0 -157
- package/lib/scaffold/features/facebook/lib/core/data/api/tracking_api.dart +0 -111
- package/lib/scaffold/features/feedback/lib/features/feedbacks/api/entities/feature_request_entity.dart +0 -27
- package/lib/scaffold/features/feedback/lib/features/feedbacks/api/entities/feature_vote_entity.dart +0 -27
- package/lib/scaffold/features/feedback/lib/features/feedbacks/api/feature_request_api.dart +0 -50
- package/lib/scaffold/features/feedback/lib/features/feedbacks/api/feature_vote_api.dart +0 -79
- package/lib/scaffold/features/feedback/lib/features/feedbacks/models/feature_requests.dart +0 -48
- package/lib/scaffold/features/feedback/lib/features/feedbacks/models/feedback_state.dart +0 -42
- package/lib/scaffold/features/feedback/lib/features/feedbacks/providers/feedback_page_notifier.dart +0 -147
- package/lib/scaffold/features/feedback/lib/features/feedbacks/repositories/feature_request_repository.dart +0 -95
- package/lib/scaffold/features/feedback/lib/features/feedbacks/ui/component/add_feature_form.dart +0 -199
- package/lib/scaffold/features/feedback/lib/features/feedbacks/ui/feedback_page.dart +0 -175
- package/lib/scaffold/features/feedback/lib/features/feedbacks/ui/widgets/add_feature_button.dart +0 -76
- package/lib/scaffold/features/feedback/lib/features/feedbacks/ui/widgets/feature_card.dart +0 -279
- package/lib/scaffold/features/ios-release/.kasy/apple.env.example +0 -8
- package/lib/scaffold/features/ios-release/.kasy/codemagic.env.example +0 -7
- package/lib/scaffold/features/ios-release/docs/codemagic-release.en.md +0 -50
- package/lib/scaffold/features/ios-release/docs/codemagic-release.es.md +0 -50
- package/lib/scaffold/features/ios-release/docs/codemagic-release.pt.md +0 -50
- package/lib/scaffold/features/ios-release/docs/ios-release.en.md +0 -41
- package/lib/scaffold/features/ios-release/docs/ios-release.es.md +0 -41
- package/lib/scaffold/features/ios-release/docs/ios-release.pt.md +0 -41
- package/lib/scaffold/features/ios-release/scripts/bump-ios-version.js +0 -38
- package/lib/scaffold/features/ios-release/scripts/release-ios.sh +0 -137
- package/lib/scaffold/features/llm_chat/lib/features/llm_chat/llm_chat_page.dart +0 -301
- package/lib/scaffold/features/local_notifications/lib/features/local_reminder/providers/reminder_notifier.dart +0 -81
- package/lib/scaffold/features/local_notifications/lib/features/local_reminder/repositories/reminder_preferences.dart +0 -76
- package/lib/scaffold/features/local_notifications/lib/features/local_reminder/ui/reminder_page.dart +0 -282
- package/lib/scaffold/features/onboarding/lib/features/onboarding/api/entities/user_info_entity.dart +0 -24
- package/lib/scaffold/features/onboarding/lib/features/onboarding/api/user_infos_api.dart +0 -71
- package/lib/scaffold/features/onboarding/lib/features/onboarding/models/user_info.dart +0 -92
- package/lib/scaffold/features/onboarding/lib/features/onboarding/providers/onboarding_model.dart +0 -15
- package/lib/scaffold/features/onboarding/lib/features/onboarding/providers/onboarding_provider.dart +0 -78
- package/lib/scaffold/features/onboarding/lib/features/onboarding/repositories/user_infos_repository.dart +0 -29
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/animations/page_transitions.dart +0 -30
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/components/onboarding_att_setup.dart +0 -66
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/components/onboarding_features.dart +0 -72
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/components/onboarding_loader.dart +0 -92
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +0 -73
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/components/onboarding_questions.dart +0 -89
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/onboarding_page.dart +0 -94
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_background.dart +0 -80
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_feature.dart +0 -139
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_illustration_scaffold.dart +0 -110
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_progress.dart +0 -84
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +0 -173
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_reassurance.dart +0 -45
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_sticky_footer.dart +0 -77
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/selectable_row_tile.dart +0 -392
- package/lib/scaffold/features/revenuecat/lib/core/data/api/tracking_api.dart +0 -116
- package/lib/scaffold/features/revenuecat/lib/core/data/models/subscription.dart +0 -322
- package/lib/scaffold/features/revenuecat/lib/core/home_widgets/home_widget_background_task.dart +0 -41
- package/lib/scaffold/features/revenuecat/lib/core/states/user_state_notifier.dart +0 -305
- package/templates/firebase/assets/images/splashscreen.png +0 -0
package/lib/scaffold/features/local_notifications/lib/features/local_reminder/ui/reminder_page.dart
DELETED
|
@@ -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
|
-
}
|
package/lib/scaffold/features/onboarding/lib/features/onboarding/api/entities/user_info_entity.dart
DELETED
|
@@ -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
|
-
}
|
package/lib/scaffold/features/onboarding/lib/features/onboarding/providers/onboarding_provider.dart
DELETED
|
@@ -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
|
-
}
|
package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/animations/page_transitions.dart
DELETED
|
@@ -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
|
-
}
|