kasy-cli 1.32.0 → 1.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/kasy.js +66 -2
- package/docs/cli-reference.md +7 -7
- package/lib/commands/apple-web.js +222 -0
- package/lib/commands/configure.js +3 -91
- package/lib/commands/doctor.js +20 -0
- package/lib/commands/facebook.js +189 -0
- package/lib/commands/new.js +61 -11
- package/lib/commands/release-version.js +234 -0
- package/lib/commands/update.js +27 -0
- package/lib/scaffold/CHANGELOG.json +27 -0
- package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api.dart +24 -0
- package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api_interface.dart +15 -0
- package/lib/scaffold/backends/api/patch/lib/features/authentication/repositories/authentication_repository.dart +27 -0
- package/lib/scaffold/backends/api/patch/lib/features/subscriptions/api/entities/subscription_entity.dart +1 -0
- package/lib/scaffold/backends/firebase/setup-from-scratch.js +199 -21
- package/lib/scaffold/backends/patch-base-hashes.json +66 -0
- package/lib/scaffold/backends/supabase/deploy.js +92 -0
- package/lib/scaffold/backends/supabase/edge-functions/stripe-webhook/index.ts +2 -0
- package/lib/scaffold/backends/supabase/migrations/20240101000007_welcome_notification.sql +36 -23
- package/lib/scaffold/backends/supabase/migrations/20240101000014_subscription_trial_end.sql +6 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/authentication/api/authentication_api.dart +92 -3
- package/lib/scaffold/backends/supabase/patch/lib/features/authentication/repositories/authentication_repository.dart +36 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/subscriptions/api/entities/subscription_entity.dart +1 -0
- package/lib/scaffold/generate.js +53 -4
- package/lib/scaffold/shared/generator-utils.js +18 -6
- package/lib/utils/apple-web.js +147 -0
- package/lib/utils/facebook.js +162 -0
- package/lib/utils/i18n/messages-en.js +85 -0
- package/lib/utils/i18n/messages-es.js +85 -0
- package/lib/utils/i18n/messages-pt.js +85 -0
- package/package.json +5 -2
- package/templates/firebase/.firebase/hosting.YnVpbGQvd2Vi.cache +73 -0
- package/templates/firebase/AGENTS.md +170 -0
- package/templates/firebase/CLAUDE.md +16 -0
- package/templates/firebase/DESIGN_SYSTEM.md +269 -0
- package/templates/firebase/docs/auth-setup.en.md +4 -2
- package/templates/firebase/docs/auth-setup.es.md +4 -2
- package/templates/firebase/docs/auth-setup.pt.md +4 -2
- package/templates/firebase/firebase.json +56 -1
- package/templates/firebase/functions/src/authentication/triggers.ts +10 -6
- package/templates/firebase/functions/src/core/data/entities/subscription_entity.ts +5 -0
- package/templates/firebase/functions/src/core/data/entities/user_entity.ts +9 -1
- package/templates/firebase/functions/src/core/data/repositories/user_repository.ts +27 -0
- package/templates/firebase/functions/src/subscriptions/models/subscriptions.ts +3 -0
- package/templates/firebase/functions/src/subscriptions/stripe_functions.ts +4 -0
- package/templates/firebase/lib/components/components.dart +1 -0
- package/templates/firebase/lib/components/kasy_alert.dart +0 -1
- package/templates/firebase/lib/components/kasy_app_bar.dart +35 -17
- package/templates/firebase/lib/components/kasy_bottom_sheet.dart +0 -1
- package/templates/firebase/lib/components/kasy_date_picker.dart +0 -5
- package/templates/firebase/lib/components/kasy_dialog.dart +0 -1
- package/templates/firebase/lib/components/kasy_otp_verification_bottom_sheet.dart +1 -1
- package/templates/firebase/lib/components/kasy_screen.dart +114 -0
- package/templates/firebase/lib/components/kasy_sidebar.dart +189 -120
- package/templates/firebase/lib/components/kasy_text_area.dart +0 -1
- package/templates/firebase/lib/components/kasy_text_field.dart +0 -1
- package/templates/firebase/lib/components/kasy_text_field_otp.dart +0 -1
- package/templates/firebase/lib/components/kasy_toast.dart +108 -73
- package/templates/firebase/lib/core/app_update/app_update_repository.dart +54 -0
- package/templates/firebase/lib/core/app_update/app_update_status.dart +14 -0
- package/templates/firebase/lib/core/app_update/update_available_sheet.dart +70 -0
- package/templates/firebase/lib/core/bottom_menu/active_tab_notifier.dart +40 -3
- package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +82 -34
- package/templates/firebase/lib/core/bottom_menu/web_content_wrapper.dart +6 -6
- package/templates/firebase/lib/core/bottom_menu/web_url.dart +20 -0
- package/templates/firebase/lib/core/chrome/chrome_visibility.dart +22 -0
- package/templates/firebase/lib/core/config/features.dart +5 -0
- package/templates/firebase/lib/core/data/api/remote_config_api.dart +38 -1
- package/templates/firebase/lib/core/guards/guard.dart +16 -2
- package/templates/firebase/lib/core/icons/kasy_icons.dart +3 -0
- package/templates/firebase/lib/core/rating/widgets/rate_banner.dart +2 -5
- package/templates/firebase/lib/core/rating/widgets/review_popup.dart +48 -124
- package/templates/firebase/lib/core/shared_preferences/shared_preferences.dart +14 -0
- package/templates/firebase/lib/core/states/components/maybe_show_update_available.dart +32 -0
- package/templates/firebase/lib/core/states/logout_action.dart +5 -1
- package/templates/firebase/lib/core/states/user_state_notifier.dart +85 -14
- package/templates/firebase/lib/core/theme/responsive_text_theme.dart +69 -0
- package/templates/firebase/lib/core/theme/texts.dart +90 -57
- package/templates/firebase/lib/core/theme/type_scale.dart +77 -0
- package/templates/firebase/lib/core/theme/web_background_sync_web.dart +20 -6
- package/templates/firebase/lib/core/utils/image_bytes_loader.dart +12 -0
- package/templates/firebase/lib/core/utils/image_bytes_loader_io.dart +28 -0
- package/templates/firebase/lib/core/utils/image_bytes_loader_web.dart +56 -0
- package/templates/firebase/lib/core/web_screen_width.dart +15 -0
- package/templates/firebase/lib/core/web_screen_width_io.dart +3 -0
- package/templates/firebase/lib/core/web_screen_width_web.dart +10 -0
- package/templates/firebase/lib/core/web_viewport_scale.dart +61 -25
- package/templates/firebase/lib/core/widgets/focus_visibility.dart +89 -0
- package/templates/firebase/lib/core/widgets/update_bottom_sheet.dart +29 -126
- package/templates/firebase/lib/features/ai_chat/ai_chat_page.dart +11 -8
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_chat_composer.dart +21 -0
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_chat_conversation_view.dart +1 -2
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_list.dart +2 -3
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_tile.dart +1 -2
- package/templates/firebase/lib/features/authentication/api/auth_web_support.dart +12 -0
- package/templates/firebase/lib/features/authentication/api/auth_web_support_web.dart +25 -0
- package/templates/firebase/lib/features/authentication/api/authentication_api.dart +266 -0
- package/templates/firebase/lib/features/authentication/api/authentication_api_interface.dart +22 -0
- package/templates/firebase/lib/features/authentication/navigation/post_login_navigation.dart +32 -0
- package/templates/firebase/lib/features/authentication/providers/phone_auth_notifier.dart +7 -7
- package/templates/firebase/lib/features/authentication/providers/signin_state_provider.dart +34 -10
- package/templates/firebase/lib/features/authentication/providers/signup_state_provider.dart +2 -2
- package/templates/firebase/lib/features/authentication/repositories/authentication_repository.dart +37 -0
- package/templates/firebase/lib/features/authentication/repositories/exceptions/authentication_exceptions.dart +8 -0
- package/templates/firebase/lib/features/authentication/ui/components/otp_verification.dart +2 -2
- package/templates/firebase/lib/features/authentication/ui/components/phone_input.dart +2 -2
- package/templates/firebase/lib/features/authentication/ui/signin_page.dart +80 -15
- package/templates/firebase/lib/features/authentication/ui/signup_page.dart +20 -14
- package/templates/firebase/lib/features/authentication/ui/widgets/auth_card_scaffold.dart +2 -2
- package/templates/firebase/lib/features/authentication/ui/widgets/recover_password_result.dart +2 -2
- package/templates/firebase/lib/features/feedbacks/ui/widgets/add_feature_button.dart +0 -1
- package/templates/firebase/lib/features/feedbacks/ui/widgets/feature_card.dart +1 -2
- package/templates/firebase/lib/features/home/design_system_page.dart +134 -67
- package/templates/firebase/lib/features/home/home_components_page.dart +8 -1
- package/templates/firebase/lib/features/home/home_components_preview_page.dart +1 -3
- package/templates/firebase/lib/features/home/home_components_preview_registry.dart +186 -56
- package/templates/firebase/lib/features/home/home_page.dart +4 -0
- package/templates/firebase/lib/features/local_reminders/ui/reminder_page.dart +169 -208
- package/templates/firebase/lib/features/notifications/providers/notifications_provider.dart +10 -0
- package/templates/firebase/lib/features/notifications/ui/components/notification_settings_sheet.dart +2 -2
- package/templates/firebase/lib/features/notifications/ui/components/notification_tile.dart +21 -8
- package/templates/firebase/lib/features/notifications/ui/components/push_notification_switcher.dart +2 -2
- package/templates/firebase/lib/features/notifications/ui/notifications_page.dart +3 -4
- package/templates/firebase/lib/features/notifications/ui/widgets/notification_tile.dart +84 -128
- package/templates/firebase/lib/features/onboarding/providers/onboarding_model.dart +11 -0
- package/templates/firebase/lib/features/onboarding/providers/onboarding_provider.dart +30 -3
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_loader.dart +13 -4
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_illustration_scaffold.dart +3 -4
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +2 -4
- package/templates/firebase/lib/features/onboarding/ui/widgets/selectable_row_tile.dart +2 -1
- package/templates/firebase/lib/features/settings/settings_page.dart +152 -11
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_page.dart +58 -21
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_tab.dart +12 -12
- package/templates/firebase/lib/features/settings/ui/components/admin/send_push_notification_page.dart +1 -4
- package/templates/firebase/lib/features/settings/ui/components/avatar_component.dart +2 -2
- package/templates/firebase/lib/features/settings/ui/components/create_password_sheet.dart +141 -0
- package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +0 -1
- package/templates/firebase/lib/features/settings/ui/widgets/admin_card.dart +42 -38
- package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +17 -2
- package/templates/firebase/lib/features/subscriptions/api/entities/subscription_entity.dart +3 -0
- package/templates/firebase/lib/features/subscriptions/api/stripe_product.dart +12 -11
- package/templates/firebase/lib/features/subscriptions/repositories/subscription_repository.dart +25 -2
- package/templates/firebase/lib/features/subscriptions/ui/component/active_premium_content.dart +319 -143
- package/templates/firebase/lib/features/subscriptions/ui/component/paywall_minimal.dart +1 -5
- package/templates/firebase/lib/features/subscriptions/ui/component/paywall_row.dart +1 -5
- package/templates/firebase/lib/features/subscriptions/ui/component/paywall_with_switch.dart +2 -5
- package/templates/firebase/lib/features/subscriptions/ui/component/premium_content.dart +1 -4
- package/templates/firebase/lib/features/subscriptions/ui/widgets/feature_line.dart +1 -2
- package/templates/firebase/lib/features/subscriptions/ui/widgets/premium_banner.dart +2 -7
- package/templates/firebase/lib/features/subscriptions/ui/widgets/premium_feature.dart +2 -4
- package/templates/firebase/lib/features/subscriptions/ui/widgets/selectable_col.dart +1 -4
- package/templates/firebase/lib/features/subscriptions/ui/widgets/selectable_row.dart +0 -3
- package/templates/firebase/lib/i18n/en.i18n.json +54 -7
- package/templates/firebase/lib/i18n/es.i18n.json +54 -7
- package/templates/firebase/lib/i18n/pt.i18n.json +54 -7
- package/templates/firebase/lib/main.dart +11 -2
- package/templates/firebase/lib/router.dart +94 -13
- package/templates/firebase/pubspec.yaml +1 -1
- package/templates/firebase/test/core/data/api/fake_remote_config_api.dart +14 -0
- package/templates/firebase/test/core/states/user_state_notifier_test.dart +47 -3
- package/templates/firebase/test/core/web_viewport_scale_test.dart +68 -0
- package/templates/firebase/test/features/authentication/data/api/auth_api_fake.dart +15 -0
- package/templates/firebase/tool/design_check.dart +152 -0
- package/templates/firebase/web/index.html +162 -14
- package/templates/firebase/assets/images/review.png +0 -0
- package/templates/firebase/assets/images/update.png +0 -0
- package/templates/firebase/lib/core/guards/user_info_guard.dart +0 -61
- package/templates/firebase/lib/features/notifications/ui/components/notifications_header.dart +0 -32
|
@@ -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
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
final
|
|
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
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
|
274
|
-
|
|
275
|
-
borderRadius:
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
|
|
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
|
-
],
|
|
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);
|
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,11 +257,10 @@ class _GroupLabel extends StatelessWidget {
|
|
|
257
257
|
),
|
|
258
258
|
child: Text(
|
|
259
259
|
label.toUpperCase(),
|
|
260
|
-
|
|
261
|
-
|
|
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
|
);
|