kasy-cli 1.37.1 → 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.
Files changed (120) hide show
  1. package/lib/scaffold/CHANGELOG.json +23 -0
  2. package/lib/scaffold/backends/api/patch/README.md +15 -0
  3. package/lib/scaffold/backends/api/patch/lib/core/data/api/user_api.dart +18 -0
  4. package/lib/scaffold/backends/api/patch/lib/features/notifications/api/device_api.dart +11 -6
  5. package/lib/scaffold/backends/api/patch/lib/features/settings/ui/components/admin/admin_users_api.dart +9 -1
  6. package/lib/scaffold/backends/api/pubspec.yaml.tpl +1 -0
  7. package/lib/scaffold/backends/patch-base-hashes.json +6 -6
  8. package/lib/scaffold/backends/supabase/edge-functions/admin-list-users/index.ts +3 -1
  9. package/lib/scaffold/backends/supabase/edge-functions/send-push-notification/index.ts +3 -0
  10. package/lib/scaffold/backends/supabase/migrations/20240101000012_welcome_decouple_from_push.sql +62 -0
  11. package/lib/scaffold/backends/supabase/patch/lib/core/data/api/user_api.dart +8 -0
  12. package/lib/scaffold/backends/supabase/patch/lib/features/notifications/api/device_api.dart +11 -6
  13. package/lib/scaffold/backends/supabase/patch/lib/features/settings/ui/components/admin/admin_users_api.dart +7 -0
  14. package/lib/scaffold/backends/supabase/pubspec.yaml.tpl +1 -0
  15. package/lib/scaffold/shared/generator-utils.js +12 -6
  16. package/package.json +1 -1
  17. package/templates/firebase/.firebase/hosting.YnVpbGQvd2Vi.cache +23 -23
  18. package/templates/firebase/AGENTS.md +7 -1
  19. package/templates/firebase/DESIGN_SYSTEM.md +35 -8
  20. package/templates/firebase/assets/icons/apple_black.svg +3 -0
  21. package/templates/firebase/assets/icons/apple_white.svg +4 -0
  22. package/templates/firebase/assets/icons/facebook.svg +49 -0
  23. package/templates/firebase/assets/icons/google.svg +1 -0
  24. package/templates/firebase/functions/src/admin/functions.ts +2 -0
  25. package/templates/firebase/functions/src/authentication/functions.ts +13 -7
  26. package/templates/firebase/functions/src/notifications/triggers.ts +6 -2
  27. package/templates/firebase/lib/components/components.dart +1 -1
  28. package/templates/firebase/lib/components/kasy_app_bar.dart +361 -20
  29. package/templates/firebase/lib/components/kasy_bottom_sheet.dart +283 -66
  30. package/templates/firebase/lib/components/kasy_card.dart +4 -0
  31. package/templates/firebase/lib/components/kasy_date_picker.dart +61 -46
  32. package/templates/firebase/lib/components/kasy_drop_down.dart +584 -0
  33. package/templates/firebase/lib/components/kasy_sidebar.dart +412 -31
  34. package/templates/firebase/lib/components/kasy_tabs.dart +31 -10
  35. package/templates/firebase/lib/components/kasy_text_field.dart +29 -7
  36. package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +29 -231
  37. package/templates/firebase/lib/core/bottom_menu/sidebar_focus.dart +224 -0
  38. package/templates/firebase/lib/core/bottom_menu/web_content_wrapper.dart +19 -9
  39. package/templates/firebase/lib/core/chrome/app_bar_config.dart +214 -0
  40. package/templates/firebase/lib/core/chrome/app_bar_scope.dart +102 -0
  41. package/templates/firebase/lib/core/data/api/user_api.dart +15 -0
  42. package/templates/firebase/lib/core/data/repositories/user_repository.dart +5 -0
  43. package/templates/firebase/lib/core/dev_inspector/dev_inspector.dart +525 -65
  44. package/templates/firebase/lib/core/dev_inspector/dev_inspector_info.dart +47 -0
  45. package/templates/firebase/lib/core/dev_inspector/dev_inspector_service.dart +55 -15
  46. package/templates/firebase/lib/core/icons/kasy_icons.dart +16 -1
  47. package/templates/firebase/lib/core/rating/widgets/review_popup.dart +18 -35
  48. package/templates/firebase/lib/core/shared_preferences/shared_preferences.dart +11 -0
  49. package/templates/firebase/lib/core/states/logout_action.dart +11 -1
  50. package/templates/firebase/lib/core/states/user_state_notifier.dart +69 -1
  51. package/templates/firebase/lib/core/theme/texts.dart +21 -6
  52. package/templates/firebase/lib/core/theme/type_scale.dart +34 -15
  53. package/templates/firebase/lib/core/theme/universal_theme.dart +9 -0
  54. package/templates/firebase/lib/core/web_device_preview/png_clipboard.dart +14 -0
  55. package/templates/firebase/lib/core/web_device_preview/png_clipboard_io.dart +9 -0
  56. package/templates/firebase/lib/core/web_device_preview/png_clipboard_web.dart +36 -0
  57. package/templates/firebase/lib/core/web_device_preview/png_export_result.dart +2 -0
  58. package/templates/firebase/lib/core/web_device_preview/web_device_preview.dart +547 -483
  59. package/templates/firebase/lib/core/web_viewport_scale.dart +64 -35
  60. package/templates/firebase/lib/core/widgets/kasy_pressable_depth.dart +14 -3
  61. package/templates/firebase/lib/core/widgets/responsive_layout.dart +8 -0
  62. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_chat_composer.dart +1 -1
  63. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_chat_conversation_view.dart +52 -35
  64. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_list.dart +1 -1
  65. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_tile.dart +18 -8
  66. package/templates/firebase/lib/features/authentication/ui/signin_page.dart +11 -61
  67. package/templates/firebase/lib/features/authentication/ui/signup_page.dart +11 -61
  68. package/templates/firebase/lib/features/authentication/ui/widgets/auth_card_scaffold.dart +7 -5
  69. package/templates/firebase/lib/features/authentication/ui/widgets/social_auth_tile.dart +83 -0
  70. package/templates/firebase/lib/features/feedbacks/ui/widgets/feature_card.dart +4 -4
  71. package/templates/firebase/lib/features/home/home_components_page.dart +264 -126
  72. package/templates/firebase/lib/features/home/home_components_preview_registry.dart +231 -57
  73. package/templates/firebase/lib/features/home/home_feed.dart +2 -2
  74. package/templates/firebase/lib/features/home/home_image_grid.dart +3 -3
  75. package/templates/firebase/lib/features/local_reminders/providers/reminder_notifier.dart +8 -1
  76. package/templates/firebase/lib/features/local_reminders/ui/reminder_page.dart +118 -57
  77. package/templates/firebase/lib/features/notifications/api/device_api.dart +11 -3
  78. package/templates/firebase/lib/features/notifications/ui/notifications_page.dart +19 -4
  79. package/templates/firebase/lib/features/notifications/ui/widgets/empty_notifications.dart +7 -56
  80. package/templates/firebase/lib/features/notifications/ui/widgets/notification_tile.dart +5 -5
  81. package/templates/firebase/lib/features/notifications/ui/widgets/push_permission_banner.dart +163 -0
  82. package/templates/firebase/lib/features/notifications/ui/widgets/web_notifications_bell.dart +262 -0
  83. package/templates/firebase/lib/features/onboarding/providers/onboarding_model.dart +9 -0
  84. package/templates/firebase/lib/features/onboarding/providers/onboarding_provider.dart +28 -0
  85. package/templates/firebase/lib/features/onboarding/ui/onboarding_page.dart +51 -12
  86. package/templates/firebase/lib/features/onboarding/ui/widgets/selectable_row_tile.dart +90 -32
  87. package/templates/firebase/lib/features/settings/settings_page.dart +99 -65
  88. package/templates/firebase/lib/features/settings/ui/components/admin/admin_page.dart +1379 -422
  89. package/templates/firebase/lib/features/settings/ui/components/admin/admin_routes.dart +14 -4
  90. package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_api.dart +7 -0
  91. package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_tab.dart +445 -233
  92. package/templates/firebase/lib/features/settings/ui/components/admin/send_push_notification_page.dart +404 -149
  93. package/templates/firebase/lib/features/settings/ui/components/avatar_component.dart +24 -31
  94. package/templates/firebase/lib/features/settings/ui/components/delete_user_component.dart +9 -1
  95. package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +77 -95
  96. package/templates/firebase/lib/features/settings/ui/widgets/settings_bottom_sheet_option_tile.dart +21 -18
  97. package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +25 -10
  98. package/templates/firebase/lib/i18n/en.i18n.json +749 -698
  99. package/templates/firebase/lib/i18n/es.i18n.json +749 -698
  100. package/templates/firebase/lib/i18n/pt.i18n.json +749 -698
  101. package/templates/firebase/lib/main.dart +20 -7
  102. package/templates/firebase/lib/router.dart +70 -46
  103. package/templates/firebase/pubspec.yaml +2 -1
  104. package/templates/firebase/test/admin_shell_chrome_test.dart +110 -0
  105. package/templates/firebase/test/components/kasy_text_field_height_test.dart +77 -0
  106. package/templates/firebase/test/core/web_viewport_scale_test.dart +23 -16
  107. package/templates/firebase/test/features/authentication/data/api/user_api_fake.dart +3 -0
  108. package/templates/firebase/tool/design_check.dart +9 -0
  109. package/templates/firebase/assets/icons/apple.png +0 -0
  110. package/templates/firebase/assets/icons/facebook.png +0 -0
  111. package/templates/firebase/assets/icons/google.png +0 -0
  112. package/templates/firebase/assets/icons/google_play_games.png +0 -0
  113. package/templates/firebase/lib/components/kasy_web_header.dart +0 -210
  114. package/templates/firebase/lib/features/authentication/ui/components/apple_signin.dart +0 -19
  115. package/templates/firebase/lib/features/authentication/ui/components/google_signin.dart +0 -32
  116. package/templates/firebase/lib/features/authentication/ui/widgets/round_signin.dart +0 -73
  117. package/templates/firebase/lib/features/feedbacks/ui/widgets/add_feature_button.dart +0 -66
  118. package/templates/firebase/lib/features/notifications/ui/components/notification_settings_sheet.dart +0 -169
  119. package/templates/firebase/lib/features/notifications/ui/components/push_notification_switcher.dart +0 -106
  120. package/templates/firebase/lib/features/settings/ui/components/admin/admin_paywalls.dart +0 -53
