kasy-cli 1.32.0 → 1.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/kasy.js +66 -2
- package/docs/cli-reference.md +7 -7
- package/lib/commands/apple-web.js +222 -0
- package/lib/commands/configure.js +3 -91
- package/lib/commands/doctor.js +20 -0
- package/lib/commands/facebook.js +189 -0
- package/lib/commands/new.js +61 -11
- package/lib/commands/release-version.js +234 -0
- package/lib/commands/update.js +27 -0
- package/lib/scaffold/CHANGELOG.json +27 -0
- package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api.dart +24 -0
- package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api_interface.dart +15 -0
- package/lib/scaffold/backends/api/patch/lib/features/authentication/repositories/authentication_repository.dart +27 -0
- package/lib/scaffold/backends/api/patch/lib/features/subscriptions/api/entities/subscription_entity.dart +1 -0
- package/lib/scaffold/backends/firebase/setup-from-scratch.js +199 -21
- package/lib/scaffold/backends/patch-base-hashes.json +66 -0
- package/lib/scaffold/backends/supabase/deploy.js +92 -0
- package/lib/scaffold/backends/supabase/edge-functions/stripe-webhook/index.ts +2 -0
- package/lib/scaffold/backends/supabase/migrations/20240101000007_welcome_notification.sql +36 -23
- package/lib/scaffold/backends/supabase/migrations/20240101000014_subscription_trial_end.sql +6 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/authentication/api/authentication_api.dart +92 -3
- package/lib/scaffold/backends/supabase/patch/lib/features/authentication/repositories/authentication_repository.dart +36 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/subscriptions/api/entities/subscription_entity.dart +1 -0
- package/lib/scaffold/generate.js +53 -4
- package/lib/scaffold/shared/generator-utils.js +18 -6
- package/lib/utils/apple-web.js +147 -0
- package/lib/utils/facebook.js +162 -0
- package/lib/utils/i18n/messages-en.js +85 -0
- package/lib/utils/i18n/messages-es.js +85 -0
- package/lib/utils/i18n/messages-pt.js +85 -0
- package/package.json +5 -2
- package/templates/firebase/.firebase/hosting.YnVpbGQvd2Vi.cache +73 -0
- package/templates/firebase/AGENTS.md +170 -0
- package/templates/firebase/CLAUDE.md +16 -0
- package/templates/firebase/DESIGN_SYSTEM.md +269 -0
- package/templates/firebase/docs/auth-setup.en.md +4 -2
- package/templates/firebase/docs/auth-setup.es.md +4 -2
- package/templates/firebase/docs/auth-setup.pt.md +4 -2
- package/templates/firebase/firebase.json +56 -1
- package/templates/firebase/functions/src/authentication/triggers.ts +10 -6
- package/templates/firebase/functions/src/core/data/entities/subscription_entity.ts +5 -0
- package/templates/firebase/functions/src/core/data/entities/user_entity.ts +9 -1
- package/templates/firebase/functions/src/core/data/repositories/user_repository.ts +27 -0
- package/templates/firebase/functions/src/subscriptions/models/subscriptions.ts +3 -0
- package/templates/firebase/functions/src/subscriptions/stripe_functions.ts +4 -0
- package/templates/firebase/lib/components/components.dart +1 -0
- package/templates/firebase/lib/components/kasy_alert.dart +0 -1
- package/templates/firebase/lib/components/kasy_app_bar.dart +35 -17
- package/templates/firebase/lib/components/kasy_bottom_sheet.dart +0 -1
- package/templates/firebase/lib/components/kasy_date_picker.dart +0 -5
- package/templates/firebase/lib/components/kasy_dialog.dart +0 -1
- package/templates/firebase/lib/components/kasy_otp_verification_bottom_sheet.dart +1 -1
- package/templates/firebase/lib/components/kasy_screen.dart +114 -0
- package/templates/firebase/lib/components/kasy_sidebar.dart +189 -120
- package/templates/firebase/lib/components/kasy_text_area.dart +0 -1
- package/templates/firebase/lib/components/kasy_text_field.dart +0 -1
- package/templates/firebase/lib/components/kasy_text_field_otp.dart +0 -1
- package/templates/firebase/lib/components/kasy_toast.dart +108 -73
- package/templates/firebase/lib/core/app_update/app_update_repository.dart +54 -0
- package/templates/firebase/lib/core/app_update/app_update_status.dart +14 -0
- package/templates/firebase/lib/core/app_update/update_available_sheet.dart +70 -0
- package/templates/firebase/lib/core/bottom_menu/active_tab_notifier.dart +40 -3
- package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +82 -34
- package/templates/firebase/lib/core/bottom_menu/web_content_wrapper.dart +6 -6
- package/templates/firebase/lib/core/bottom_menu/web_url.dart +20 -0
- package/templates/firebase/lib/core/chrome/chrome_visibility.dart +22 -0
- package/templates/firebase/lib/core/config/features.dart +5 -0
- package/templates/firebase/lib/core/data/api/remote_config_api.dart +38 -1
- package/templates/firebase/lib/core/guards/guard.dart +16 -2
- package/templates/firebase/lib/core/icons/kasy_icons.dart +3 -0
- package/templates/firebase/lib/core/rating/widgets/rate_banner.dart +2 -5
- package/templates/firebase/lib/core/rating/widgets/review_popup.dart +48 -124
- package/templates/firebase/lib/core/shared_preferences/shared_preferences.dart +14 -0
- package/templates/firebase/lib/core/states/components/maybe_show_update_available.dart +32 -0
- package/templates/firebase/lib/core/states/logout_action.dart +5 -1
- package/templates/firebase/lib/core/states/user_state_notifier.dart +85 -14
- package/templates/firebase/lib/core/theme/responsive_text_theme.dart +69 -0
- package/templates/firebase/lib/core/theme/texts.dart +90 -57
- package/templates/firebase/lib/core/theme/type_scale.dart +77 -0
- package/templates/firebase/lib/core/theme/web_background_sync_web.dart +20 -6
- package/templates/firebase/lib/core/utils/image_bytes_loader.dart +12 -0
- package/templates/firebase/lib/core/utils/image_bytes_loader_io.dart +28 -0
- package/templates/firebase/lib/core/utils/image_bytes_loader_web.dart +56 -0
- package/templates/firebase/lib/core/web_screen_width.dart +15 -0
- package/templates/firebase/lib/core/web_screen_width_io.dart +3 -0
- package/templates/firebase/lib/core/web_screen_width_web.dart +10 -0
- package/templates/firebase/lib/core/web_viewport_scale.dart +61 -25
- package/templates/firebase/lib/core/widgets/focus_visibility.dart +89 -0
- package/templates/firebase/lib/core/widgets/update_bottom_sheet.dart +29 -126
- package/templates/firebase/lib/features/ai_chat/ai_chat_page.dart +11 -8
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_chat_composer.dart +21 -0
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_chat_conversation_view.dart +1 -2
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_list.dart +2 -3
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_tile.dart +1 -2
- package/templates/firebase/lib/features/authentication/api/auth_web_support.dart +12 -0
- package/templates/firebase/lib/features/authentication/api/auth_web_support_web.dart +25 -0
- package/templates/firebase/lib/features/authentication/api/authentication_api.dart +266 -0
- package/templates/firebase/lib/features/authentication/api/authentication_api_interface.dart +22 -0
- package/templates/firebase/lib/features/authentication/navigation/post_login_navigation.dart +32 -0
- package/templates/firebase/lib/features/authentication/providers/phone_auth_notifier.dart +7 -7
- package/templates/firebase/lib/features/authentication/providers/signin_state_provider.dart +34 -10
- package/templates/firebase/lib/features/authentication/providers/signup_state_provider.dart +2 -2
- package/templates/firebase/lib/features/authentication/repositories/authentication_repository.dart +37 -0
- package/templates/firebase/lib/features/authentication/repositories/exceptions/authentication_exceptions.dart +8 -0
- package/templates/firebase/lib/features/authentication/ui/components/otp_verification.dart +2 -2
- package/templates/firebase/lib/features/authentication/ui/components/phone_input.dart +2 -2
- package/templates/firebase/lib/features/authentication/ui/signin_page.dart +80 -15
- package/templates/firebase/lib/features/authentication/ui/signup_page.dart +20 -14
- package/templates/firebase/lib/features/authentication/ui/widgets/auth_card_scaffold.dart +2 -2
- package/templates/firebase/lib/features/authentication/ui/widgets/recover_password_result.dart +2 -2
- package/templates/firebase/lib/features/feedbacks/ui/widgets/add_feature_button.dart +0 -1
- package/templates/firebase/lib/features/feedbacks/ui/widgets/feature_card.dart +1 -2
- package/templates/firebase/lib/features/home/design_system_page.dart +134 -67
- package/templates/firebase/lib/features/home/home_components_page.dart +8 -1
- package/templates/firebase/lib/features/home/home_components_preview_page.dart +1 -3
- package/templates/firebase/lib/features/home/home_components_preview_registry.dart +186 -56
- package/templates/firebase/lib/features/home/home_page.dart +4 -0
- package/templates/firebase/lib/features/local_reminders/ui/reminder_page.dart +169 -208
- package/templates/firebase/lib/features/notifications/providers/notifications_provider.dart +10 -0
- package/templates/firebase/lib/features/notifications/ui/components/notification_settings_sheet.dart +2 -2
- package/templates/firebase/lib/features/notifications/ui/components/notification_tile.dart +21 -8
- package/templates/firebase/lib/features/notifications/ui/components/push_notification_switcher.dart +2 -2
- package/templates/firebase/lib/features/notifications/ui/notifications_page.dart +3 -4
- package/templates/firebase/lib/features/notifications/ui/widgets/notification_tile.dart +84 -128
- package/templates/firebase/lib/features/onboarding/providers/onboarding_model.dart +11 -0
- package/templates/firebase/lib/features/onboarding/providers/onboarding_provider.dart +30 -3
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_loader.dart +13 -4
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_illustration_scaffold.dart +3 -4
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +2 -4
- package/templates/firebase/lib/features/onboarding/ui/widgets/selectable_row_tile.dart +2 -1
- package/templates/firebase/lib/features/settings/settings_page.dart +152 -11
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_page.dart +58 -21
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_tab.dart +12 -12
- package/templates/firebase/lib/features/settings/ui/components/admin/send_push_notification_page.dart +1 -4
- package/templates/firebase/lib/features/settings/ui/components/avatar_component.dart +2 -2
- package/templates/firebase/lib/features/settings/ui/components/create_password_sheet.dart +141 -0
- package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +0 -1
- package/templates/firebase/lib/features/settings/ui/widgets/admin_card.dart +42 -38
- package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +17 -2
- package/templates/firebase/lib/features/subscriptions/api/entities/subscription_entity.dart +3 -0
- package/templates/firebase/lib/features/subscriptions/api/stripe_product.dart +12 -11
- package/templates/firebase/lib/features/subscriptions/repositories/subscription_repository.dart +25 -2
- package/templates/firebase/lib/features/subscriptions/ui/component/active_premium_content.dart +319 -143
- package/templates/firebase/lib/features/subscriptions/ui/component/paywall_minimal.dart +1 -5
- package/templates/firebase/lib/features/subscriptions/ui/component/paywall_row.dart +1 -5
- package/templates/firebase/lib/features/subscriptions/ui/component/paywall_with_switch.dart +2 -5
- package/templates/firebase/lib/features/subscriptions/ui/component/premium_content.dart +1 -4
- package/templates/firebase/lib/features/subscriptions/ui/widgets/feature_line.dart +1 -2
- package/templates/firebase/lib/features/subscriptions/ui/widgets/premium_banner.dart +2 -7
- package/templates/firebase/lib/features/subscriptions/ui/widgets/premium_feature.dart +2 -4
- package/templates/firebase/lib/features/subscriptions/ui/widgets/selectable_col.dart +1 -4
- package/templates/firebase/lib/features/subscriptions/ui/widgets/selectable_row.dart +0 -3
- package/templates/firebase/lib/i18n/en.i18n.json +54 -7
- package/templates/firebase/lib/i18n/es.i18n.json +54 -7
- package/templates/firebase/lib/i18n/pt.i18n.json +54 -7
- package/templates/firebase/lib/main.dart +11 -2
- package/templates/firebase/lib/router.dart +94 -13
- package/templates/firebase/pubspec.yaml +1 -1
- package/templates/firebase/test/core/data/api/fake_remote_config_api.dart +14 -0
- package/templates/firebase/test/core/states/user_state_notifier_test.dart +47 -3
- package/templates/firebase/test/core/web_viewport_scale_test.dart +68 -0
- package/templates/firebase/test/features/authentication/data/api/auth_api_fake.dart +15 -0
- package/templates/firebase/tool/design_check.dart +152 -0
- package/templates/firebase/web/index.html +162 -14
- package/templates/firebase/assets/images/review.png +0 -0
- package/templates/firebase/assets/images/update.png +0 -0
- package/templates/firebase/lib/core/guards/user_info_guard.dart +0 -61
- package/templates/firebase/lib/features/notifications/ui/components/notifications_header.dart +0 -32
|
@@ -9,8 +9,13 @@ import 'package:kasy_kit/features/notifications/providers/models/notification.da
|
|
|
9
9
|
typedef OnTapNotification = void Function(app.Notification notification);
|
|
10
10
|
|
|
11
11
|
/// A tile for the [NotificationsPage] widget.
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
///
|
|
13
|
+
/// Renders only the row content (icon + title / body / date + unread dot). The
|
|
14
|
+
/// card surface (elevated [KasyCard]) and the tap/hover feedback live in
|
|
15
|
+
/// [NotificationTileComponent], so a notification reads exactly like a Settings
|
|
16
|
+
/// card. Unread vs read is signalled by the trailing dot and text strength, not
|
|
17
|
+
/// by a tinted background.
|
|
18
|
+
class NotificationTile extends StatelessWidget {
|
|
14
19
|
final double titleOpacity;
|
|
15
20
|
final Color titleColor;
|
|
16
21
|
final Color descriptionColor;
|
|
@@ -24,7 +29,6 @@ class NotificationTile extends StatefulWidget {
|
|
|
24
29
|
|
|
25
30
|
const NotificationTile({
|
|
26
31
|
super.key,
|
|
27
|
-
required this.bgOpacity,
|
|
28
32
|
required this.titleOpacity,
|
|
29
33
|
required this.icon,
|
|
30
34
|
required this.date,
|
|
@@ -47,7 +51,6 @@ class NotificationTile extends StatefulWidget {
|
|
|
47
51
|
Key? key,
|
|
48
52
|
}) => NotificationTile(
|
|
49
53
|
key: key,
|
|
50
|
-
bgOpacity: .08,
|
|
51
54
|
titleOpacity: 1,
|
|
52
55
|
showUnreadDot: true,
|
|
53
56
|
icon: imageUrl != null
|
|
@@ -56,7 +59,9 @@ class NotificationTile extends StatefulWidget {
|
|
|
56
59
|
date: date,
|
|
57
60
|
title: title,
|
|
58
61
|
description: description,
|
|
59
|
-
|
|
62
|
+
// Unread notifications carry the brand primary on the title so they read
|
|
63
|
+
// as the active, attention-worthy item (the read variant stays muted).
|
|
64
|
+
titleColor: context.colors.primary,
|
|
60
65
|
descriptionColor: context.colors.onSurface,
|
|
61
66
|
dateColor: context.colors.muted,
|
|
62
67
|
onTap: onTap,
|
|
@@ -72,7 +77,6 @@ class NotificationTile extends StatefulWidget {
|
|
|
72
77
|
Key? key,
|
|
73
78
|
}) => NotificationTile(
|
|
74
79
|
key: key,
|
|
75
|
-
bgOpacity: 0,
|
|
76
80
|
titleOpacity: .6,
|
|
77
81
|
icon: imageUrl != null
|
|
78
82
|
? TileNotificationImage(url: imageUrl)
|
|
@@ -80,7 +84,10 @@ class NotificationTile extends StatefulWidget {
|
|
|
80
84
|
date: date,
|
|
81
85
|
title: title,
|
|
82
86
|
description: description,
|
|
83
|
-
|
|
87
|
+
// Read notifications keep a full-strength, bold title (it just loses the
|
|
88
|
+
// primary colour and the unread dot); only the description is muted, so the
|
|
89
|
+
// title still reads as bold instead of washed out.
|
|
90
|
+
titleColor: context.colors.onSurface,
|
|
84
91
|
descriptionColor: context.colors.onSurface.withValues(alpha: 0.45),
|
|
85
92
|
dateColor: context.colors.muted,
|
|
86
93
|
onTap: onTap,
|
|
@@ -111,124 +118,66 @@ class NotificationTile extends StatefulWidget {
|
|
|
111
118
|
onTap: onTap,
|
|
112
119
|
);
|
|
113
120
|
|
|
114
|
-
@override
|
|
115
|
-
State<NotificationTile> createState() => _NotificationTileState();
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
class _NotificationTileState extends State<NotificationTile>
|
|
119
|
-
with SingleTickerProviderStateMixin {
|
|
120
|
-
late final AnimationController _controller;
|
|
121
|
-
late Animation<double> _opacityAnimation;
|
|
122
|
-
|
|
123
|
-
@override
|
|
124
|
-
void initState() {
|
|
125
|
-
super.initState();
|
|
126
|
-
_controller = AnimationController(
|
|
127
|
-
duration: const Duration(milliseconds: 500),
|
|
128
|
-
vsync: this,
|
|
129
|
-
);
|
|
130
|
-
_opacityAnimation = Tween(
|
|
131
|
-
begin: 0.0,
|
|
132
|
-
end: widget.bgOpacity,
|
|
133
|
-
).animate(CurvedAnimation(parent: _controller, curve: Curves.decelerate));
|
|
134
|
-
_controller.forward();
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
@override
|
|
138
|
-
void didUpdateWidget(covariant NotificationTile oldWidget) {
|
|
139
|
-
super.didUpdateWidget(oldWidget);
|
|
140
|
-
if (oldWidget.bgOpacity != widget.bgOpacity) {
|
|
141
|
-
_opacityAnimation = Tween(
|
|
142
|
-
begin: oldWidget.bgOpacity,
|
|
143
|
-
end: widget.bgOpacity,
|
|
144
|
-
).animate(CurvedAnimation(parent: _controller, curve: Curves.ease));
|
|
145
|
-
_controller.forward(from: 0);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
@override
|
|
150
|
-
void dispose() {
|
|
151
|
-
_controller.dispose();
|
|
152
|
-
super.dispose();
|
|
153
|
-
}
|
|
154
|
-
|
|
155
121
|
@override
|
|
156
122
|
Widget build(BuildContext context) {
|
|
157
|
-
return
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
Flexible(
|
|
181
|
-
flex: 0,
|
|
182
|
-
child: Text(
|
|
183
|
-
widget.title,
|
|
184
|
-
style: context.textTheme.labelLarge?.copyWith(
|
|
185
|
-
color: widget.titleColor,
|
|
186
|
-
),
|
|
187
|
-
overflow: TextOverflow.clip,
|
|
188
|
-
maxLines: 2,
|
|
189
|
-
),
|
|
190
|
-
),
|
|
191
|
-
const SizedBox(height: KasySpacing.xs),
|
|
192
|
-
Flexible(
|
|
193
|
-
flex: 0,
|
|
194
|
-
child: Text(
|
|
195
|
-
widget.description,
|
|
196
|
-
style: context.textTheme.bodyMedium?.copyWith(
|
|
197
|
-
color: widget.descriptionColor,
|
|
123
|
+
return Row(
|
|
124
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
125
|
+
children: [
|
|
126
|
+
icon,
|
|
127
|
+
const SizedBox(width: KasySpacing.sm),
|
|
128
|
+
Expanded(
|
|
129
|
+
child: Column(
|
|
130
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
131
|
+
children: [
|
|
132
|
+
// Headline row: bold title on the left, timestamp (and the
|
|
133
|
+
// unread dot) pinned to the right — the compact, professional
|
|
134
|
+
// notification layout (Linear / Gmail).
|
|
135
|
+
Row(
|
|
136
|
+
children: [
|
|
137
|
+
Expanded(
|
|
138
|
+
child: Text(
|
|
139
|
+
title,
|
|
140
|
+
style: context.textTheme.titleSmall?.copyWith(
|
|
141
|
+
color: titleColor,
|
|
142
|
+
fontWeight: FontWeight.w600,
|
|
143
|
+
),
|
|
144
|
+
overflow: TextOverflow.ellipsis,
|
|
145
|
+
maxLines: 1,
|
|
198
146
|
),
|
|
199
|
-
overflow: TextOverflow.ellipsis,
|
|
200
|
-
maxLines: 3,
|
|
201
147
|
),
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
flex: 0,
|
|
206
|
-
child: Text(
|
|
207
|
-
Jiffy.parseFromDateTime(widget.date).fromNow(),
|
|
148
|
+
const SizedBox(width: KasySpacing.sm),
|
|
149
|
+
Text(
|
|
150
|
+
Jiffy.parseFromDateTime(date).fromNow(),
|
|
208
151
|
style: context.textTheme.bodySmall?.copyWith(
|
|
209
|
-
color:
|
|
152
|
+
color: dateColor,
|
|
210
153
|
),
|
|
211
154
|
),
|
|
155
|
+
if (showUnreadDot) ...[
|
|
156
|
+
const SizedBox(width: KasySpacing.sm),
|
|
157
|
+
Container(
|
|
158
|
+
width: 6,
|
|
159
|
+
height: 6,
|
|
160
|
+
decoration: BoxDecoration(
|
|
161
|
+
shape: BoxShape.circle,
|
|
162
|
+
color: context.colors.primary,
|
|
163
|
+
),
|
|
164
|
+
),
|
|
165
|
+
],
|
|
166
|
+
],
|
|
167
|
+
),
|
|
168
|
+
const SizedBox(height: KasySpacing.xs),
|
|
169
|
+
Text(
|
|
170
|
+
description,
|
|
171
|
+
style: context.textTheme.bodyMedium?.copyWith(
|
|
172
|
+
color: descriptionColor,
|
|
212
173
|
),
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
),
|
|
216
|
-
if (widget.showUnreadDot) ...[
|
|
217
|
-
const SizedBox(width: KasySpacing.sm),
|
|
218
|
-
Padding(
|
|
219
|
-
padding: const EdgeInsets.only(top: 3),
|
|
220
|
-
child: Container(
|
|
221
|
-
width: 8,
|
|
222
|
-
height: 8,
|
|
223
|
-
decoration: BoxDecoration(
|
|
224
|
-
shape: BoxShape.circle,
|
|
225
|
-
color: context.colors.primary,
|
|
226
|
-
),
|
|
174
|
+
overflow: TextOverflow.ellipsis,
|
|
175
|
+
maxLines: 2,
|
|
227
176
|
),
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
177
|
+
],
|
|
178
|
+
),
|
|
179
|
+
),
|
|
180
|
+
],
|
|
232
181
|
);
|
|
233
182
|
}
|
|
234
183
|
}
|
|
@@ -269,8 +218,8 @@ class TileNotificationImage extends StatelessWidget {
|
|
|
269
218
|
borderRadius: BorderRadius.circular(8),
|
|
270
219
|
child: Image.network(
|
|
271
220
|
url,
|
|
272
|
-
width:
|
|
273
|
-
height:
|
|
221
|
+
width: 36,
|
|
222
|
+
height: 36,
|
|
274
223
|
fit: BoxFit.cover,
|
|
275
224
|
errorBuilder: (context, error, stack) =>
|
|
276
225
|
const TileNotificationIcon(active: false),
|
|
@@ -297,8 +246,8 @@ class TileNotificationIcon extends StatelessWidget {
|
|
|
297
246
|
? context.colors.primary
|
|
298
247
|
: context.colors.onSurface.withValues(alpha: 0.35);
|
|
299
248
|
return Container(
|
|
300
|
-
width:
|
|
301
|
-
height:
|
|
249
|
+
width: 36,
|
|
250
|
+
height: 36,
|
|
302
251
|
decoration: BoxDecoration(
|
|
303
252
|
color: active
|
|
304
253
|
? context.colors.primary.withValues(alpha: 0.12)
|
|
@@ -307,7 +256,7 @@ class TileNotificationIcon extends StatelessWidget {
|
|
|
307
256
|
),
|
|
308
257
|
child: Icon(
|
|
309
258
|
active ? KasyIcons.notificationActive : KasyIcons.notification,
|
|
310
|
-
size: KasyIconSize.
|
|
259
|
+
size: KasyIconSize.sm,
|
|
311
260
|
color: color,
|
|
312
261
|
),
|
|
313
262
|
);
|
|
@@ -326,27 +275,34 @@ class NotificationSkeletonTile extends StatelessWidget {
|
|
|
326
275
|
padding: EdgeInsets.only(top: KasySpacing.sm),
|
|
327
276
|
child: KasySkeletonGroup(
|
|
328
277
|
child: Padding(
|
|
329
|
-
padding: EdgeInsets.
|
|
278
|
+
padding: EdgeInsets.symmetric(
|
|
279
|
+
horizontal: KasySpacing.md,
|
|
280
|
+
vertical: KasySpacing.smd,
|
|
281
|
+
),
|
|
330
282
|
child: Row(
|
|
331
283
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
332
284
|
children: [
|
|
333
285
|
KasySkeleton(
|
|
334
|
-
width:
|
|
335
|
-
height:
|
|
286
|
+
width: 36,
|
|
287
|
+
height: 36,
|
|
336
288
|
borderRadius: BorderRadius.all(Radius.circular(10)),
|
|
337
289
|
),
|
|
338
|
-
SizedBox(width: KasySpacing.
|
|
290
|
+
SizedBox(width: KasySpacing.sm),
|
|
339
291
|
Expanded(
|
|
340
292
|
child: Column(
|
|
341
293
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
342
294
|
children: [
|
|
343
|
-
|
|
295
|
+
Row(
|
|
296
|
+
children: [
|
|
297
|
+
Expanded(child: KasySkeleton(width: 150, height: 13)),
|
|
298
|
+
SizedBox(width: KasySpacing.sm),
|
|
299
|
+
KasySkeleton(width: 44, height: 10),
|
|
300
|
+
],
|
|
301
|
+
),
|
|
344
302
|
SizedBox(height: KasySpacing.sm),
|
|
345
303
|
KasySkeleton(width: double.infinity, height: 11),
|
|
346
304
|
SizedBox(height: KasySpacing.xs),
|
|
347
305
|
KasySkeleton(width: 210, height: 11),
|
|
348
|
-
SizedBox(height: KasySpacing.smd),
|
|
349
|
-
KasySkeleton(width: 70, height: 10),
|
|
350
306
|
],
|
|
351
307
|
),
|
|
352
308
|
),
|
|
@@ -1,15 +1,26 @@
|
|
|
1
|
+
import 'package:kasy_kit/features/onboarding/models/user_info.dart';
|
|
2
|
+
|
|
1
3
|
class OnboardingState {
|
|
2
4
|
DateTime? reminder;
|
|
3
5
|
|
|
6
|
+
/// Answers collected before the anonymous account exists. The account is now
|
|
7
|
+
/// created lazily at the end of onboarding (the loader screen), so the
|
|
8
|
+
/// question screens have no user id to write to yet — we buffer the answers
|
|
9
|
+
/// here and flush them once the account is created.
|
|
10
|
+
final List<UserInfoDetail> pendingUserInfo;
|
|
11
|
+
|
|
4
12
|
OnboardingState({
|
|
5
13
|
this.reminder,
|
|
14
|
+
this.pendingUserInfo = const [],
|
|
6
15
|
});
|
|
7
16
|
|
|
8
17
|
OnboardingState copyWith({
|
|
9
18
|
DateTime? reminder,
|
|
19
|
+
List<UserInfoDetail>? pendingUserInfo,
|
|
10
20
|
}) {
|
|
11
21
|
return OnboardingState(
|
|
12
22
|
reminder: reminder ?? this.reminder,
|
|
23
|
+
pendingUserInfo: pendingUserInfo ?? this.pendingUserInfo,
|
|
13
24
|
);
|
|
14
25
|
}
|
|
15
26
|
}
|
|
@@ -31,8 +31,18 @@ class OnboardingNotifier extends _$OnboardingNotifier {
|
|
|
31
31
|
|
|
32
32
|
Future<void> onAnsweredQuestion(UserInfoDetail value) async {
|
|
33
33
|
final userId = ref.read(userStateNotifierProvider).user.idOrNull;
|
|
34
|
-
if (userId
|
|
35
|
-
|
|
34
|
+
if (userId != null) {
|
|
35
|
+
// Account already exists (e.g. a returning guest re-onboarding): save now.
|
|
36
|
+
await ref.read(userInfosRepositoryProvider).save(userId, value);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
// No account yet — it's created at the end of onboarding. Buffer the answer
|
|
40
|
+
// (replacing any previous answer of the same kind) so it isn't lost; it's
|
|
41
|
+
// flushed in [onOnboardingCompleted] once the account exists.
|
|
42
|
+
final others = state.pendingUserInfo
|
|
43
|
+
.where((info) => info.runtimeType != value.runtimeType)
|
|
44
|
+
.toList();
|
|
45
|
+
state = state.copyWith(pendingUserInfo: [...others, value]);
|
|
36
46
|
}
|
|
37
47
|
|
|
38
48
|
Future<void> setupNotifications() async {
|
|
@@ -66,7 +76,24 @@ class OnboardingNotifier extends _$OnboardingNotifier {
|
|
|
66
76
|
Future<void> onOnboardingCompleted() async {
|
|
67
77
|
final userStateNotifier = ref.read(userStateNotifierProvider.notifier);
|
|
68
78
|
|
|
69
|
-
|
|
79
|
+
// This is the "preparing everything for you" moment (the loader screen):
|
|
80
|
+
// the only place the anonymous guest account gets created. Doing it here —
|
|
81
|
+
// instead of eagerly on app start — means a fresh anonymous account is born
|
|
82
|
+
// only when the user actually commits to using the app.
|
|
83
|
+
await userStateNotifier.continueAsGuest();
|
|
84
|
+
|
|
85
|
+
// Now that the account exists, flush the answers collected during the
|
|
86
|
+
// questions (gender, age, …) that had nowhere to go before.
|
|
87
|
+
final userId = ref.read(userStateNotifierProvider).user.idOrNull;
|
|
88
|
+
final pending = state.pendingUserInfo;
|
|
89
|
+
if (userId != null && pending.isNotEmpty) {
|
|
90
|
+
final repository = ref.read(userInfosRepositoryProvider);
|
|
91
|
+
for (final info in pending) {
|
|
92
|
+
await repository.save(userId, info);
|
|
93
|
+
}
|
|
94
|
+
state = state.copyWith(pendingUserInfo: const []);
|
|
95
|
+
}
|
|
96
|
+
|
|
70
97
|
await userStateNotifier.refresh();
|
|
71
98
|
}
|
|
72
99
|
|
|
@@ -20,10 +20,19 @@ class _OnboardingJournalLoaderState extends ConsumerState<OnboardingLoader> {
|
|
|
20
20
|
@override
|
|
21
21
|
void initState() {
|
|
22
22
|
super.initState();
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
_prepare();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
Future<void> _prepare() async {
|
|
27
|
+
// Run the real work (which now lazily creates the guest account) while the
|
|
28
|
+
// loader animation plays, so its network latency is hidden under the
|
|
29
|
+
// minimum display time instead of being added on top of it.
|
|
30
|
+
await Future.wait([
|
|
31
|
+
ref.onboardingNotifier.onOnboardingCompleted(),
|
|
32
|
+
Future<void>.delayed(const Duration(milliseconds: 3500)),
|
|
33
|
+
]);
|
|
34
|
+
if (!mounted) return;
|
|
35
|
+
widget.onCompleted();
|
|
27
36
|
}
|
|
28
37
|
|
|
29
38
|
@override
|
package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_illustration_scaffold.dart
CHANGED
|
@@ -93,12 +93,11 @@ class OnboardingIllustrationScaffold extends StatelessWidget {
|
|
|
93
93
|
child: Text(
|
|
94
94
|
title,
|
|
95
95
|
textAlign: TextAlign.start,
|
|
96
|
+
// Design-system page-title scale (headlineMedium = 24/w700).
|
|
97
|
+
// Previously bumped to 28 with custom tracking, which read as
|
|
98
|
+
// oversized next to the rest of the app.
|
|
96
99
|
style: context.textTheme.headlineMedium?.copyWith(
|
|
97
100
|
color: colors.onBackground,
|
|
98
|
-
fontSize: 28,
|
|
99
|
-
fontWeight: FontWeight.w700,
|
|
100
|
-
letterSpacing: -0.3,
|
|
101
|
-
height: 1.2,
|
|
102
101
|
),
|
|
103
102
|
),
|
|
104
103
|
),
|
package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart
CHANGED
|
@@ -71,12 +71,10 @@ class _OnboardingRadioQuestionState
|
|
|
71
71
|
child: Text(
|
|
72
72
|
widget.title,
|
|
73
73
|
textAlign: TextAlign.start,
|
|
74
|
+
// Design-system page-title scale (headlineMedium = 24/w700),
|
|
75
|
+
// matching the illustration steps instead of a bespoke 26px size.
|
|
74
76
|
style: context.textTheme.headlineMedium?.copyWith(
|
|
75
77
|
color: context.colors.onBackground,
|
|
76
|
-
fontSize: 26,
|
|
77
|
-
fontWeight: FontWeight.w700,
|
|
78
|
-
letterSpacing: -0.2,
|
|
79
|
-
height: 1.2,
|
|
80
78
|
),
|
|
81
79
|
),
|
|
82
80
|
),
|
|
@@ -283,7 +283,8 @@ class _SelectableRowTileState extends State<SelectableRowTile>
|
|
|
283
283
|
padding: const EdgeInsets.only(right: KasySpacing.md),
|
|
284
284
|
child: Text(
|
|
285
285
|
widget.emoj!,
|
|
286
|
-
style: context.textTheme.titleMedium
|
|
286
|
+
style: context.textTheme.titleMedium
|
|
287
|
+
?.copyWith(fontSize: 28), // design-check: ignore — emoji glyph
|
|
287
288
|
),
|
|
288
289
|
),
|
|
289
290
|
),
|