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.
Files changed (169) hide show
  1. package/README.md +1 -1
  2. package/bin/kasy.js +66 -2
  3. package/docs/cli-reference.md +7 -7
  4. package/lib/commands/apple-web.js +222 -0
  5. package/lib/commands/configure.js +3 -91
  6. package/lib/commands/doctor.js +20 -0
  7. package/lib/commands/facebook.js +189 -0
  8. package/lib/commands/new.js +61 -11
  9. package/lib/commands/release-version.js +234 -0
  10. package/lib/commands/update.js +27 -0
  11. package/lib/scaffold/CHANGELOG.json +27 -0
  12. package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api.dart +24 -0
  13. package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api_interface.dart +15 -0
  14. package/lib/scaffold/backends/api/patch/lib/features/authentication/repositories/authentication_repository.dart +27 -0
  15. package/lib/scaffold/backends/api/patch/lib/features/subscriptions/api/entities/subscription_entity.dart +1 -0
  16. package/lib/scaffold/backends/firebase/setup-from-scratch.js +199 -21
  17. package/lib/scaffold/backends/patch-base-hashes.json +66 -0
  18. package/lib/scaffold/backends/supabase/deploy.js +92 -0
  19. package/lib/scaffold/backends/supabase/edge-functions/stripe-webhook/index.ts +2 -0
  20. package/lib/scaffold/backends/supabase/migrations/20240101000007_welcome_notification.sql +36 -23
  21. package/lib/scaffold/backends/supabase/migrations/20240101000014_subscription_trial_end.sql +6 -0
  22. package/lib/scaffold/backends/supabase/patch/lib/features/authentication/api/authentication_api.dart +92 -3
  23. package/lib/scaffold/backends/supabase/patch/lib/features/authentication/repositories/authentication_repository.dart +36 -0
  24. package/lib/scaffold/backends/supabase/patch/lib/features/subscriptions/api/entities/subscription_entity.dart +1 -0
  25. package/lib/scaffold/generate.js +53 -4
  26. package/lib/scaffold/shared/generator-utils.js +18 -6
  27. package/lib/utils/apple-web.js +147 -0
  28. package/lib/utils/facebook.js +162 -0
  29. package/lib/utils/i18n/messages-en.js +85 -0
  30. package/lib/utils/i18n/messages-es.js +85 -0
  31. package/lib/utils/i18n/messages-pt.js +85 -0
  32. package/package.json +5 -2
  33. package/templates/firebase/.firebase/hosting.YnVpbGQvd2Vi.cache +73 -0
  34. package/templates/firebase/AGENTS.md +170 -0
  35. package/templates/firebase/CLAUDE.md +16 -0
  36. package/templates/firebase/DESIGN_SYSTEM.md +269 -0
  37. package/templates/firebase/docs/auth-setup.en.md +4 -2
  38. package/templates/firebase/docs/auth-setup.es.md +4 -2
  39. package/templates/firebase/docs/auth-setup.pt.md +4 -2
  40. package/templates/firebase/firebase.json +56 -1
  41. package/templates/firebase/functions/src/authentication/triggers.ts +10 -6
  42. package/templates/firebase/functions/src/core/data/entities/subscription_entity.ts +5 -0
  43. package/templates/firebase/functions/src/core/data/entities/user_entity.ts +9 -1
  44. package/templates/firebase/functions/src/core/data/repositories/user_repository.ts +27 -0
  45. package/templates/firebase/functions/src/subscriptions/models/subscriptions.ts +3 -0
  46. package/templates/firebase/functions/src/subscriptions/stripe_functions.ts +4 -0
  47. package/templates/firebase/lib/components/components.dart +1 -0
  48. package/templates/firebase/lib/components/kasy_alert.dart +0 -1
  49. package/templates/firebase/lib/components/kasy_app_bar.dart +35 -17
  50. package/templates/firebase/lib/components/kasy_bottom_sheet.dart +0 -1
  51. package/templates/firebase/lib/components/kasy_date_picker.dart +0 -5
  52. package/templates/firebase/lib/components/kasy_dialog.dart +0 -1
  53. package/templates/firebase/lib/components/kasy_otp_verification_bottom_sheet.dart +1 -1
  54. package/templates/firebase/lib/components/kasy_screen.dart +114 -0
  55. package/templates/firebase/lib/components/kasy_sidebar.dart +189 -120
  56. package/templates/firebase/lib/components/kasy_text_area.dart +0 -1
  57. package/templates/firebase/lib/components/kasy_text_field.dart +0 -1
  58. package/templates/firebase/lib/components/kasy_text_field_otp.dart +0 -1
  59. package/templates/firebase/lib/components/kasy_toast.dart +108 -73
  60. package/templates/firebase/lib/core/app_update/app_update_repository.dart +54 -0
  61. package/templates/firebase/lib/core/app_update/app_update_status.dart +14 -0
  62. package/templates/firebase/lib/core/app_update/update_available_sheet.dart +70 -0
  63. package/templates/firebase/lib/core/bottom_menu/active_tab_notifier.dart +40 -3
  64. package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +82 -34
  65. package/templates/firebase/lib/core/bottom_menu/web_content_wrapper.dart +6 -6
  66. package/templates/firebase/lib/core/bottom_menu/web_url.dart +20 -0
  67. package/templates/firebase/lib/core/chrome/chrome_visibility.dart +22 -0
  68. package/templates/firebase/lib/core/config/features.dart +5 -0
  69. package/templates/firebase/lib/core/data/api/remote_config_api.dart +38 -1
  70. package/templates/firebase/lib/core/guards/guard.dart +16 -2
  71. package/templates/firebase/lib/core/icons/kasy_icons.dart +3 -0
  72. package/templates/firebase/lib/core/rating/widgets/rate_banner.dart +2 -5
  73. package/templates/firebase/lib/core/rating/widgets/review_popup.dart +48 -124
  74. package/templates/firebase/lib/core/shared_preferences/shared_preferences.dart +14 -0
  75. package/templates/firebase/lib/core/states/components/maybe_show_update_available.dart +32 -0
  76. package/templates/firebase/lib/core/states/logout_action.dart +5 -1
  77. package/templates/firebase/lib/core/states/user_state_notifier.dart +85 -14
  78. package/templates/firebase/lib/core/theme/responsive_text_theme.dart +69 -0
  79. package/templates/firebase/lib/core/theme/texts.dart +90 -57
  80. package/templates/firebase/lib/core/theme/type_scale.dart +77 -0
  81. package/templates/firebase/lib/core/theme/web_background_sync_web.dart +20 -6
  82. package/templates/firebase/lib/core/utils/image_bytes_loader.dart +12 -0
  83. package/templates/firebase/lib/core/utils/image_bytes_loader_io.dart +28 -0
  84. package/templates/firebase/lib/core/utils/image_bytes_loader_web.dart +56 -0
  85. package/templates/firebase/lib/core/web_screen_width.dart +15 -0
  86. package/templates/firebase/lib/core/web_screen_width_io.dart +3 -0
  87. package/templates/firebase/lib/core/web_screen_width_web.dart +10 -0
  88. package/templates/firebase/lib/core/web_viewport_scale.dart +61 -25
  89. package/templates/firebase/lib/core/widgets/focus_visibility.dart +89 -0
  90. package/templates/firebase/lib/core/widgets/update_bottom_sheet.dart +29 -126
  91. package/templates/firebase/lib/features/ai_chat/ai_chat_page.dart +11 -8
  92. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_chat_composer.dart +21 -0
  93. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_chat_conversation_view.dart +1 -2
  94. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_list.dart +2 -3
  95. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_tile.dart +1 -2
  96. package/templates/firebase/lib/features/authentication/api/auth_web_support.dart +12 -0
  97. package/templates/firebase/lib/features/authentication/api/auth_web_support_web.dart +25 -0
  98. package/templates/firebase/lib/features/authentication/api/authentication_api.dart +266 -0
  99. package/templates/firebase/lib/features/authentication/api/authentication_api_interface.dart +22 -0
  100. package/templates/firebase/lib/features/authentication/navigation/post_login_navigation.dart +32 -0
  101. package/templates/firebase/lib/features/authentication/providers/phone_auth_notifier.dart +7 -7
  102. package/templates/firebase/lib/features/authentication/providers/signin_state_provider.dart +34 -10
  103. package/templates/firebase/lib/features/authentication/providers/signup_state_provider.dart +2 -2
  104. package/templates/firebase/lib/features/authentication/repositories/authentication_repository.dart +37 -0
  105. package/templates/firebase/lib/features/authentication/repositories/exceptions/authentication_exceptions.dart +8 -0
  106. package/templates/firebase/lib/features/authentication/ui/components/otp_verification.dart +2 -2
  107. package/templates/firebase/lib/features/authentication/ui/components/phone_input.dart +2 -2
  108. package/templates/firebase/lib/features/authentication/ui/signin_page.dart +80 -15
  109. package/templates/firebase/lib/features/authentication/ui/signup_page.dart +20 -14
  110. package/templates/firebase/lib/features/authentication/ui/widgets/auth_card_scaffold.dart +2 -2
  111. package/templates/firebase/lib/features/authentication/ui/widgets/recover_password_result.dart +2 -2
  112. package/templates/firebase/lib/features/feedbacks/ui/widgets/add_feature_button.dart +0 -1
  113. package/templates/firebase/lib/features/feedbacks/ui/widgets/feature_card.dart +1 -2
  114. package/templates/firebase/lib/features/home/design_system_page.dart +134 -67
  115. package/templates/firebase/lib/features/home/home_components_page.dart +8 -1
  116. package/templates/firebase/lib/features/home/home_components_preview_page.dart +1 -3
  117. package/templates/firebase/lib/features/home/home_components_preview_registry.dart +186 -56
  118. package/templates/firebase/lib/features/home/home_page.dart +4 -0
  119. package/templates/firebase/lib/features/local_reminders/ui/reminder_page.dart +169 -208
  120. package/templates/firebase/lib/features/notifications/providers/notifications_provider.dart +10 -0
  121. package/templates/firebase/lib/features/notifications/ui/components/notification_settings_sheet.dart +2 -2
  122. package/templates/firebase/lib/features/notifications/ui/components/notification_tile.dart +21 -8
  123. package/templates/firebase/lib/features/notifications/ui/components/push_notification_switcher.dart +2 -2
  124. package/templates/firebase/lib/features/notifications/ui/notifications_page.dart +3 -4
  125. package/templates/firebase/lib/features/notifications/ui/widgets/notification_tile.dart +84 -128
  126. package/templates/firebase/lib/features/onboarding/providers/onboarding_model.dart +11 -0
  127. package/templates/firebase/lib/features/onboarding/providers/onboarding_provider.dart +30 -3
  128. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_loader.dart +13 -4
  129. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_illustration_scaffold.dart +3 -4
  130. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +2 -4
  131. package/templates/firebase/lib/features/onboarding/ui/widgets/selectable_row_tile.dart +2 -1
  132. package/templates/firebase/lib/features/settings/settings_page.dart +152 -11
  133. package/templates/firebase/lib/features/settings/ui/components/admin/admin_page.dart +58 -21
  134. package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_tab.dart +12 -12
  135. package/templates/firebase/lib/features/settings/ui/components/admin/send_push_notification_page.dart +1 -4
  136. package/templates/firebase/lib/features/settings/ui/components/avatar_component.dart +2 -2
  137. package/templates/firebase/lib/features/settings/ui/components/create_password_sheet.dart +141 -0
  138. package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +0 -1
  139. package/templates/firebase/lib/features/settings/ui/widgets/admin_card.dart +42 -38
  140. package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +17 -2
  141. package/templates/firebase/lib/features/subscriptions/api/entities/subscription_entity.dart +3 -0
  142. package/templates/firebase/lib/features/subscriptions/api/stripe_product.dart +12 -11
  143. package/templates/firebase/lib/features/subscriptions/repositories/subscription_repository.dart +25 -2
  144. package/templates/firebase/lib/features/subscriptions/ui/component/active_premium_content.dart +319 -143
  145. package/templates/firebase/lib/features/subscriptions/ui/component/paywall_minimal.dart +1 -5
  146. package/templates/firebase/lib/features/subscriptions/ui/component/paywall_row.dart +1 -5
  147. package/templates/firebase/lib/features/subscriptions/ui/component/paywall_with_switch.dart +2 -5
  148. package/templates/firebase/lib/features/subscriptions/ui/component/premium_content.dart +1 -4
  149. package/templates/firebase/lib/features/subscriptions/ui/widgets/feature_line.dart +1 -2
  150. package/templates/firebase/lib/features/subscriptions/ui/widgets/premium_banner.dart +2 -7
  151. package/templates/firebase/lib/features/subscriptions/ui/widgets/premium_feature.dart +2 -4
  152. package/templates/firebase/lib/features/subscriptions/ui/widgets/selectable_col.dart +1 -4
  153. package/templates/firebase/lib/features/subscriptions/ui/widgets/selectable_row.dart +0 -3
  154. package/templates/firebase/lib/i18n/en.i18n.json +54 -7
  155. package/templates/firebase/lib/i18n/es.i18n.json +54 -7
  156. package/templates/firebase/lib/i18n/pt.i18n.json +54 -7
  157. package/templates/firebase/lib/main.dart +11 -2
  158. package/templates/firebase/lib/router.dart +94 -13
  159. package/templates/firebase/pubspec.yaml +1 -1
  160. package/templates/firebase/test/core/data/api/fake_remote_config_api.dart +14 -0
  161. package/templates/firebase/test/core/states/user_state_notifier_test.dart +47 -3
  162. package/templates/firebase/test/core/web_viewport_scale_test.dart +68 -0
  163. package/templates/firebase/test/features/authentication/data/api/auth_api_fake.dart +15 -0
  164. package/templates/firebase/tool/design_check.dart +152 -0
  165. package/templates/firebase/web/index.html +162 -14
  166. package/templates/firebase/assets/images/review.png +0 -0
  167. package/templates/firebase/assets/images/update.png +0 -0
  168. package/templates/firebase/lib/core/guards/user_info_guard.dart +0 -61
  169. package/templates/firebase/lib/features/notifications/ui/components/notifications_header.dart +0 -32
