kasy-cli 1.32.0 → 1.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/kasy.js +42 -0
- 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 +50 -2
- package/lib/scaffold/CHANGELOG.json +18 -0
- package/lib/scaffold/backends/firebase/setup-from-scratch.js +164 -0
- package/lib/scaffold/backends/supabase/deploy.js +92 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/authentication/api/authentication_api.dart +10 -0
- 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 +62 -0
- package/lib/utils/i18n/messages-es.js +62 -0
- package/lib/utils/i18n/messages-pt.js +62 -0
- package/package.json +2 -2
- package/templates/firebase/AGENTS.md +87 -0
- package/templates/firebase/CLAUDE.md +16 -0
- package/templates/firebase/DESIGN_SYSTEM.md +234 -0
- package/templates/firebase/docs/auth-setup.en.md +2 -2
- package/templates/firebase/docs/auth-setup.es.md +2 -2
- package/templates/firebase/docs/auth-setup.pt.md +2 -2
- package/templates/firebase/lib/components/components.dart +1 -0
- package/templates/firebase/lib/components/kasy_app_bar.dart +4 -1
- package/templates/firebase/lib/components/kasy_screen.dart +114 -0
- package/templates/firebase/lib/components/kasy_toast.dart +39 -70
- 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/rating/widgets/review_popup.dart +46 -124
- package/templates/firebase/lib/core/widgets/update_bottom_sheet.dart +29 -126
- package/templates/firebase/lib/features/ai_chat/ai_chat_page.dart +11 -7
- 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_conversation_list.dart +1 -1
- package/templates/firebase/lib/features/authentication/api/authentication_api.dart +61 -0
- package/templates/firebase/lib/features/authentication/ui/signin_page.dart +21 -15
- package/templates/firebase/lib/features/authentication/ui/signup_page.dart +20 -14
- package/templates/firebase/lib/features/feedbacks/ui/widgets/feature_card.dart +1 -2
- package/templates/firebase/lib/features/home/home_components_page.dart +7 -1
- package/templates/firebase/lib/features/home/home_components_preview_registry.dart +32 -0
- package/templates/firebase/lib/features/local_reminders/ui/reminder_page.dart +165 -209
- 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 -6
- package/templates/firebase/lib/features/notifications/ui/widgets/notification_tile.dart +77 -126
- 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/ui/components/admin/admin_page.dart +17 -8
- package/templates/firebase/lib/features/settings/ui/components/avatar_component.dart +2 -2
- package/templates/firebase/lib/i18n/en.i18n.json +5 -4
- package/templates/firebase/lib/i18n/es.i18n.json +5 -4
- package/templates/firebase/lib/i18n/pt.i18n.json +5 -4
- package/templates/firebase/lib/router.dart +2 -0
- package/templates/firebase/pubspec.yaml +1 -1
- package/templates/firebase/tool/design_check.dart +152 -0
- package/templates/firebase/assets/images/review.png +0 -0
- package/templates/firebase/assets/images/update.png +0 -0
- package/templates/firebase/lib/features/notifications/ui/components/notifications_header.dart +0 -32
|
@@ -91,7 +91,13 @@ class HomeComponentsPage extends StatelessWidget {
|
|
|
91
91
|
Flexible(
|
|
92
92
|
child: Text(
|
|
93
93
|
row.name,
|
|
94
|
-
|
|
94
|
+
// Lighter weight (w500) to match the
|
|
95
|
+
// app's row/list scale instead of the
|
|
96
|
+
// heavier heading weight.
|
|
97
|
+
style: context.textTheme.titleMedium
|
|
98
|
+
?.copyWith(
|
|
99
|
+
fontWeight: FontWeight.w500,
|
|
100
|
+
),
|
|
95
101
|
),
|
|
96
102
|
),
|
|
97
103
|
if (_kReadyComponents.contains(
|
|
@@ -397,6 +397,10 @@ ComponentPreviewDefinition? getComponentPreviewDefinition(
|
|
|
397
397
|
label: 'With Label & Description',
|
|
398
398
|
builder: _buildTextAreaWithLabelDescription,
|
|
399
399
|
),
|
|
400
|
+
ComponentPreviewVariant(
|
|
401
|
+
label: 'Variants',
|
|
402
|
+
builder: _buildTextAreaVariants,
|
|
403
|
+
),
|
|
400
404
|
ComponentPreviewVariant(
|
|
401
405
|
label: 'States',
|
|
402
406
|
builder: _buildTextAreaStates,
|
|
@@ -2785,6 +2789,34 @@ Widget _buildTextAreaWithLabelDescription(BuildContext context) {
|
|
|
2785
2789
|
return const _TextAreaWithLabelDescriptionContent();
|
|
2786
2790
|
}
|
|
2787
2791
|
|
|
2792
|
+
Widget _buildTextAreaVariants(BuildContext context) {
|
|
2793
|
+
return const Column(
|
|
2794
|
+
mainAxisSize: MainAxisSize.min,
|
|
2795
|
+
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
2796
|
+
children: [
|
|
2797
|
+
KasyTextArea(
|
|
2798
|
+
label: 'Primary Variant',
|
|
2799
|
+
hint: 'Primary style text area',
|
|
2800
|
+
description: 'Default variant with primary styling',
|
|
2801
|
+
),
|
|
2802
|
+
SizedBox(height: KasySpacing.lg),
|
|
2803
|
+
KasyTextArea(
|
|
2804
|
+
label: 'Secondary Variant',
|
|
2805
|
+
hint: 'Secondary style text area',
|
|
2806
|
+
description: 'Secondary variant for surfaces',
|
|
2807
|
+
variant: KasyTextFieldVariant.secondary,
|
|
2808
|
+
),
|
|
2809
|
+
SizedBox(height: KasySpacing.lg),
|
|
2810
|
+
KasyTextArea(
|
|
2811
|
+
label: 'Flat Variant',
|
|
2812
|
+
hint: 'Flat style text area',
|
|
2813
|
+
description: 'Primary fill, hairline border, no shadow',
|
|
2814
|
+
variant: KasyTextFieldVariant.flat,
|
|
2815
|
+
),
|
|
2816
|
+
],
|
|
2817
|
+
);
|
|
2818
|
+
}
|
|
2819
|
+
|
|
2788
2820
|
Widget _buildTextAreaStates(BuildContext context) {
|
|
2789
2821
|
return const Column(
|
|
2790
2822
|
mainAxisSize: MainAxisSize.min,
|
|
@@ -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,163 +48,133 @@ 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
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
+
padding: const EdgeInsets.fromLTRB(
|
|
68
|
+
KasySpacing.pageHorizontalGutter,
|
|
69
|
+
KasySpacing.belowChromeContentGap,
|
|
70
|
+
KasySpacing.pageHorizontalGutter,
|
|
71
|
+
KasySpacing.xl,
|
|
83
72
|
),
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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,
|
|
73
|
+
child: Column(
|
|
74
|
+
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
75
|
+
children: [
|
|
76
|
+
KasyCard(
|
|
77
|
+
padding: const EdgeInsets.symmetric(
|
|
78
|
+
horizontal: KasySpacing.md,
|
|
79
|
+
vertical: KasySpacing.xs,
|
|
80
|
+
),
|
|
81
|
+
child: SettingsSwitchTile(
|
|
82
|
+
icon: KasyIcons.notification,
|
|
83
|
+
title: tr.toggleLabel,
|
|
84
|
+
value: state.enabled,
|
|
85
|
+
onChanged: notifier.setEnabled,
|
|
86
|
+
),
|
|
104
87
|
),
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
88
|
+
if (state.enabled) ...[
|
|
89
|
+
const SizedBox(height: KasySpacing.xl),
|
|
90
|
+
_FieldLabel(tr.typeLabel),
|
|
91
|
+
const SizedBox(height: KasySpacing.sm),
|
|
92
|
+
// Default hug mode scrolls horizontally when the labels don't
|
|
93
|
+
// fit (long localized strings) instead of overflowing the row.
|
|
94
|
+
KasyTabs(
|
|
95
|
+
tabs: [tr.daily, tr.weekly, tr.specificDate],
|
|
96
|
+
selectedIndex: _types.indexOf(state.type),
|
|
97
|
+
onTabSelected: (i) => notifier.setType(_types[i]),
|
|
98
|
+
),
|
|
99
|
+
// daily / weekly schedule by a wall-clock time (hour + minute);
|
|
100
|
+
// specificDate carries its own time inside the chosen date.
|
|
101
|
+
if (state.type == ReminderType.daily ||
|
|
102
|
+
state.type == ReminderType.weekly) ...[
|
|
103
|
+
const SizedBox(height: KasySpacing.lg),
|
|
104
|
+
_FieldLabel(tr.timeLabel),
|
|
105
|
+
const SizedBox(height: KasySpacing.sm),
|
|
106
|
+
_TimeTile(
|
|
107
|
+
hour: state.hour,
|
|
108
|
+
minute: state.minute,
|
|
109
|
+
onChanged: (h, m) => notifier.setTime(h, m),
|
|
110
|
+
),
|
|
111
|
+
],
|
|
112
|
+
if (state.type == ReminderType.weekly) ...[
|
|
113
|
+
const SizedBox(height: KasySpacing.lg),
|
|
114
|
+
_FieldLabel(tr.dayLabel),
|
|
115
|
+
const SizedBox(height: KasySpacing.sm),
|
|
116
|
+
_DaySelector(
|
|
117
|
+
current: state.dayOfWeek,
|
|
118
|
+
onChanged: notifier.setDayOfWeek,
|
|
119
|
+
),
|
|
120
|
+
],
|
|
121
|
+
if (state.type == ReminderType.specificDate) ...[
|
|
122
|
+
const SizedBox(height: KasySpacing.lg),
|
|
123
|
+
_FieldLabel(tr.dateLabel),
|
|
124
|
+
const SizedBox(height: KasySpacing.sm),
|
|
125
|
+
KasyDatePicker(
|
|
126
|
+
value: state.date,
|
|
127
|
+
minDate: DateTime.now(),
|
|
128
|
+
onChanged: (picked) {
|
|
129
|
+
// Keep the previously chosen time when the day changes.
|
|
130
|
+
final DateTime? base = state.date;
|
|
131
|
+
notifier.setDate(
|
|
132
|
+
DateTime(
|
|
133
|
+
picked.year,
|
|
134
|
+
picked.month,
|
|
135
|
+
picked.day,
|
|
136
|
+
base?.hour ?? 9,
|
|
137
|
+
base?.minute ?? 0,
|
|
138
|
+
),
|
|
139
|
+
);
|
|
140
|
+
},
|
|
141
|
+
),
|
|
142
|
+
const SizedBox(height: KasySpacing.lg),
|
|
143
|
+
_FieldLabel(tr.timeLabel),
|
|
144
|
+
const SizedBox(height: KasySpacing.sm),
|
|
145
|
+
_TimeTile(
|
|
146
|
+
hour: state.date?.hour ?? 9,
|
|
147
|
+
minute: state.date?.minute ?? 0,
|
|
148
|
+
onChanged: (h, m) {
|
|
149
|
+
// Keep the previously chosen day when the time changes.
|
|
150
|
+
final DateTime base = state.date ?? DateTime.now();
|
|
151
|
+
notifier.setDate(
|
|
152
|
+
DateTime(base.year, base.month, base.day, h, m),
|
|
153
|
+
);
|
|
154
|
+
},
|
|
155
|
+
),
|
|
156
|
+
],
|
|
157
|
+
],
|
|
158
|
+
],
|
|
159
|
+
),
|
|
131
160
|
),
|
|
132
|
-
],
|
|
133
|
-
selected: {current},
|
|
134
|
-
onSelectionChanged: (s) => onChanged(s.first),
|
|
135
|
-
style: ButtonStyle(
|
|
136
|
-
textStyle: WidgetStateProperty.all(context.textTheme.labelSmall),
|
|
137
161
|
),
|
|
138
162
|
);
|
|
139
163
|
}
|
|
140
164
|
}
|
|
141
165
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
final
|
|
166
|
+
/// Quiet group eyebrow above each field — the design-system section label
|
|
167
|
+
/// (small, gently tracked, muted), matching the Settings / sidebar pattern.
|
|
168
|
+
class _FieldLabel extends StatelessWidget {
|
|
169
|
+
final String label;
|
|
146
170
|
|
|
147
|
-
const
|
|
148
|
-
required this.hour,
|
|
149
|
-
required this.minute,
|
|
150
|
-
required this.onChanged,
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
String _pad(int v) => v.toString().padLeft(2, '0');
|
|
171
|
+
const _FieldLabel(this.label);
|
|
154
172
|
|
|
155
173
|
@override
|
|
156
174
|
Widget build(BuildContext context) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
),
|
|
203
|
-
),
|
|
175
|
+
return Text(
|
|
176
|
+
label,
|
|
177
|
+
style: KasyTextTheme.sectionLabel.copyWith(color: context.colors.muted),
|
|
204
178
|
);
|
|
205
179
|
}
|
|
206
180
|
}
|
|
@@ -217,12 +191,12 @@ class _DaySelector extends StatelessWidget {
|
|
|
217
191
|
final days = ['Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb', 'Dom'];
|
|
218
192
|
return Wrap(
|
|
219
193
|
spacing: KasySpacing.xs,
|
|
194
|
+
runSpacing: KasySpacing.xs,
|
|
220
195
|
children: List.generate(7, (i) {
|
|
221
196
|
final dayIndex = i + 1;
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
selected: selected,
|
|
197
|
+
return KasyChip(
|
|
198
|
+
label: days[i],
|
|
199
|
+
selected: current == dayIndex,
|
|
226
200
|
onSelected: (_) => onChanged(dayIndex),
|
|
227
201
|
);
|
|
228
202
|
}),
|
|
@@ -230,82 +204,64 @@ class _DaySelector extends StatelessWidget {
|
|
|
230
204
|
}
|
|
231
205
|
}
|
|
232
206
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
207
|
+
/// A tappable field that opens the time picker. Built on [KasyCard] so it
|
|
208
|
+
/// inherits the design-system surface, press depth and keyboard focus ring.
|
|
209
|
+
class _TimeTile extends StatelessWidget {
|
|
210
|
+
final int hour;
|
|
211
|
+
final int minute;
|
|
212
|
+
final void Function(int hour, int minute) onChanged;
|
|
236
213
|
|
|
237
|
-
const
|
|
214
|
+
const _TimeTile({
|
|
215
|
+
required this.hour,
|
|
216
|
+
required this.minute,
|
|
217
|
+
required this.onChanged,
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
String _pad(int v) => v.toString().padLeft(2, '0');
|
|
238
221
|
|
|
239
222
|
@override
|
|
240
223
|
Widget build(BuildContext context) {
|
|
241
|
-
|
|
242
|
-
|
|
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(
|
|
224
|
+
Future<void> pickTime() async {
|
|
225
|
+
final picked = await showTimePicker(
|
|
255
226
|
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
|
-
),
|
|
227
|
+
initialTime: TimeOfDay(hour: hour, minute: minute),
|
|
270
228
|
);
|
|
229
|
+
if (picked != null) {
|
|
230
|
+
onChanged(picked.hour, picked.minute);
|
|
231
|
+
}
|
|
271
232
|
}
|
|
272
233
|
|
|
273
|
-
return
|
|
274
|
-
|
|
275
|
-
borderRadius:
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
234
|
+
return KasyCard(
|
|
235
|
+
onTap: pickTime,
|
|
236
|
+
borderRadius: KasyRadius.mdBorderRadius,
|
|
237
|
+
padding: const EdgeInsets.symmetric(
|
|
238
|
+
horizontal: KasySpacing.md,
|
|
239
|
+
vertical: KasySpacing.smd,
|
|
240
|
+
),
|
|
241
|
+
child: Row(
|
|
242
|
+
children: [
|
|
243
|
+
Icon(
|
|
244
|
+
KasyIcons.time,
|
|
245
|
+
size: KasyIconSize.lg,
|
|
246
|
+
color: context.colors.primary,
|
|
284
247
|
),
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
248
|
+
const SizedBox(width: KasySpacing.sm),
|
|
249
|
+
Text(
|
|
250
|
+
'${_pad(hour)}:${_pad(minute)}',
|
|
251
|
+
style: context.textTheme.bodyLarge?.copyWith(
|
|
252
|
+
color: context.colors.onSurface,
|
|
253
|
+
fontWeight: FontWeight.w600,
|
|
254
|
+
),
|
|
288
255
|
),
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
-
],
|
|
256
|
+
const Spacer(),
|
|
257
|
+
Icon(
|
|
258
|
+
KasyIcons.arrowForwardIos,
|
|
259
|
+
size: KasyIconSize.xs,
|
|
260
|
+
color: context.colors.muted,
|
|
306
261
|
),
|
|
307
|
-
|
|
262
|
+
],
|
|
308
263
|
),
|
|
309
264
|
);
|
|
310
265
|
}
|
|
311
266
|
}
|
|
267
|
+
|
package/templates/firebase/lib/features/notifications/ui/components/notification_settings_sheet.dart
CHANGED
|
@@ -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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
);
|
package/templates/firebase/lib/features/notifications/ui/components/push_notification_switcher.dart
CHANGED
|
@@ -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,12 +257,9 @@ class _GroupLabel extends StatelessWidget {
|
|
|
257
257
|
),
|
|
258
258
|
child: Text(
|
|
259
259
|
label.toUpperCase(),
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
letterSpacing: 1.4,
|
|
264
|
-
fontWeight: FontWeight.w700,
|
|
265
|
-
),
|
|
260
|
+
// Design-system section-eyebrow role (same as Settings' section labels),
|
|
261
|
+
// instead of a bespoke 11/w700 style.
|
|
262
|
+
style: KasyTextTheme.sectionLabel.copyWith(color: context.colors.muted),
|
|
266
263
|
),
|
|
267
264
|
);
|
|
268
265
|
}
|