kasy-cli 1.38.0 → 1.39.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/lib/scaffold/CHANGELOG.json +14 -0
- package/lib/scaffold/backends/api/patch/README.md +15 -0
- package/lib/scaffold/backends/api/patch/lib/features/notifications/api/device_api.dart +11 -6
- package/lib/scaffold/backends/api/patch/lib/features/settings/ui/components/admin/admin_users_api.dart +9 -1
- package/lib/scaffold/backends/api/pubspec.yaml.tpl +1 -0
- package/lib/scaffold/backends/patch-base-hashes.json +6 -6
- package/lib/scaffold/backends/supabase/edge-functions/admin-list-users/index.ts +3 -1
- package/lib/scaffold/backends/supabase/edge-functions/send-push-notification/index.ts +3 -0
- package/lib/scaffold/backends/supabase/migrations/20240101000012_welcome_decouple_from_push.sql +62 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/notifications/api/device_api.dart +11 -6
- package/lib/scaffold/backends/supabase/patch/lib/features/settings/ui/components/admin/admin_users_api.dart +7 -0
- package/lib/scaffold/backends/supabase/pubspec.yaml.tpl +1 -0
- package/lib/scaffold/shared/generator-utils.js +12 -6
- package/package.json +1 -1
- package/templates/firebase/.firebase/hosting.YnVpbGQvd2Vi.cache +23 -23
- package/templates/firebase/DESIGN_SYSTEM.md +22 -8
- package/templates/firebase/assets/icons/apple_black.svg +3 -0
- package/templates/firebase/assets/icons/apple_white.svg +4 -0
- package/templates/firebase/assets/icons/facebook.svg +49 -0
- package/templates/firebase/assets/icons/google.svg +1 -0
- package/templates/firebase/functions/src/admin/functions.ts +2 -0
- package/templates/firebase/functions/src/authentication/functions.ts +13 -7
- package/templates/firebase/functions/src/notifications/triggers.ts +6 -2
- package/templates/firebase/lib/components/components.dart +1 -1
- package/templates/firebase/lib/components/kasy_app_bar.dart +314 -14
- package/templates/firebase/lib/components/kasy_card.dart +4 -0
- package/templates/firebase/lib/components/kasy_drop_down.dart +584 -0
- package/templates/firebase/lib/components/kasy_sidebar.dart +18 -6
- package/templates/firebase/lib/components/kasy_tabs.dart +31 -10
- package/templates/firebase/lib/components/kasy_text_field.dart +29 -7
- package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +27 -18
- package/templates/firebase/lib/core/bottom_menu/web_content_wrapper.dart +12 -10
- package/templates/firebase/lib/core/chrome/app_bar_config.dart +214 -0
- package/templates/firebase/lib/core/chrome/app_bar_scope.dart +102 -0
- package/templates/firebase/lib/core/data/api/user_api.dart +11 -0
- package/templates/firebase/lib/core/dev_inspector/dev_inspector_service.dart +55 -15
- package/templates/firebase/lib/core/rating/widgets/review_popup.dart +18 -35
- package/templates/firebase/lib/core/shared_preferences/shared_preferences.dart +11 -0
- package/templates/firebase/lib/core/states/logout_action.dart +11 -1
- package/templates/firebase/lib/core/states/user_state_notifier.dart +28 -1
- package/templates/firebase/lib/core/theme/texts.dart +21 -6
- package/templates/firebase/lib/core/theme/type_scale.dart +34 -15
- package/templates/firebase/lib/core/web_device_preview/web_device_preview.dart +51 -19
- package/templates/firebase/lib/core/web_viewport_scale.dart +64 -35
- package/templates/firebase/lib/core/widgets/kasy_pressable_depth.dart +14 -3
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_chat_composer.dart +1 -1
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_chat_conversation_view.dart +52 -35
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_list.dart +1 -1
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_tile.dart +18 -8
- package/templates/firebase/lib/features/authentication/ui/signin_page.dart +11 -61
- package/templates/firebase/lib/features/authentication/ui/signup_page.dart +11 -61
- package/templates/firebase/lib/features/authentication/ui/widgets/auth_card_scaffold.dart +7 -5
- package/templates/firebase/lib/features/authentication/ui/widgets/social_auth_tile.dart +83 -0
- package/templates/firebase/lib/features/feedbacks/ui/widgets/feature_card.dart +4 -4
- package/templates/firebase/lib/features/home/home_components_page.dart +253 -125
- package/templates/firebase/lib/features/home/home_components_preview_registry.dart +231 -57
- package/templates/firebase/lib/features/home/home_feed.dart +2 -2
- package/templates/firebase/lib/features/home/home_image_grid.dart +3 -3
- package/templates/firebase/lib/features/local_reminders/providers/reminder_notifier.dart +8 -1
- package/templates/firebase/lib/features/local_reminders/ui/reminder_page.dart +111 -57
- package/templates/firebase/lib/features/notifications/api/device_api.dart +11 -3
- package/templates/firebase/lib/features/notifications/ui/notifications_page.dart +16 -4
- package/templates/firebase/lib/features/notifications/ui/widgets/empty_notifications.dart +7 -56
- package/templates/firebase/lib/features/notifications/ui/widgets/notification_tile.dart +5 -5
- package/templates/firebase/lib/features/notifications/ui/widgets/push_permission_banner.dart +163 -0
- package/templates/firebase/lib/features/notifications/ui/widgets/web_notifications_bell.dart +2 -2
- package/templates/firebase/lib/features/onboarding/providers/onboarding_model.dart +9 -0
- package/templates/firebase/lib/features/onboarding/providers/onboarding_provider.dart +28 -0
- package/templates/firebase/lib/features/onboarding/ui/onboarding_page.dart +51 -12
- package/templates/firebase/lib/features/onboarding/ui/widgets/selectable_row_tile.dart +90 -32
- package/templates/firebase/lib/features/settings/settings_page.dart +53 -32
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_page.dart +895 -111
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_api.dart +7 -0
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_tab.dart +445 -233
- package/templates/firebase/lib/features/settings/ui/components/admin/send_push_notification_page.dart +171 -41
- package/templates/firebase/lib/features/settings/ui/components/avatar_component.dart +1 -1
- package/templates/firebase/lib/features/settings/ui/components/delete_user_component.dart +9 -1
- package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +48 -47
- package/templates/firebase/lib/features/settings/ui/widgets/settings_bottom_sheet_option_tile.dart +21 -18
- package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +25 -10
- package/templates/firebase/lib/i18n/en.i18n.json +749 -712
- package/templates/firebase/lib/i18n/es.i18n.json +749 -712
- package/templates/firebase/lib/i18n/pt.i18n.json +749 -712
- package/templates/firebase/lib/main.dart +20 -7
- package/templates/firebase/lib/router.dart +32 -26
- package/templates/firebase/pubspec.yaml +2 -1
- package/templates/firebase/test/admin_shell_chrome_test.dart +11 -5
- package/templates/firebase/test/components/kasy_text_field_height_test.dart +77 -0
- package/templates/firebase/test/core/web_viewport_scale_test.dart +23 -16
- package/templates/firebase/tool/design_check.dart +9 -0
- package/templates/firebase/assets/icons/apple.png +0 -0
- package/templates/firebase/assets/icons/facebook.png +0 -0
- package/templates/firebase/assets/icons/google.png +0 -0
- package/templates/firebase/assets/icons/google_play_games.png +0 -0
- package/templates/firebase/lib/components/kasy_web_header.dart +0 -218
- package/templates/firebase/lib/core/chrome/web_header_scope.dart +0 -20
- package/templates/firebase/lib/features/authentication/ui/components/apple_signin.dart +0 -19
- package/templates/firebase/lib/features/authentication/ui/components/google_signin.dart +0 -32
- package/templates/firebase/lib/features/authentication/ui/widgets/round_signin.dart +0 -73
- package/templates/firebase/lib/features/feedbacks/ui/widgets/add_feature_button.dart +0 -66
- package/templates/firebase/lib/features/notifications/ui/components/notification_settings_sheet.dart +0 -179
- package/templates/firebase/lib/features/notifications/ui/components/push_notification_switcher.dart +0 -106
|
@@ -205,6 +205,12 @@ class _EmptyState extends StatelessWidget {
|
|
|
205
205
|
}
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
/// How much of the available row width a single bubble may occupy. Caps long
|
|
209
|
+
/// messages so they don't stretch edge-to-edge on wide layouts (desktop),
|
|
210
|
+
/// keeping the left/right chat rhythm like WhatsApp. Short messages still hug
|
|
211
|
+
/// their content via [Flexible].
|
|
212
|
+
const double _kMaxBubbleWidthFraction = 0.75;
|
|
213
|
+
|
|
208
214
|
class _ChatBubble extends StatelessWidget {
|
|
209
215
|
const _ChatBubble({required this.message, required this.isUser});
|
|
210
216
|
|
|
@@ -223,50 +229,61 @@ class _ChatBubble extends StatelessWidget {
|
|
|
223
229
|
? context.colors.primary
|
|
224
230
|
: context.colors.onBackground.withValues(alpha: 0.06),
|
|
225
231
|
borderRadius: BorderRadius.only(
|
|
226
|
-
topLeft: const Radius.circular(
|
|
227
|
-
topRight: const Radius.circular(
|
|
232
|
+
topLeft: const Radius.circular(KasyRadius.lg),
|
|
233
|
+
topRight: const Radius.circular(KasyRadius.lg),
|
|
228
234
|
bottomLeft: isUser
|
|
229
|
-
? const Radius.circular(
|
|
230
|
-
: const Radius.circular(
|
|
235
|
+
? const Radius.circular(KasyRadius.lg)
|
|
236
|
+
: const Radius.circular(KasyRadius.xs),
|
|
231
237
|
bottomRight: isUser
|
|
232
|
-
? const Radius.circular(
|
|
233
|
-
: const Radius.circular(
|
|
238
|
+
? const Radius.circular(KasyRadius.xs)
|
|
239
|
+
: const Radius.circular(KasyRadius.lg),
|
|
234
240
|
),
|
|
235
241
|
),
|
|
236
242
|
child: Text(
|
|
237
243
|
message.content,
|
|
238
|
-
style: context.textTheme.
|
|
244
|
+
style: context.textTheme.bodyLarge?.copyWith(
|
|
239
245
|
color: isUser ? context.colors.onPrimary : context.colors.onBackground,
|
|
240
246
|
height: 1.4,
|
|
241
247
|
),
|
|
242
248
|
),
|
|
243
249
|
);
|
|
244
250
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
const SizedBox(width: KasySpacing.xs),
|
|
254
|
-
const AiChatUserAvatar(),
|
|
255
|
-
],
|
|
256
|
-
),
|
|
257
|
-
);
|
|
258
|
-
}
|
|
251
|
+
return LayoutBuilder(
|
|
252
|
+
builder: (context, constraints) {
|
|
253
|
+
final Widget cappedBubble = ConstrainedBox(
|
|
254
|
+
constraints: BoxConstraints(
|
|
255
|
+
maxWidth: constraints.maxWidth * _kMaxBubbleWidthFraction,
|
|
256
|
+
),
|
|
257
|
+
child: bubble,
|
|
258
|
+
);
|
|
259
259
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
260
|
+
if (isUser) {
|
|
261
|
+
return Padding(
|
|
262
|
+
padding: const EdgeInsets.only(bottom: KasySpacing.sm),
|
|
263
|
+
child: Row(
|
|
264
|
+
mainAxisAlignment: MainAxisAlignment.end,
|
|
265
|
+
crossAxisAlignment: CrossAxisAlignment.end,
|
|
266
|
+
children: [
|
|
267
|
+
Flexible(child: cappedBubble),
|
|
268
|
+
const SizedBox(width: KasySpacing.xs),
|
|
269
|
+
const AiChatUserAvatar(),
|
|
270
|
+
],
|
|
271
|
+
),
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return Padding(
|
|
276
|
+
padding: const EdgeInsets.only(bottom: KasySpacing.sm),
|
|
277
|
+
child: Row(
|
|
278
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
279
|
+
children: [
|
|
280
|
+
const AiChatAssistantAvatar(),
|
|
281
|
+
const SizedBox(width: KasySpacing.xs),
|
|
282
|
+
Flexible(child: cappedBubble),
|
|
283
|
+
],
|
|
284
|
+
),
|
|
285
|
+
);
|
|
286
|
+
},
|
|
270
287
|
);
|
|
271
288
|
}
|
|
272
289
|
}
|
|
@@ -327,10 +344,10 @@ class _TypingIndicatorState extends State<_TypingIndicator>
|
|
|
327
344
|
decoration: BoxDecoration(
|
|
328
345
|
color: context.colors.onBackground.withValues(alpha: 0.06),
|
|
329
346
|
borderRadius: const BorderRadius.only(
|
|
330
|
-
topLeft: Radius.circular(
|
|
331
|
-
topRight: Radius.circular(
|
|
332
|
-
bottomLeft: Radius.circular(
|
|
333
|
-
bottomRight: Radius.circular(
|
|
347
|
+
topLeft: Radius.circular(KasyRadius.lg),
|
|
348
|
+
topRight: Radius.circular(KasyRadius.lg),
|
|
349
|
+
bottomLeft: Radius.circular(KasyRadius.xs),
|
|
350
|
+
bottomRight: Radius.circular(KasyRadius.lg),
|
|
334
351
|
),
|
|
335
352
|
),
|
|
336
353
|
child: Row(
|
|
@@ -126,7 +126,7 @@ class AiConversationList extends ConsumerWidget {
|
|
|
126
126
|
padding: const EdgeInsets.symmetric(horizontal: KasySpacing.lg),
|
|
127
127
|
decoration: BoxDecoration(
|
|
128
128
|
color: context.colors.surfaceErrorSoft,
|
|
129
|
-
borderRadius: BorderRadius.circular(
|
|
129
|
+
borderRadius: BorderRadius.circular(KasyRadius.lg),
|
|
130
130
|
),
|
|
131
131
|
child: Icon(KasyIcons.trash, color: context.colors.error),
|
|
132
132
|
);
|
|
@@ -57,7 +57,7 @@ class _AiConversationTileState extends ConsumerState<AiConversationTile> {
|
|
|
57
57
|
color: widget.selected
|
|
58
58
|
? context.colors.accentSoft
|
|
59
59
|
: (_hovered ? context.colors.surfaceNeutralSoft : null),
|
|
60
|
-
borderRadius: BorderRadius.circular(
|
|
60
|
+
borderRadius: BorderRadius.circular(KasyRadius.lg),
|
|
61
61
|
),
|
|
62
62
|
child: Row(
|
|
63
63
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
@@ -75,7 +75,7 @@ class _AiConversationTileState extends ConsumerState<AiConversationTile> {
|
|
|
75
75
|
title,
|
|
76
76
|
maxLines: 1,
|
|
77
77
|
overflow: TextOverflow.ellipsis,
|
|
78
|
-
style: context.kasyTextTheme.
|
|
78
|
+
style: context.kasyTextTheme.listRowTitle.copyWith(
|
|
79
79
|
color: titleColor,
|
|
80
80
|
),
|
|
81
81
|
),
|
|
@@ -89,7 +89,7 @@ class _AiConversationTileState extends ConsumerState<AiConversationTile> {
|
|
|
89
89
|
preview,
|
|
90
90
|
maxLines: 1,
|
|
91
91
|
overflow: TextOverflow.ellipsis,
|
|
92
|
-
style: context.textTheme.
|
|
92
|
+
style: context.textTheme.bodyMedium?.copyWith(
|
|
93
93
|
color: subtitleColor,
|
|
94
94
|
),
|
|
95
95
|
),
|
|
@@ -106,7 +106,7 @@ class _AiConversationTileState extends ConsumerState<AiConversationTile> {
|
|
|
106
106
|
label: title,
|
|
107
107
|
child: KasyFocusRing(
|
|
108
108
|
onActivate: widget.onTap,
|
|
109
|
-
borderRadius: BorderRadius.circular(
|
|
109
|
+
borderRadius: BorderRadius.circular(KasyRadius.lg),
|
|
110
110
|
child: GestureDetector(
|
|
111
111
|
behavior: HitTestBehavior.opaque,
|
|
112
112
|
onTap: widget.onTap,
|
|
@@ -129,10 +129,20 @@ class _AiConversationTileState extends ConsumerState<AiConversationTile> {
|
|
|
129
129
|
if (kIsWeb && _hovered) {
|
|
130
130
|
return SizedBox(
|
|
131
131
|
height: 16,
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
132
|
+
// Plain pointer-cursor tap (no Material ripple), consistent with the
|
|
133
|
+
// rest of the kit's web controls. The icon only appears while the row
|
|
134
|
+
// is hovered, so it's already a pointer-only affordance.
|
|
135
|
+
child: MouseRegion(
|
|
136
|
+
cursor: SystemMouseCursors.click,
|
|
137
|
+
child: GestureDetector(
|
|
138
|
+
behavior: HitTestBehavior.opaque,
|
|
139
|
+
onTap: widget.onDelete,
|
|
140
|
+
child: Icon(
|
|
141
|
+
KasyIcons.trash,
|
|
142
|
+
size: KasyIconSize.sm,
|
|
143
|
+
color: context.colors.error,
|
|
144
|
+
),
|
|
145
|
+
),
|
|
136
146
|
),
|
|
137
147
|
);
|
|
138
148
|
}
|
|
@@ -8,10 +8,8 @@ import 'package:kasy_kit/components/components.dart';
|
|
|
8
8
|
import 'package:kasy_kit/core/bottom_menu/web_url.dart';
|
|
9
9
|
import 'package:kasy_kit/core/config/features.dart';
|
|
10
10
|
import 'package:kasy_kit/core/data/models/user.dart';
|
|
11
|
-
import 'package:kasy_kit/core/haptics/kasy_haptics.dart';
|
|
12
11
|
import 'package:kasy_kit/core/states/user_state_notifier.dart';
|
|
13
12
|
import 'package:kasy_kit/core/theme/theme.dart';
|
|
14
|
-
import 'package:kasy_kit/core/widgets/kasy_focus_ring.dart';
|
|
15
13
|
import 'package:kasy_kit/core/widgets/kasy_pressable_depth.dart';
|
|
16
14
|
import 'package:kasy_kit/environments.dart';
|
|
17
15
|
import 'package:kasy_kit/features/authentication/providers/models/email.dart';
|
|
@@ -20,6 +18,7 @@ import 'package:kasy_kit/features/authentication/providers/models/signin_state.d
|
|
|
20
18
|
import 'package:kasy_kit/features/authentication/providers/signin_state_provider.dart';
|
|
21
19
|
import 'package:kasy_kit/features/authentication/ui/widgets/auth_card_scaffold.dart';
|
|
22
20
|
import 'package:kasy_kit/features/authentication/ui/widgets/auth_page_back_button.dart';
|
|
21
|
+
import 'package:kasy_kit/features/authentication/ui/widgets/social_auth_tile.dart';
|
|
23
22
|
import 'package:kasy_kit/features/authentication/ui/widgets/social_separator.dart';
|
|
24
23
|
import 'package:kasy_kit/i18n/translations.g.dart';
|
|
25
24
|
|
|
@@ -323,9 +322,9 @@ class _SocialSigninRow extends ConsumerWidget {
|
|
|
323
322
|
return Row(
|
|
324
323
|
children: [
|
|
325
324
|
Expanded(
|
|
326
|
-
child:
|
|
325
|
+
child: SocialAuthButton(
|
|
327
326
|
label: t.auth.signin.google,
|
|
328
|
-
|
|
327
|
+
iconAsset: 'assets/icons/google.svg',
|
|
329
328
|
onPressed: isSending
|
|
330
329
|
? null
|
|
331
330
|
: () =>
|
|
@@ -335,9 +334,13 @@ class _SocialSigninRow extends ConsumerWidget {
|
|
|
335
334
|
if (showApple) ...[
|
|
336
335
|
const SizedBox(width: KasySpacing.sm),
|
|
337
336
|
Expanded(
|
|
338
|
-
child:
|
|
337
|
+
child: SocialAuthButton(
|
|
339
338
|
label: t.auth.signin.apple,
|
|
340
|
-
|
|
339
|
+
// Apple's mark is officially black-on-light / white-on-dark, so
|
|
340
|
+
// pick the variant that stays visible on the current theme.
|
|
341
|
+
iconAsset: context.isDark
|
|
342
|
+
? 'assets/icons/apple_white.svg'
|
|
343
|
+
: 'assets/icons/apple_black.svg',
|
|
341
344
|
onPressed: isSending
|
|
342
345
|
? null
|
|
343
346
|
: () => ref
|
|
@@ -349,13 +352,9 @@ class _SocialSigninRow extends ConsumerWidget {
|
|
|
349
352
|
if (showFacebook) ...[
|
|
350
353
|
const SizedBox(width: KasySpacing.sm),
|
|
351
354
|
Expanded(
|
|
352
|
-
child:
|
|
355
|
+
child: SocialAuthButton(
|
|
353
356
|
label: t.auth.signin.facebook,
|
|
354
|
-
|
|
355
|
-
'assets/icons/facebook.png',
|
|
356
|
-
width: 20,
|
|
357
|
-
height: 20,
|
|
358
|
-
),
|
|
357
|
+
iconAsset: 'assets/icons/facebook.svg',
|
|
359
358
|
onPressed: isSending
|
|
360
359
|
? null
|
|
361
360
|
: () => ref
|
|
@@ -369,52 +368,3 @@ class _SocialSigninRow extends ConsumerWidget {
|
|
|
369
368
|
}
|
|
370
369
|
}
|
|
371
370
|
|
|
372
|
-
class _SocialSigninTile extends StatelessWidget {
|
|
373
|
-
const _SocialSigninTile({
|
|
374
|
-
required this.label,
|
|
375
|
-
required this.icon,
|
|
376
|
-
required this.onPressed,
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
final String label;
|
|
380
|
-
final Widget icon;
|
|
381
|
-
final VoidCallback? onPressed;
|
|
382
|
-
|
|
383
|
-
@override
|
|
384
|
-
Widget build(BuildContext context) {
|
|
385
|
-
final bool enabled = onPressed != null;
|
|
386
|
-
void handleTap() {
|
|
387
|
-
KasyHaptics.medium(context);
|
|
388
|
-
onPressed?.call();
|
|
389
|
-
}
|
|
390
|
-
return KasyFocusRing(
|
|
391
|
-
enabled: enabled,
|
|
392
|
-
onActivate: handleTap,
|
|
393
|
-
borderRadius: BorderRadius.circular(KasyRadius.sm),
|
|
394
|
-
child: Material(
|
|
395
|
-
color: Colors.transparent,
|
|
396
|
-
child: InkWell(
|
|
397
|
-
// Focus + keyboard activation live in KasyFocusRing; the InkWell keeps
|
|
398
|
-
// its tap ripple but doesn't take focus, so the ring is the only Tab
|
|
399
|
-
// stop and matches every other button's focus outline.
|
|
400
|
-
canRequestFocus: false,
|
|
401
|
-
onTap: enabled ? handleTap : null,
|
|
402
|
-
borderRadius: BorderRadius.circular(KasyRadius.md),
|
|
403
|
-
child: Ink(
|
|
404
|
-
height: 44,
|
|
405
|
-
decoration: BoxDecoration(
|
|
406
|
-
color: context.colors.surface,
|
|
407
|
-
borderRadius: BorderRadius.circular(KasyRadius.sm),
|
|
408
|
-
border: Border.all(
|
|
409
|
-
color: context.colors.outline.withValues(alpha: 0.38),
|
|
410
|
-
),
|
|
411
|
-
),
|
|
412
|
-
child: Center(
|
|
413
|
-
child: Semantics(button: true, label: label, child: icon),
|
|
414
|
-
),
|
|
415
|
-
),
|
|
416
|
-
),
|
|
417
|
-
),
|
|
418
|
-
);
|
|
419
|
-
}
|
|
420
|
-
}
|
|
@@ -7,10 +7,8 @@ import 'package:go_router/go_router.dart';
|
|
|
7
7
|
import 'package:kasy_kit/components/components.dart';
|
|
8
8
|
import 'package:kasy_kit/core/config/features.dart';
|
|
9
9
|
import 'package:kasy_kit/core/data/models/user.dart';
|
|
10
|
-
import 'package:kasy_kit/core/haptics/kasy_haptics.dart';
|
|
11
10
|
import 'package:kasy_kit/core/states/user_state_notifier.dart';
|
|
12
11
|
import 'package:kasy_kit/core/theme/theme.dart';
|
|
13
|
-
import 'package:kasy_kit/core/widgets/kasy_focus_ring.dart';
|
|
14
12
|
import 'package:kasy_kit/core/widgets/kasy_pressable_depth.dart';
|
|
15
13
|
import 'package:kasy_kit/features/authentication/providers/models/email.dart';
|
|
16
14
|
import 'package:kasy_kit/features/authentication/providers/models/password.dart';
|
|
@@ -20,6 +18,7 @@ import 'package:kasy_kit/features/authentication/providers/signin_state_provider
|
|
|
20
18
|
import 'package:kasy_kit/features/authentication/providers/signup_state_provider.dart';
|
|
21
19
|
import 'package:kasy_kit/features/authentication/ui/widgets/auth_card_scaffold.dart';
|
|
22
20
|
import 'package:kasy_kit/features/authentication/ui/widgets/auth_page_back_button.dart';
|
|
21
|
+
import 'package:kasy_kit/features/authentication/ui/widgets/social_auth_tile.dart';
|
|
23
22
|
import 'package:kasy_kit/features/authentication/ui/widgets/social_separator.dart';
|
|
24
23
|
import 'package:kasy_kit/i18n/translations.g.dart';
|
|
25
24
|
|
|
@@ -223,9 +222,9 @@ class _SocialSignupRow extends ConsumerWidget {
|
|
|
223
222
|
return Row(
|
|
224
223
|
children: [
|
|
225
224
|
Expanded(
|
|
226
|
-
child:
|
|
225
|
+
child: SocialAuthButton(
|
|
227
226
|
label: t.auth.signin.google,
|
|
228
|
-
|
|
227
|
+
iconAsset: 'assets/icons/google.svg',
|
|
229
228
|
onPressed: isSending
|
|
230
229
|
? null
|
|
231
230
|
: () =>
|
|
@@ -235,9 +234,13 @@ class _SocialSignupRow extends ConsumerWidget {
|
|
|
235
234
|
if (showApple) ...[
|
|
236
235
|
const SizedBox(width: KasySpacing.sm),
|
|
237
236
|
Expanded(
|
|
238
|
-
child:
|
|
237
|
+
child: SocialAuthButton(
|
|
239
238
|
label: t.auth.signin.apple,
|
|
240
|
-
|
|
239
|
+
// Apple's mark is officially black-on-light / white-on-dark, so
|
|
240
|
+
// pick the variant that stays visible on the current theme.
|
|
241
|
+
iconAsset: context.isDark
|
|
242
|
+
? 'assets/icons/apple_white.svg'
|
|
243
|
+
: 'assets/icons/apple_black.svg',
|
|
241
244
|
onPressed: isSending
|
|
242
245
|
? null
|
|
243
246
|
: () => ref
|
|
@@ -249,13 +252,9 @@ class _SocialSignupRow extends ConsumerWidget {
|
|
|
249
252
|
if (showFacebook) ...[
|
|
250
253
|
const SizedBox(width: KasySpacing.sm),
|
|
251
254
|
Expanded(
|
|
252
|
-
child:
|
|
255
|
+
child: SocialAuthButton(
|
|
253
256
|
label: t.auth.signin.facebook,
|
|
254
|
-
|
|
255
|
-
'assets/icons/facebook.png',
|
|
256
|
-
width: 20,
|
|
257
|
-
height: 20,
|
|
258
|
-
),
|
|
257
|
+
iconAsset: 'assets/icons/facebook.svg',
|
|
259
258
|
onPressed: isSending
|
|
260
259
|
? null
|
|
261
260
|
: () => ref
|
|
@@ -269,52 +268,3 @@ class _SocialSignupRow extends ConsumerWidget {
|
|
|
269
268
|
}
|
|
270
269
|
}
|
|
271
270
|
|
|
272
|
-
class _SocialSignupTile extends StatelessWidget {
|
|
273
|
-
const _SocialSignupTile({
|
|
274
|
-
required this.label,
|
|
275
|
-
required this.icon,
|
|
276
|
-
required this.onPressed,
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
final String label;
|
|
280
|
-
final Widget icon;
|
|
281
|
-
final VoidCallback? onPressed;
|
|
282
|
-
|
|
283
|
-
@override
|
|
284
|
-
Widget build(BuildContext context) {
|
|
285
|
-
final bool enabled = onPressed != null;
|
|
286
|
-
void handleTap() {
|
|
287
|
-
KasyHaptics.medium(context);
|
|
288
|
-
onPressed?.call();
|
|
289
|
-
}
|
|
290
|
-
return KasyFocusRing(
|
|
291
|
-
enabled: enabled,
|
|
292
|
-
onActivate: handleTap,
|
|
293
|
-
borderRadius: BorderRadius.circular(KasyRadius.sm),
|
|
294
|
-
child: Material(
|
|
295
|
-
color: Colors.transparent,
|
|
296
|
-
child: InkWell(
|
|
297
|
-
// Focus + keyboard activation live in KasyFocusRing; the InkWell keeps
|
|
298
|
-
// its tap ripple but doesn't take focus, so the ring is the only Tab
|
|
299
|
-
// stop and matches every other button's focus outline.
|
|
300
|
-
canRequestFocus: false,
|
|
301
|
-
onTap: enabled ? handleTap : null,
|
|
302
|
-
borderRadius: BorderRadius.circular(KasyRadius.md),
|
|
303
|
-
child: Ink(
|
|
304
|
-
height: 44,
|
|
305
|
-
decoration: BoxDecoration(
|
|
306
|
-
color: context.colors.surface,
|
|
307
|
-
borderRadius: BorderRadius.circular(KasyRadius.sm),
|
|
308
|
-
border: Border.all(
|
|
309
|
-
color: context.colors.outline.withValues(alpha: 0.38),
|
|
310
|
-
),
|
|
311
|
-
),
|
|
312
|
-
child: Center(
|
|
313
|
-
child: Semantics(button: true, label: label, child: icon),
|
|
314
|
-
),
|
|
315
|
-
),
|
|
316
|
-
),
|
|
317
|
-
),
|
|
318
|
-
);
|
|
319
|
-
}
|
|
320
|
-
}
|
|
@@ -25,7 +25,7 @@ class AuthCardScaffold extends StatelessWidget {
|
|
|
25
25
|
required this.subtitle,
|
|
26
26
|
required this.children,
|
|
27
27
|
this.showLogo = true,
|
|
28
|
-
this.logoHeight =
|
|
28
|
+
this.logoHeight = 132,
|
|
29
29
|
this.maxContentWidth = 420,
|
|
30
30
|
});
|
|
31
31
|
|
|
@@ -74,7 +74,7 @@ class AuthCardScaffold extends StatelessWidget {
|
|
|
74
74
|
curve: Curves.easeOut,
|
|
75
75
|
),
|
|
76
76
|
),
|
|
77
|
-
const SizedBox(height: KasySpacing.
|
|
77
|
+
const SizedBox(height: KasySpacing.sm),
|
|
78
78
|
],
|
|
79
79
|
Text(
|
|
80
80
|
title,
|
|
@@ -111,9 +111,11 @@ class AuthCardScaffold extends StatelessWidget {
|
|
|
111
111
|
child: isMobile
|
|
112
112
|
? content
|
|
113
113
|
: KasyCard(
|
|
114
|
-
padding: const EdgeInsets.
|
|
115
|
-
|
|
116
|
-
|
|
114
|
+
padding: const EdgeInsets.fromLTRB(
|
|
115
|
+
KasySpacing.lg,
|
|
116
|
+
KasySpacing.md,
|
|
117
|
+
KasySpacing.lg,
|
|
118
|
+
KasySpacing.xl,
|
|
117
119
|
),
|
|
118
120
|
child: content,
|
|
119
121
|
),
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import 'package:flutter/material.dart';
|
|
2
|
+
import 'package:flutter_svg/flutter_svg.dart';
|
|
3
|
+
import 'package:kasy_kit/core/haptics/kasy_haptics.dart';
|
|
4
|
+
import 'package:kasy_kit/core/theme/theme.dart';
|
|
5
|
+
import 'package:kasy_kit/core/widgets/kasy_hover.dart';
|
|
6
|
+
|
|
7
|
+
/// Outlined social sign-in button (Google / Apple / Facebook), shared by the
|
|
8
|
+
/// sign-in and sign-up screens.
|
|
9
|
+
///
|
|
10
|
+
/// Each tile fills its slot in the social row (the parent wraps it in an
|
|
11
|
+
/// [Expanded]) and centers the provider glyph. It uses [KasyHover] so it has a
|
|
12
|
+
/// real hover highlight on web/desktop and a press fill on every platform — no
|
|
13
|
+
/// Material ripple, matching the rest of the kit's interactive controls. While a
|
|
14
|
+
/// request is in flight ([onPressed] null) it dims and stops responding to
|
|
15
|
+
/// pointer and keyboard.
|
|
16
|
+
class SocialAuthButton extends StatelessWidget {
|
|
17
|
+
const SocialAuthButton({
|
|
18
|
+
super.key,
|
|
19
|
+
required this.label,
|
|
20
|
+
required this.iconAsset,
|
|
21
|
+
required this.onPressed,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
/// Accessibility label (the provider name) announced to screen readers.
|
|
25
|
+
final String label;
|
|
26
|
+
|
|
27
|
+
/// Path to the provider's official brand SVG bundled under `assets/icons/`
|
|
28
|
+
/// (e.g. `assets/icons/google.svg`). Rendered at full brand colour and at a
|
|
29
|
+
/// shared [KasyIconSize.lg] box so every provider stays the same size. For
|
|
30
|
+
/// Apple, the caller picks the black/white variant for the current theme.
|
|
31
|
+
final String iconAsset;
|
|
32
|
+
|
|
33
|
+
/// Tap handler. When null the button is disabled (dimmed, inert).
|
|
34
|
+
final VoidCallback? onPressed;
|
|
35
|
+
|
|
36
|
+
@override
|
|
37
|
+
Widget build(BuildContext context) {
|
|
38
|
+
final bool enabled = onPressed != null;
|
|
39
|
+
final BorderRadius radius = BorderRadius.circular(KasyRadius.sm);
|
|
40
|
+
|
|
41
|
+
// No background fill: the KasyHover overlay sits behind the transparent
|
|
42
|
+
// interior to provide the hover/press tint, and on the surface auth card a
|
|
43
|
+
// fill would be invisible anyway. The hairline border gives the outlined
|
|
44
|
+
// shape, consistent with KasyButton's outlined variant.
|
|
45
|
+
final Widget surface = Container(
|
|
46
|
+
height: 44,
|
|
47
|
+
decoration: BoxDecoration(
|
|
48
|
+
borderRadius: radius,
|
|
49
|
+
border: Border.all(
|
|
50
|
+
color: context.colors.outline.withValues(alpha: 0.38),
|
|
51
|
+
),
|
|
52
|
+
),
|
|
53
|
+
child: Center(
|
|
54
|
+
// Brand logos have different aspect ratios (Apple is tall, Google is
|
|
55
|
+
// square); SvgPicture defaults to BoxFit.contain, which keeps each one
|
|
56
|
+
// inside the shared box without distortion so the row stays balanced.
|
|
57
|
+
child: SvgPicture.asset(
|
|
58
|
+
iconAsset,
|
|
59
|
+
width: KasyIconSize.lg,
|
|
60
|
+
height: KasyIconSize.lg,
|
|
61
|
+
),
|
|
62
|
+
),
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
if (!enabled) {
|
|
66
|
+
return Opacity(opacity: 0.5, child: surface);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return KasyHover(
|
|
70
|
+
onTap: () {
|
|
71
|
+
KasyHaptics.medium(context);
|
|
72
|
+
onPressed!();
|
|
73
|
+
},
|
|
74
|
+
// KasyHover fires a light selection haptic by default; a social sign-in is
|
|
75
|
+
// a significant action, so opt out and fire the heavier "medium" above.
|
|
76
|
+
hapticEnabled: false,
|
|
77
|
+
focusable: true,
|
|
78
|
+
borderRadius: radius,
|
|
79
|
+
semanticLabel: label,
|
|
80
|
+
child: surface,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -59,15 +59,15 @@ class _FeatureCardState extends State<FeatureCard> {
|
|
|
59
59
|
children: [
|
|
60
60
|
Text(
|
|
61
61
|
widget.title,
|
|
62
|
-
style: context.
|
|
63
|
-
|
|
62
|
+
style: context.kasyTextTheme.listRowTitle.copyWith(
|
|
63
|
+
color: context.colors.onSurface,
|
|
64
|
+
fontWeight: FontWeight.w600,
|
|
64
65
|
),
|
|
65
66
|
),
|
|
66
67
|
const SizedBox(height: 2),
|
|
67
68
|
Text(
|
|
68
69
|
widget.description,
|
|
69
|
-
style: context.textTheme.
|
|
70
|
-
fontWeight: FontWeight.w400,
|
|
70
|
+
style: context.textTheme.bodyMedium?.copyWith(
|
|
71
71
|
color: context.colors.muted,
|
|
72
72
|
),
|
|
73
73
|
),
|