@@ -1,11 +1,15 @@
1
1
  import 'package:flutter/material.dart';
2
2
  import 'package:flutter_riverpod/flutter_riverpod.dart';
3
3
  import 'package:kasy_kit/components/kasy_app_bar.dart';
4
+ import 'package:kasy_kit/components/kasy_card.dart';
5
+ import 'package:kasy_kit/components/kasy_chip.dart';
6
+ import 'package:kasy_kit/components/kasy_date_picker.dart';
7
+ import 'package:kasy_kit/components/kasy_tabs.dart';
4
8
  import 'package:kasy_kit/core/theme/theme.dart';
5
- import 'package:kasy_kit/core/widgets/kasy_focus_ring.dart';
6
9
  import 'package:kasy_kit/core/widgets/kasy_scroll_behavior.dart';
7
10
  import 'package:kasy_kit/features/local_reminders/providers/reminder_notifier.dart';
8
11
  import 'package:kasy_kit/features/local_reminders/repositories/reminder_preferences.dart';
12
+ import 'package:kasy_kit/features/settings/ui/widgets/settings_tile.dart';
9
13
  import 'package:kasy_kit/i18n/translations.g.dart';
10
14
 
11
15
  class ReminderPage extends ConsumerWidget {
@@ -44,162 +48,137 @@ class _ReminderForm extends ConsumerWidget {
44
48
 
45
49
  const _ReminderForm({required this.state});
46
50
 
51
+ // Tab order mirrors the type selector left-to-right.
52
+ static const List<ReminderType> _types = <ReminderType>[
53
+ ReminderType.daily,
54
+ ReminderType.weekly,
55
+ ReminderType.specificDate,
56
+ ];
57
+
47
58
  @override
48
59
  Widget build(BuildContext context, WidgetRef ref) {
49
60
  final tr = Translations.of(context).reminderPage;
50
61
  final notifier = ref.read(reminderProvider.notifier);
51
62
 
52
- return Column(
53
- crossAxisAlignment: CrossAxisAlignment.stretch,
54
- children: [
55
- SwitchListTile(
56
- title: Text(tr.toggleLabel, style: context.textTheme.bodyLarge),
57
- value: state.enabled,
58
- onChanged: notifier.setEnabled,
59
- ),
60
- if (state.enabled) ...[
61
- const Divider(),
62
- const SizedBox(height: KasySpacing.sm),
63
- Text(
64
- tr.typeLabel,
65
- style: context.textTheme.labelLarge?.copyWith(
66
- color: context.colors.muted,
67
- ),
68
- ),
69
- const SizedBox(height: KasySpacing.sm),
70
- _TypeSelector(current: state.type, onChanged: notifier.setType),
71
- const SizedBox(height: KasySpacing.lg),
72
- Text(
73
- tr.timeLabel,
74
- style: context.textTheme.labelLarge?.copyWith(
75
- color: context.colors.muted,
76
- ),
77
- ),
78
- const SizedBox(height: KasySpacing.sm),
79
- _TimeTile(
80
- hour: state.hour,
81
- minute: state.minute,
82
- onChanged: (h, m) => notifier.setTime(h, m),
63
+ return Center(
64
+ child: ConstrainedBox(
65
+ constraints: const BoxConstraints(maxWidth: 600),
66
+ child: Padding(
67
+ // Horizontal gutter is already applied by KasyOverlayScaffold; only
68
+ // add vertical spacing here so the side padding matches the
69
+ // Notifications screen (a single gutter, not a doubled one).
70
+ padding: const EdgeInsets.fromLTRB(
71
+ 0,
72
+ KasySpacing.belowChromeContentGap,
73
+ 0,
74
+ KasySpacing.xl,
83
75
  ),
84
- if (state.type == ReminderType.weekly) ...[
85
- const SizedBox(height: KasySpacing.lg),
86
- Text(
87
- tr.dayLabel,
88
- style: context.textTheme.labelLarge?.copyWith(
89
- color: context.colors.muted,
90
- ),
91
- ),
92
- const SizedBox(height: KasySpacing.sm),
93
- _DaySelector(
94
- current: state.dayOfWeek,
95
- onChanged: notifier.setDayOfWeek,
96
- ),
97
- ],
98
- if (state.type == ReminderType.specificDate) ...[
99
- const SizedBox(height: KasySpacing.lg),
100
- Text(
101
- tr.dateLabel,
102
- style: context.textTheme.labelLarge?.copyWith(
103
- color: context.colors.muted,
76
+ child: Column(
77
+ crossAxisAlignment: CrossAxisAlignment.stretch,
78
+ children: [
79
+ KasyCard(
80
+ padding: const EdgeInsets.symmetric(
81
+ horizontal: KasySpacing.md,
82
+ vertical: KasySpacing.xs,
83
+ ),
84
+ child: SettingsSwitchTile(
85
+ icon: KasyIcons.notification,
86
+ title: tr.toggleLabel,
87
+ value: state.enabled,
88
+ onChanged: notifier.setEnabled,
89
+ ),
104
90
  ),
105
- ),
106
- const SizedBox(height: KasySpacing.sm),
107
- _DateTile(date: state.date, onChanged: notifier.setDate),
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(
129
- value: ReminderType.specificDate,
130
- label: Text(tr.specificDate),
91
+ if (state.enabled) ...[
92
+ const SizedBox(height: KasySpacing.xl),
93
+ _FieldLabel(tr.typeLabel),
94
+ const SizedBox(height: KasySpacing.sm),
95
+ // Default hug mode scrolls horizontally when the labels don't
96
+ // fit (long localized strings) instead of overflowing the row.
97
+ KasyTabs(
98
+ tabs: [tr.daily, tr.weekly, tr.specificDate],
99
+ selectedIndex: _types.indexOf(state.type),
100
+ onTabSelected: (i) => notifier.setType(_types[i]),
101
+ ),
102
+ // daily / weekly schedule by a wall-clock time (hour + minute);
103
+ // specificDate carries its own time inside the chosen date.
104
+ if (state.type == ReminderType.daily ||
105
+ state.type == ReminderType.weekly) ...[
106
+ const SizedBox(height: KasySpacing.lg),
107
+ _FieldLabel(tr.timeLabel),
108
+ const SizedBox(height: KasySpacing.sm),
109
+ _TimeTile(
110
+ hour: state.hour,
111
+ minute: state.minute,
112
+ onChanged: (h, m) => notifier.setTime(h, m),
113
+ ),
114
+ ],
115
+ if (state.type == ReminderType.weekly) ...[
116
+ const SizedBox(height: KasySpacing.lg),
117
+ _FieldLabel(tr.dayLabel),
118
+ const SizedBox(height: KasySpacing.sm),
119
+ _DaySelector(
120
+ current: state.dayOfWeek,
121
+ onChanged: notifier.setDayOfWeek,
122
+ ),
123
+ ],
124
+ if (state.type == ReminderType.specificDate) ...[
125
+ const SizedBox(height: KasySpacing.lg),
126
+ _FieldLabel(tr.dateLabel),
127
+ const SizedBox(height: KasySpacing.sm),
128
+ KasyDatePicker(
129
+ value: state.date,
130
+ minDate: DateTime.now(),
131
+ onChanged: (picked) {
132
+ // Keep the previously chosen time when the day changes.
133
+ final DateTime? base = state.date;
134
+ notifier.setDate(
135
+ DateTime(
136
+ picked.year,
137
+ picked.month,
138
+ picked.day,
139
+ base?.hour ?? 9,
140
+ base?.minute ?? 0,
141
+ ),
142
+ );
143
+ },
144
+ ),
145
+ const SizedBox(height: KasySpacing.lg),
146
+ _FieldLabel(tr.timeLabel),
147
+ const SizedBox(height: KasySpacing.sm),
148
+ _TimeTile(
149
+ hour: state.date?.hour ?? 9,
150
+ minute: state.date?.minute ?? 0,
151
+ onChanged: (h, m) {
152
+ // Keep the previously chosen day when the time changes.
153
+ final DateTime base = state.date ?? DateTime.now();
154
+ notifier.setDate(
155
+ DateTime(base.year, base.month, base.day, h, m),
156
+ );
157
+ },
158
+ ),
159
+ ],
160
+ ],
161
+ ],
162
+ ),
131
163
  ),
132
- ],
133
- selected: {current},
134
- onSelectionChanged: (s) => onChanged(s.first),
135
- style: ButtonStyle(
136
- textStyle: WidgetStateProperty.all(context.textTheme.labelSmall),
137
164
  ),
138
165
  );
139
166
  }
140
167
  }
141
168
 
142
- class _TimeTile extends StatelessWidget {
143
- final int hour;
144
- final int minute;
145
- final void Function(int hour, int minute) onChanged;
169
+ /// Quiet group eyebrow above each field — the design-system section label
170
+ /// (small, gently tracked, muted), matching the Settings / sidebar pattern.
171
+ class _FieldLabel extends StatelessWidget {
172
+ final String label;
146
173
 
147
- const _TimeTile({
148
- required this.hour,
149
- required this.minute,
150
- required this.onChanged,
151
- });
152
-
153
- String _pad(int v) => v.toString().padLeft(2, '0');
174
+ const _FieldLabel(this.label);
154
175
 
155
176
  @override
156
177
  Widget build(BuildContext context) {
157
- Future<void> pickTime() async {
158
- final picked = await showTimePicker(
159
- context: context,
160
- initialTime: TimeOfDay(hour: hour, minute: minute),
161
- );
162
- if (picked != null) {
163
- onChanged(picked.hour, picked.minute);
164
- }
165
- }
166
-
167
- return KasyFocusRing(
168
- onActivate: pickTime,
169
- borderRadius: BorderRadius.circular(KasySpacing.sm),
170
- child: InkWell(
171
- canRequestFocus: false,
172
- onTap: pickTime,
173
- borderRadius: BorderRadius.circular(KasySpacing.sm),
174
- child: Container(
175
- padding: const EdgeInsets.symmetric(
176
- horizontal: KasySpacing.md,
177
- vertical: KasySpacing.smd,
178
- ),
179
- decoration: BoxDecoration(
180
- color: context.colors.surface,
181
- borderRadius: BorderRadius.circular(KasySpacing.sm),
182
- ),
183
- child: Row(
184
- children: [
185
- Icon(KasyIcons.time, color: context.colors.primary),
186
- const SizedBox(width: KasySpacing.sm),
187
- Text(
188
- '${_pad(hour)}:${_pad(minute)}',
189
- style: context.textTheme.headlineMedium?.copyWith(
190
- color: context.colors.onSurface,
191
- fontWeight: FontWeight.bold,
192
- ),
193
- ),
194
- const Spacer(),
195
- Icon(
196
- KasyIcons.arrowForwardIos,
197
- size: KasyIconSize.xs,
198
- color: context.colors.muted,
199
- ),
200
- ],
201
- ),
202
- ),
178
+ return Text(
179
+ label,
180
+ style: context.kasyTextTheme.sectionLabel.copyWith(
181
+ color: context.colors.muted,
203
182
  ),
204
183
  );
205
184
  }
@@ -217,12 +196,12 @@ class _DaySelector extends StatelessWidget {
217
196
  final days = ['Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb', 'Dom'];
218
197
  return Wrap(
219
198
  spacing: KasySpacing.xs,
199
+ runSpacing: KasySpacing.xs,
220
200
  children: List.generate(7, (i) {
221
201
  final dayIndex = i + 1;
222
- final selected = current == dayIndex;
223
- return ChoiceChip(
224
- label: Text(days[i]),
225
- selected: selected,
202
+ return KasyChip(
203
+ label: days[i],
204
+ selected: current == dayIndex,
226
205
  onSelected: (_) => onChanged(dayIndex),
227
206
  );
228
207
  }),
@@ -230,82 +209,64 @@ class _DaySelector extends StatelessWidget {
230
209
  }
231
210
  }
232
211
 
233
- class _DateTile extends StatelessWidget {
234
- final DateTime? date;
235
- final ValueChanged<DateTime> onChanged;
212
+ /// A tappable field that opens the time picker. Built on [KasyCard] so it
213
+ /// inherits the design-system surface, press depth and keyboard focus ring.
214
+ class _TimeTile extends StatelessWidget {
215
+ final int hour;
216
+ final int minute;
217
+ final void Function(int hour, int minute) onChanged;
236
218
 
237
- const _DateTile({required this.date, required this.onChanged});
219
+ const _TimeTile({
220
+ required this.hour,
221
+ required this.minute,
222
+ required this.onChanged,
223
+ });
224
+
225
+ String _pad(int v) => v.toString().padLeft(2, '0');
238
226
 
239
227
  @override
240
228
  Widget build(BuildContext context) {
241
- final label = date != null
242
- ? '${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')}'
243
- : Translations.of(context).reminderPage.selectDate;
244
-
245
- Future<void> pickDateTime() async {
246
- final now = DateTime.now();
247
- final pickedDate = await showDatePicker(
248
- context: context,
249
- initialDate: date ?? now,
250
- firstDate: now,
251
- lastDate: now.add(const Duration(days: 365)),
252
- );
253
- if (pickedDate == null || !context.mounted) return;
254
- final pickedTime = await showTimePicker(
229
+ Future<void> pickTime() async {
230
+ final picked = await showTimePicker(
255
231
  context: context,
256
- initialTime: TimeOfDay(
257
- hour: date?.hour ?? 9,
258
- minute: date?.minute ?? 0,
259
- ),
260
- );
261
- if (pickedTime == null) return;
262
- onChanged(
263
- DateTime(
264
- pickedDate.year,
265
- pickedDate.month,
266
- pickedDate.day,
267
- pickedTime.hour,
268
- pickedTime.minute,
269
- ),
232
+ initialTime: TimeOfDay(hour: hour, minute: minute),
270
233
  );
234
+ if (picked != null) {
235
+ onChanged(picked.hour, picked.minute);
236
+ }
271
237
  }
272
238
 
273
- return KasyFocusRing(
274
- onActivate: pickDateTime,
275
- borderRadius: BorderRadius.circular(KasySpacing.sm),
276
- child: InkWell(
277
- canRequestFocus: false,
278
- onTap: pickDateTime,
279
- borderRadius: BorderRadius.circular(KasySpacing.sm),
280
- child: Container(
281
- padding: const EdgeInsets.symmetric(
282
- horizontal: KasySpacing.md,
283
- vertical: KasySpacing.smd,
239
+ return KasyCard(
240
+ onTap: pickTime,
241
+ borderRadius: KasyRadius.mdBorderRadius,
242
+ padding: const EdgeInsets.symmetric(
243
+ horizontal: KasySpacing.md,
244
+ vertical: KasySpacing.smd,
245
+ ),
246
+ child: Row(
247
+ children: [
248
+ Icon(
249
+ KasyIcons.time,
250
+ size: KasyIconSize.lg,
251
+ color: context.colors.primary,
284
252
  ),
285
- decoration: BoxDecoration(
286
- color: context.colors.surface,
287
- borderRadius: BorderRadius.circular(KasySpacing.sm),
253
+ const SizedBox(width: KasySpacing.sm),
254
+ Text(
255
+ '${_pad(hour)}:${_pad(minute)}',
256
+ style: context.textTheme.bodyLarge?.copyWith(
257
+ color: context.colors.onSurface,
258
+ fontWeight: FontWeight.w600,
259
+ ),
288
260
  ),
289
- child: Row(
290
- children: [
291
- Icon(KasyIcons.calendar, color: context.colors.primary),
292
- const SizedBox(width: KasySpacing.sm),
293
- Text(
294
- label,
295
- style: context.textTheme.bodyLarge?.copyWith(
296
- color: context.colors.onSurface,
297
- ),
298
- ),
299
- const Spacer(),
300
- Icon(
301
- KasyIcons.arrowForwardIos,
302
- size: KasyIconSize.xs,
303
- color: context.colors.muted,
304
- ),
305
- ],
261
+ const Spacer(),
262
+ Icon(
263
+ KasyIcons.arrowForwardIos,
264
+ size: KasyIconSize.xs,
265
+ color: context.colors.muted,
306
266
  ),
307
- ),
267
+ ],
308
268
  ),
309
269
  );
310
270
  }
311
271
  }
272
+
@@ -3,6 +3,7 @@ import 'dart:async';
3
3
  import 'package:kasy_kit/core/states/user_state_notifier.dart';
4
4
  import 'package:kasy_kit/features/notifications/providers/models/notification.dart';
5
5
  import 'package:kasy_kit/features/notifications/providers/models/notification_list.dart';
6
+ import 'package:kasy_kit/features/notifications/providers/unread_notifications_count_provider.dart';
6
7
  import 'package:kasy_kit/features/notifications/repositories/notifications_repository.dart';
7
8
  import 'package:logger/logger.dart';
8
9
  import 'package:riverpod_annotation/riverpod_annotation.dart';
@@ -62,6 +63,10 @@ class NotificationsNotifier extends _$NotificationsNotifier {
62
63
  data: [...updatedNotifications, ...seenNotifications],
63
64
  ),
64
65
  );
66
+ // The bottom-bar unread badge reads from a separate, polled source. Nudge
67
+ // it to re-check now so it drops as soon as we mark these read, instead of
68
+ // lagging until the next poll cycle.
69
+ ref.invalidate(unreadNotificationsCountProvider);
65
70
  } catch (e) {
66
71
  Logger().e("error $e");
67
72
  }
@@ -82,6 +87,9 @@ class NotificationsNotifier extends _$NotificationsNotifier {
82
87
  );
83
88
  try {
84
89
  await notificationRepository.delete(userId, notification);
90
+ // Deleting an unread notification changes the unread count: refresh the
91
+ // badge source now rather than waiting for its next poll.
92
+ ref.invalidate(unreadNotificationsCountProvider);
85
93
  } catch (e) {
86
94
  Logger().e("delete error $e");
87
95
  state = AsyncValue.data(previous);
@@ -102,6 +110,8 @@ class NotificationsNotifier extends _$NotificationsNotifier {
102
110
  await Future.wait(
103
111
  previous.data.map((n) => notificationRepository.delete(userId, n)),
104
112
  );
113
+ // Clearing the list zeroes the unread count: refresh the badge now.
114
+ ref.invalidate(unreadNotificationsCountProvider);
105
115
  } catch (e) {
106
116
  Logger().e("deleteAll error $e");
107
117
  state = AsyncValue.data(previous);
@@ -121,8 +121,8 @@ class _NotificationSettingsSheetState
121
121
  title: tr.push_title,
122
122
  value: _isEnabled,
123
123
  iconBackgroundColor: _isEnabled
124
- ? const Color(0xFFFF6B35)
125
- : const Color(0xFF78909C),
124
+ ? const Color(0xFFFF6B35) // design-check: ignore — category accent
125
+ : const Color(0xFF78909C), // design-check: ignore — category accent
126
126
  onChanged: _toggling ? (_) {} : _onToggle,
127
127
  ),
128
128
  // subtitle below
@@ -1,5 +1,6 @@
1
1
  import 'package:flutter/material.dart';
2
2
  import 'package:flutter_animate/flutter_animate.dart';
3
+ import 'package:kasy_kit/components/kasy_card.dart';
3
4
  import 'package:kasy_kit/core/theme/theme.dart';
4
5
  import 'package:kasy_kit/core/widgets/kasy_hover.dart';
5
6
  import 'package:kasy_kit/features/notifications/providers/models/notification.dart'
@@ -31,15 +32,27 @@ class NotificationTileComponent extends StatelessWidget {
31
32
  ],
32
33
  delay: index < 6 ? Duration(milliseconds: 60 * index) : Duration.zero,
33
34
  onComplete: (controller) => controller.stop(),
34
- child: KasyHover(
35
- onTap: () => onTap?.call(notification),
36
- borderRadius: KasyRadius.smBorderRadius,
37
- focusable: true,
35
+ // Card surface (elevated, radius 16, hairline border + soft shadow) to
36
+ // match the Settings card; the hover/press feedback and keyboard focus
37
+ // ring live inside on the surface, exactly like a Settings row.
38
+ child: KasyCard(
38
39
  margin: const EdgeInsets.only(top: KasySpacing.sm),
39
- child: NotificationTile.from(
40
- key: ValueKey('notification_${notification.id}'),
41
- context,
42
- notification,
40
+ borderRadius: KasyRadius.lgBorderRadius,
41
+ padding: EdgeInsets.zero,
42
+ child: KasyHover(
43
+ onTap: () => onTap?.call(notification),
44
+ borderRadius: KasyRadius.lgBorderRadius,
45
+ focusable: true,
46
+ focusGapColor: context.colors.surface,
47
+ padding: const EdgeInsets.symmetric(
48
+ horizontal: KasySpacing.md,
49
+ vertical: KasySpacing.smd,
50
+ ),
51
+ child: NotificationTile.from(
52
+ key: ValueKey('notification_${notification.id}'),
53
+ context,
54
+ notification,
55
+ ),
43
56
  ),
44
57
  ),
45
58
  );
@@ -72,8 +72,8 @@ class _PushNotificationSwitcherState
72
72
  title: tr.push_title,
73
73
  value: _isEnabled,
74
74
  iconBackgroundColor: _isEnabled
75
- ? const Color(0xFFFF6B35)
76
- : const Color(0xFF78909C),
75
+ ? const Color(0xFFFF6B35) // design-check: ignore — category accent
76
+ : const Color(0xFF78909C), // design-check: ignore — category accent
77
77
  onChanged: _loading ? (_) {} : _onChanged,
78
78
  ),
79
79
  Padding(
@@ -257,11 +257,10 @@ class _GroupLabel extends StatelessWidget {
257
257
  ),
258
258
  child: Text(
259
259
  label.toUpperCase(),
260
- style: context.textTheme.labelSmall?.copyWith(
261
- fontSize: 11,
260
+ // Design-system section-eyebrow role (same as Settings' section labels),
261
+ // instead of a bespoke 11/w700 style.
262
+ style: context.kasyTextTheme.sectionLabel.copyWith(
262
263
  color: context.colors.muted,
263
- letterSpacing: 1.4,
264
- fontWeight: FontWeight.w700,
265
264
  ),
266
265
  ),
267
266
  );