@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
5
5
  import 'package:flutter_riverpod/flutter_riverpod.dart';
6
6
  import 'package:image_picker/image_picker.dart';
7
7
  import 'package:kasy_kit/components/kasy_avatar.dart';
8
+ import 'package:kasy_kit/components/kasy_bottom_sheet.dart';
8
9
  import 'package:kasy_kit/core/data/entities/upload_result.dart';
9
10
  import 'package:kasy_kit/core/data/models/user.dart';
10
11
  import 'package:kasy_kit/core/data/repositories/user_repository.dart';
@@ -12,6 +13,7 @@ import 'package:kasy_kit/core/states/models/user_state.dart';
12
13
  import 'package:kasy_kit/core/states/user_state_notifier.dart';
13
14
  import 'package:kasy_kit/core/theme/theme.dart';
14
15
  import 'package:kasy_kit/core/widgets/kasy_focus_ring.dart';
16
+ import 'package:kasy_kit/core/widgets/kasy_hover.dart';
15
17
  import 'package:kasy_kit/features/settings/ui/widgets/avatar_utils.dart';
16
18
  import 'package:kasy_kit/features/settings/ui/widgets/round_progress.dart';
17
19
  import 'package:kasy_kit/i18n/translations.g.dart';
@@ -65,7 +67,7 @@ class _EditableUserAvatarState extends ConsumerState<EditableUserAvatar> {
65
67
  return KasyFocusRing(
66
68
  enabled: onTapAvatar != null,
67
69
  onActivate: onTapAvatar,
68
- borderRadius: BorderRadius.circular(999),
70
+ borderRadius: BorderRadius.circular(KasyRadius.full),
69
71
  child: GestureDetector(
70
72
  behavior: HitTestBehavior.opaque,
71
73
  onTap: onTapAvatar,
@@ -144,14 +146,10 @@ class _EditableUserAvatarState extends ConsumerState<EditableUserAvatar> {
144
146
  BuildContext context, {
145
147
  required bool hasAvatar,
146
148
  }) {
147
- return showModalBottomSheet<_AvatarAction>(
149
+ return showKasyBottomSheet<_AvatarAction>(
148
150
  context: context,
149
- useRootNavigator: true,
150
- backgroundColor: context.colors.surface,
151
- shape: const RoundedRectangleBorder(
152
- borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
153
- ),
154
- builder: (sheetContext) => SafeArea(
151
+ builder: (sheetContext) => KasySheetSurface(
152
+ showDragHandle: false,
155
153
  child: Padding(
156
154
  padding: const EdgeInsets.symmetric(vertical: KasySpacing.sm),
157
155
  child: Column(
@@ -321,31 +319,26 @@ class _BottomSheetTile extends StatelessWidget {
321
319
  @override
322
320
  Widget build(BuildContext context) {
323
321
  final Color fg = color ?? context.colors.onSurface;
324
- return KasyFocusRing(
325
- onActivate: onTap,
322
+ return KasyHover(
323
+ onTap: onTap,
324
+ focusable: true,
325
+ focusGapColor: context.colors.surface,
326
326
  borderRadius: BorderRadius.circular(KasyRadius.sm),
327
- gapColor: context.colors.surface,
328
- child: InkWell(
329
- canRequestFocus: false,
330
- onTap: onTap,
331
- child: Padding(
332
- padding: const EdgeInsets.symmetric(
333
- horizontal: KasySpacing.md,
334
- vertical: KasySpacing.smd,
335
- ),
336
- child: Row(
337
- children: [
338
- if (icon != null) ...[
339
- Icon(icon, size: KasyIconSize.lg, color: fg),
340
- const SizedBox(width: KasySpacing.sm),
341
- ],
342
- Text(
343
- label,
344
- style: context.textTheme.bodyLarge?.copyWith(color: fg),
345
- ),
346
- ],
327
+ padding: const EdgeInsets.symmetric(
328
+ horizontal: KasySpacing.md,
329
+ vertical: KasySpacing.smd,
330
+ ),
331
+ child: Row(
332
+ children: [
333
+ if (icon != null) ...[
334
+ Icon(icon, size: KasyIconSize.lg, color: fg),
335
+ const SizedBox(width: KasySpacing.sm),
336
+ ],
337
+ Text(
338
+ label,
339
+ style: context.textTheme.bodyLarge?.copyWith(color: fg),
347
340
  ),
348
- ),
341
+ ],
349
342
  ),
350
343
  );
351
344
  }
@@ -4,6 +4,7 @@ import 'package:kasy_kit/components/components.dart';
4
4
  import 'package:kasy_kit/core/states/user_state_notifier.dart';
5
5
  import 'package:kasy_kit/core/theme/theme.dart';
6
6
  import 'package:kasy_kit/i18n/translations.g.dart';
7
+ import 'package:kasy_kit/router.dart';
7
8
 
8
9
  class DeleteUserButton extends ConsumerWidget {
9
10
  const DeleteUserButton({super.key});
@@ -40,7 +41,14 @@ class DeleteUserButton extends ConsumerWidget {
40
41
  }
41
42
  }
42
43
  },
43
- );
44
+ ).whenComplete(() {
45
+ // Same pageless-dialog issue as logout: on success deleteAccount flips
46
+ // the state to anonymous while this confirm dialog still sits on the
47
+ // root navigator, so the redirect to /signin can't land. Re-run it
48
+ // once the dialog has popped. Harmless when the user cancels or the
49
+ // deletion failed (still authenticated → redirect keeps them here).
50
+ if (context.mounted) ref.read(goRouterProvider).refresh();
51
+ });
44
52
  },
45
53
  );
46
54
  }
@@ -2,10 +2,11 @@ import 'dart:async';
2
2
 
3
3
  import 'package:flutter/material.dart';
4
4
  import 'package:flutter_riverpod/flutter_riverpod.dart';
5
+ import 'package:kasy_kit/components/kasy_bottom_sheet.dart';
5
6
  import 'package:kasy_kit/core/home_widgets/home_widget_mywidget_service.dart';
6
7
  import 'package:kasy_kit/core/shared_preferences/shared_preferences.dart';
7
8
  import 'package:kasy_kit/core/theme/theme.dart';
8
- import 'package:kasy_kit/core/widgets/kasy_focus_ring.dart';
9
+ import 'package:kasy_kit/core/widgets/kasy_hover.dart';
9
10
  import 'package:kasy_kit/features/settings/ui/widgets/settings_tile.dart';
10
11
  import 'package:kasy_kit/i18n/app_locale_display.dart';
11
12
  import 'package:kasy_kit/i18n/translations.g.dart';
@@ -18,41 +19,41 @@ class LanguageSwitcher extends ConsumerWidget {
18
19
  @override
19
20
  Widget build(BuildContext context, WidgetRef ref) {
20
21
  final AppLocale current = TranslationProvider.of(context).locale;
21
- return KasyFocusRing(
22
- onActivate: () => _showLanguagePicker(context, ref, current),
23
- borderRadius: KasyRadius.smBorderRadius,
24
- child: InkWell(
25
- canRequestFocus: false,
26
- onTap: () => _showLanguagePicker(context, ref, current),
27
- child: Padding(
28
- padding: const EdgeInsets.symmetric(vertical: KasySpacing.sm),
29
- child: Row(
30
- children: <Widget>[
31
- Icon(
32
- KasyIcons.language,
33
- size: KasyIconSize.rowLeading,
22
+ return KasyHover(
23
+ onTap: () => _showLanguagePicker(context, ref, current),
24
+ focusable: true,
25
+ // Rectangular highlight (KasyHover default): this row lives inside a card,
26
+ // whose rounded corners clip the ends — a rounded fill here would float.
27
+ semanticLabel: context.t.settings.language_title,
28
+ padding: const EdgeInsets.symmetric(
29
+ horizontal: KasySpacing.md,
30
+ vertical: KasySpacing.smd,
31
+ ),
32
+ child: Row(
33
+ children: <Widget>[
34
+ Icon(
35
+ KasyIcons.language,
36
+ size: KasyIconSize.rowLeading,
37
+ color: context.colors.onSurface,
38
+ ),
39
+ const SizedBox(width: KasySpacing.sm),
40
+ Expanded(
41
+ child: Text(
42
+ context.t.settings.language_title,
43
+ style: context.kasyTextTheme.listRowTitle.copyWith(
34
44
  color: context.colors.onSurface,
35
45
  ),
36
- const SizedBox(width: KasySpacing.sm),
37
- Expanded(
38
- child: Text(
39
- context.t.settings.language_title,
40
- style: context.textTheme.titleSmall?.copyWith(
41
- color: context.colors.onSurface,
42
- ),
43
- ),
44
- ),
45
- Text(
46
- current.nativeName,
47
- style: context.textTheme.bodyMedium?.copyWith(
48
- color: context.colors.muted,
49
- ),
50
- ),
51
- const SizedBox(width: KasySpacing.xs),
52
- const SettingsListChevron(),
53
- ],
46
+ ),
54
47
  ),
55
- ),
48
+ Text(
49
+ current.nativeName,
50
+ style: context.kasyTextTheme.listRowValue.copyWith(
51
+ color: context.colors.muted,
52
+ ),
53
+ ),
54
+ const SizedBox(width: KasySpacing.xs),
55
+ const SettingsListChevron(),
56
+ ],
56
57
  ),
57
58
  );
58
59
  }
@@ -63,39 +64,26 @@ class LanguageSwitcher extends ConsumerWidget {
63
64
  AppLocale current,
64
65
  ) {
65
66
  final String sheetTitle = context.t.settings.language_title;
66
- showModalBottomSheet<void>(
67
+ showKasyBottomSheet<void>(
67
68
  context: context,
68
- useRootNavigator: true,
69
- backgroundColor: context.colors.surface,
70
- shape: const RoundedRectangleBorder(
71
- borderRadius: BorderRadius.vertical(top: Radius.circular(KasyRadius.lg)),
72
- ),
73
- builder: (sheetContext) => SafeArea(
69
+ builder: (sheetContext) => KasySheetSurface(
74
70
  child: Padding(
75
- padding: const EdgeInsets.fromLTRB(
76
- KasySpacing.md,
77
- KasySpacing.sm,
78
- KasySpacing.md,
79
- KasySpacing.sm,
80
- ),
71
+ // No horizontal padding: option rows go full-bleed so the highlight
72
+ // spans the whole sheet (no inset pill). Title and rows carry their
73
+ // own horizontal inset instead.
74
+ padding: const EdgeInsets.symmetric(vertical: KasySpacing.sm),
81
75
  child: Column(
82
76
  mainAxisSize: MainAxisSize.min,
83
77
  children: <Widget>[
84
- Container(
85
- width: 36,
86
- height: 4,
87
- margin: const EdgeInsets.only(bottom: KasySpacing.md),
88
- decoration: BoxDecoration(
89
- color: sheetContext.colors.onSurface.withValues(alpha: 0.18),
90
- borderRadius: BorderRadius.circular(2),
91
- ),
92
- ),
93
- Align(
94
- alignment: Alignment.centerLeft,
95
- child: Text(
96
- sheetTitle,
97
- style: sheetContext.textTheme.titleMedium?.copyWith(
98
- color: sheetContext.colors.onSurface,
78
+ Padding(
79
+ padding: const EdgeInsets.symmetric(horizontal: KasySpacing.md),
80
+ child: Align(
81
+ alignment: Alignment.centerLeft,
82
+ child: Text(
83
+ sheetTitle,
84
+ style: sheetContext.textTheme.titleMedium?.copyWith(
85
+ color: sheetContext.colors.onSurface,
86
+ ),
99
87
  ),
100
88
  ),
101
89
  ),
@@ -156,42 +144,36 @@ class _LocaleOptionTile extends StatelessWidget {
156
144
  final Color primary = context.colors.primary;
157
145
  final Color fg = isSelected ? primary : context.colors.onSurface;
158
146
 
159
- return KasyFocusRing(
160
- onActivate: onTap,
161
- borderRadius: BorderRadius.circular(KasyRadius.sm),
162
- child: InkWell(
163
- canRequestFocus: false,
164
- onTap: onTap,
165
- borderRadius: BorderRadius.circular(KasyRadius.sm),
166
- child: Padding(
167
- padding: const EdgeInsets.symmetric(
168
- horizontal: KasySpacing.xs,
169
- vertical: KasySpacing.smd,
170
- ),
171
- child: Row(
172
- children: <Widget>[
173
- Expanded(
174
- child: Text(
175
- locale.nativeName,
176
- style: context.textTheme.bodyLarge?.copyWith(
177
- color: fg,
178
- fontWeight:
179
- isSelected ? FontWeight.w600 : FontWeight.w400,
180
- ),
181
- ),
147
+ return KasyHover(
148
+ onTap: onTap,
149
+ focusable: true,
150
+ // Full-bleed rectangular highlight (default radius): the sheet rounds its
151
+ // own corners, so options span edge-to-edge like a native menu/list.
152
+ padding: const EdgeInsets.symmetric(
153
+ horizontal: KasySpacing.md,
154
+ vertical: KasySpacing.smd,
155
+ ),
156
+ child: Row(
157
+ children: <Widget>[
158
+ Expanded(
159
+ child: Text(
160
+ locale.nativeName,
161
+ style: context.textTheme.bodyLarge?.copyWith(
162
+ color: fg,
163
+ fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400,
182
164
  ),
183
- if (isSelected)
184
- Container(
185
- width: 10,
186
- height: 10,
187
- decoration: BoxDecoration(
188
- color: primary,
189
- shape: BoxShape.circle,
190
- ),
191
- ),
192
- ],
165
+ ),
193
166
  ),
194
- ),
167
+ if (isSelected)
168
+ Container(
169
+ width: 10,
170
+ height: 10,
171
+ decoration: BoxDecoration(
172
+ color: primary,
173
+ shape: BoxShape.circle,
174
+ ),
175
+ ),
176
+ ],
195
177
  ),
196
178
  );
197
179
  }
@@ -1,5 +1,6 @@
1
1
  import 'package:flutter/material.dart';
2
2
  import 'package:kasy_kit/core/theme/theme.dart';
3
+ import 'package:kasy_kit/core/widgets/kasy_hover.dart';
3
4
 
4
5
  /// Row used in settings modal bottom sheets (avatar source, language, etc.).
5
6
  class SettingsBottomSheetOptionTile extends StatelessWidget {
@@ -26,26 +27,28 @@ class SettingsBottomSheetOptionTile extends StatelessWidget {
26
27
  final TextStyle? labelStyle =
27
28
  context.textTheme.titleMedium?.copyWith(color: fg);
28
29
  final Text labelWidget = Text(label, style: labelStyle);
29
- return InkWell(
30
+ return KasyHover(
30
31
  onTap: onTap,
31
- child: Padding(
32
- padding: const EdgeInsets.symmetric(
33
- horizontal: KasySpacing.md,
34
- vertical: KasySpacing.smd,
35
- ),
36
- child: Row(
37
- children: <Widget>[
38
- if (leading != null) ...<Widget>[
39
- leading!,
40
- const SizedBox(width: KasySpacing.sm),
41
- ] else if (icon != null) ...<Widget>[
42
- Icon(icon, size: KasyIconSize.lg, color: fg),
43
- const SizedBox(width: KasySpacing.sm),
44
- ],
45
- Expanded(child: labelWidget),
46
- if (trailing != null) trailing!,
32
+ focusable: true,
33
+ semanticLabel: label,
34
+ // Full-bleed rectangular highlight (default radius): these rows live in a
35
+ // bottom sheet whose rounded corners clip the ends, like a native menu.
36
+ padding: const EdgeInsets.symmetric(
37
+ horizontal: KasySpacing.md,
38
+ vertical: KasySpacing.smd,
39
+ ),
40
+ child: Row(
41
+ children: <Widget>[
42
+ if (leading != null) ...<Widget>[
43
+ leading!,
44
+ const SizedBox(width: KasySpacing.sm),
45
+ ] else if (icon != null) ...<Widget>[
46
+ Icon(icon, size: KasyIconSize.lg, color: fg),
47
+ const SizedBox(width: KasySpacing.sm),
47
48
  ],
48
- ),
49
+ Expanded(child: labelWidget),
50
+ if (trailing != null) trailing!,
51
+ ],
49
52
  ),
50
53
  );
51
54
  }
@@ -26,7 +26,7 @@ class SettingsIconBadge extends StatelessWidget {
26
26
  height: size,
27
27
  decoration: BoxDecoration(
28
28
  color: color.withValues(alpha: 0.12),
29
- borderRadius: BorderRadius.circular(8),
29
+ borderRadius: BorderRadius.circular(KasyRadius.sm),
30
30
  ),
31
31
  child: Icon(
32
32
  icon,
@@ -65,7 +65,17 @@ class SettingsDivider extends StatelessWidget {
65
65
 
66
66
  @override
67
67
  Widget build(BuildContext context) {
68
- return Divider(color: context.colors.onBackground.withValues(alpha: .06));
68
+ // Inset to align with the rows' content (which now carry the horizontal
69
+ // padding the card used to have), so the hairline doesn't touch the edges.
70
+ return Divider(
71
+ // Hairline only — no extra height — so rows sit close together (iOS-style
72
+ // contiguous list) instead of floating apart with a 16px gap.
73
+ height: 1,
74
+ thickness: 1,
75
+ color: context.colors.onBackground.withValues(alpha: .06),
76
+ indent: KasySpacing.md,
77
+ endIndent: KasySpacing.md,
78
+ );
69
79
  }
70
80
  }
71
81
 
@@ -91,7 +101,10 @@ class SettingsSwitchTile extends StatelessWidget {
91
101
  @override
92
102
  Widget build(BuildContext context) {
93
103
  return Padding(
94
- padding: const EdgeInsets.symmetric(vertical: KasySpacing.sm),
104
+ padding: const EdgeInsets.symmetric(
105
+ horizontal: KasySpacing.md,
106
+ vertical: KasySpacing.smd,
107
+ ),
95
108
  child: Row(
96
109
  children: <Widget>[
97
110
  if (iconBackgroundColor != null)
@@ -110,7 +123,7 @@ class SettingsSwitchTile extends StatelessWidget {
110
123
  children: <Widget>[
111
124
  Text(
112
125
  title,
113
- style: context.textTheme.titleSmall?.copyWith(
126
+ style: context.kasyTextTheme.listRowTitle.copyWith(
114
127
  color: context.colors.onSurface,
115
128
  ),
116
129
  ),
@@ -165,12 +178,14 @@ class SettingsTile extends StatelessWidget {
165
178
  Widget build(BuildContext context) {
166
179
  return KasyHover(
167
180
  onTap: onTap,
168
- hoverEnabled: false,
169
- pressEnabled: false,
170
181
  focusable: true,
171
- borderRadius: KasyRadius.smBorderRadius,
182
+ // Rectangular highlight (default): the card clips the rounded ends, so
183
+ // middle rows stay square instead of showing a floating rounded pill.
172
184
  semanticLabel: title,
173
- padding: const EdgeInsets.symmetric(vertical: KasySpacing.sm),
185
+ padding: const EdgeInsets.symmetric(
186
+ horizontal: KasySpacing.md,
187
+ vertical: KasySpacing.smd,
188
+ ),
174
189
  child: Row(
175
190
  children: [
176
191
  if (iconBackgroundColor != null)
@@ -185,7 +200,7 @@ class SettingsTile extends StatelessWidget {
185
200
  Expanded(
186
201
  child: Text(
187
202
  title,
188
- style: context.textTheme.titleSmall?.copyWith(
203
+ style: context.kasyTextTheme.listRowTitle.copyWith(
189
204
  color: context.colors.onSurface,
190
205
  ),
191
206
  ),
@@ -193,7 +208,7 @@ class SettingsTile extends StatelessWidget {
193
208
  if (trailingLabel != null) ...[
194
209
  Text(
195
210
  trailingLabel!,
196
- style: context.textTheme.bodyMedium?.copyWith(
211
+ style: context.kasyTextTheme.listRowValue.copyWith(
197
212
  color: context.colors.muted,
198
213
  ),
199
214
  ),