kasy-cli 1.31.14 → 1.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commands/new.js +15 -1
- package/lib/scaffold/CHANGELOG.json +9 -0
- package/lib/scaffold/backends/api/patch/README.md +87 -2
- package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api.dart +34 -0
- package/lib/scaffold/backends/api/patch/lib/features/subscriptions/api/stripe_backend_api.dart +12 -2
- package/lib/scaffold/backends/firebase/setup-from-scratch.js +22 -0
- package/lib/scaffold/backends/supabase/edge-functions/send-push-notification/README.md +26 -22
- package/lib/scaffold/backends/supabase/edge-functions/send-push-notification/index.ts +8 -11
- package/lib/scaffold/backends/supabase/edge-functions/stripe-create-checkout-session/index.ts +3 -1
- package/lib/scaffold/backends/supabase/edge-functions/stripe-create-portal-session/index.ts +60 -3
- package/lib/scaffold/backends/supabase/patch/lib/features/authentication/api/authentication_api.dart +12 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/subscriptions/api/stripe_backend_api.dart +12 -2
- package/lib/scaffold/backends/supabase/pubspec.yaml.tpl +3 -2
- package/lib/scaffold/generate.js +1 -1
- package/lib/scaffold/shared/generator-utils.js +22 -3
- package/lib/utils/i18n/messages-en.js +2 -0
- package/lib/utils/i18n/messages-es.js +2 -0
- package/lib/utils/i18n/messages-pt.js +2 -0
- package/package.json +2 -2
- package/templates/firebase/docs/auth-setup.en.md +7 -1
- package/templates/firebase/docs/auth-setup.es.md +7 -1
- package/templates/firebase/docs/auth-setup.pt.md +7 -1
- package/templates/firebase/functions/src/subscriptions/stripe_functions.ts +79 -5
- package/templates/firebase/lib/components/kasy_accordion.dart +2 -2
- package/templates/firebase/lib/components/kasy_alert.dart +1 -1
- package/templates/firebase/lib/components/kasy_app_bar.dart +3 -3
- package/templates/firebase/lib/components/kasy_bottom_sheet.dart +1 -1
- package/templates/firebase/lib/components/kasy_button.dart +8 -8
- package/templates/firebase/lib/components/kasy_chip.dart +1 -1
- package/templates/firebase/lib/components/kasy_date_picker.dart +26 -21
- package/templates/firebase/lib/components/kasy_dialog.dart +2 -2
- package/templates/firebase/lib/components/kasy_sidebar.dart +2 -2
- package/templates/firebase/lib/components/kasy_tabs.dart +2 -2
- package/templates/firebase/lib/components/kasy_text_area.dart +37 -5
- package/templates/firebase/lib/components/kasy_text_field.dart +77 -16
- package/templates/firebase/lib/components/kasy_toast.dart +1 -1
- package/templates/firebase/lib/components/kasy_web_header.dart +4 -3
- package/templates/firebase/lib/core/config/features.dart +13 -0
- package/templates/firebase/lib/core/dev_inspector/dev_inspector.dart +21 -0
- package/templates/firebase/lib/core/rating/widgets/rate_banner.dart +1 -1
- package/templates/firebase/lib/core/rating/widgets/review_popup.dart +1 -1
- package/templates/firebase/lib/core/theme/icon_sizes.dart +47 -0
- package/templates/firebase/lib/core/theme/shadows.dart +13 -0
- package/templates/firebase/lib/core/theme/texts.dart +32 -0
- package/templates/firebase/lib/core/theme/theme.dart +2 -0
- package/templates/firebase/lib/core/web_device_preview/web_device_preview.dart +3 -0
- package/templates/firebase/lib/core/web_viewport_scale.dart +23 -4
- package/templates/firebase/lib/core/widgets/update_bottom_sheet.dart +1 -1
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_tile.dart +1 -1
- package/templates/firebase/lib/features/authentication/ui/components/otp_verification.dart +1 -1
- package/templates/firebase/lib/features/authentication/ui/components/phone_input.dart +2 -1
- package/templates/firebase/lib/features/authentication/ui/recover_password_page.dart +3 -1
- package/templates/firebase/lib/features/authentication/ui/signin_page.dart +36 -14
- package/templates/firebase/lib/features/authentication/ui/signup_page.dart +27 -11
- package/templates/firebase/lib/features/authentication/ui/widgets/recover_password_result.dart +1 -1
- package/templates/firebase/lib/features/feedbacks/ui/feedback_page.dart +1 -1
- package/templates/firebase/lib/features/feedbacks/ui/widgets/add_feature_button.dart +1 -1
- package/templates/firebase/lib/features/feedbacks/ui/widgets/feature_card.dart +1 -1
- package/templates/firebase/lib/features/home/home_components_preview_registry.dart +22 -3
- package/templates/firebase/lib/features/home/home_image_grid.dart +1 -1
- package/templates/firebase/lib/features/local_reminders/ui/reminder_page.dart +2 -2
- package/templates/firebase/lib/features/notifications/ui/widgets/empty_notifications.dart +6 -1
- package/templates/firebase/lib/features/notifications/ui/widgets/notification_tile.dart +35 -38
- package/templates/firebase/lib/features/notifications/ui/widgets/permission_request_view.dart +1 -1
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_loader.dart +1 -1
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_module_mockups.dart +3 -3
- package/templates/firebase/lib/features/onboarding/ui/widgets/selectable_row_tile.dart +1 -1
- package/templates/firebase/lib/features/settings/settings_page.dart +264 -307
- package/templates/firebase/lib/features/settings/ui/components/avatar_component.dart +2 -2
- package/templates/firebase/lib/features/settings/ui/components/edit_name_sheet.dart +115 -0
- package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +2 -2
- package/templates/firebase/lib/features/settings/ui/widgets/settings_bottom_sheet_option_tile.dart +1 -1
- package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +13 -5
- package/templates/firebase/lib/features/subscriptions/api/stripe_backend_api.dart +12 -2
- package/templates/firebase/lib/features/subscriptions/api/stripe_payment_api.dart +7 -1
- package/templates/firebase/lib/features/subscriptions/providers/premium_page_provider.dart +11 -3
- package/templates/firebase/lib/features/subscriptions/ui/component/paywall_row.dart +1 -1
- package/templates/firebase/lib/features/subscriptions/ui/widgets/comparison_table.dart +1 -1
- package/templates/firebase/lib/features/subscriptions/ui/widgets/feature_line.dart +2 -2
- package/templates/firebase/lib/features/subscriptions/ui/widgets/premium_close_button.dart +1 -1
- package/templates/firebase/lib/features/subscriptions/ui/widgets/premium_feature.dart +1 -1
- package/templates/firebase/lib/features/subscriptions/ui/widgets/selectable_col.dart +3 -3
- package/templates/firebase/lib/features/subscriptions/ui/widgets/selectable_row.dart +1 -1
- package/templates/firebase/lib/i18n/en.i18n.json +8 -0
- package/templates/firebase/lib/i18n/es.i18n.json +8 -0
- package/templates/firebase/lib/i18n/pt.i18n.json +8 -0
- package/templates/firebase/pubspec.yaml +0 -1
- package/templates/firebase/web/stripe_success.html +64 -26
- package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/animations/movefade_anim.dart +0 -34
- package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/components/onboarding_att_setup.dart +0 -67
- package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +0 -183
- package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/animations/movefade_anim.dart +0 -34
- package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/components/onboarding_att_setup.dart +0 -67
- package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +0 -183
- package/templates/firebase/lib/features/authentication/ui/components/facebook_signin.dart +0 -19
- package/templates/firebase/login-redesign-preview.png +0 -0
|
@@ -13,10 +13,10 @@ import 'package:kasy_kit/core/security/biometric_ui_bundle.dart';
|
|
|
13
13
|
import 'package:kasy_kit/core/states/logout_action.dart';
|
|
14
14
|
import 'package:kasy_kit/core/states/user_state_notifier.dart';
|
|
15
15
|
import 'package:kasy_kit/core/theme/theme.dart';
|
|
16
|
-
import 'package:kasy_kit/core/widgets/kasy_focus_ring.dart';
|
|
17
16
|
import 'package:kasy_kit/core/widgets/kasy_hover.dart';
|
|
18
17
|
import 'package:kasy_kit/features/settings/ui/components/avatar_component.dart';
|
|
19
18
|
import 'package:kasy_kit/features/settings/ui/components/delete_user_component.dart';
|
|
19
|
+
import 'package:kasy_kit/features/settings/ui/components/edit_name_sheet.dart';
|
|
20
20
|
import 'package:kasy_kit/features/settings/ui/components/language_switcher.dart';
|
|
21
21
|
import 'package:kasy_kit/features/settings/ui/widgets/settings_tile.dart';
|
|
22
22
|
import 'package:kasy_kit/i18n/translations.g.dart';
|
|
@@ -31,6 +31,7 @@ class SettingsPage extends ConsumerWidget {
|
|
|
31
31
|
final tr = context.t.settings;
|
|
32
32
|
final user = ref.watch(userStateNotifierProvider).user;
|
|
33
33
|
final isAuthenticated = user is AuthenticatedUserData;
|
|
34
|
+
final String? userId = user.idOrNull;
|
|
34
35
|
final (displayName, displayEmail) = switch (user) {
|
|
35
36
|
final AuthenticatedUserData u => (
|
|
36
37
|
(u.name?.isNotEmpty ?? false) ? u.name! : u.email.split('@').first,
|
|
@@ -38,41 +39,43 @@ class SettingsPage extends ConsumerWidget {
|
|
|
38
39
|
),
|
|
39
40
|
_ => (tr.my_account, ''),
|
|
40
41
|
};
|
|
42
|
+
// The real stored name (may be empty) — what the editor seeds with, as
|
|
43
|
+
// opposed to the email-prefix fallback shown for display.
|
|
44
|
+
final String editableName = switch (user) {
|
|
45
|
+
final AuthenticatedUserData u => u.name ?? '',
|
|
46
|
+
_ => '',
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
final double width = MediaQuery.sizeOf(context).width;
|
|
50
|
+
// Two-pane master/detail (Vercel/Stripe/Claude) spans the whole desktop
|
|
51
|
+
// range and only collapses to a single column on tablet/phone.
|
|
52
|
+
final bool wide = width >= 1024;
|
|
53
|
+
// The "hide bars on scroll" toggle only makes sense on phones — tablet and
|
|
54
|
+
// desktop use the sidebar, which never hides.
|
|
55
|
+
final bool isPhone = width < 768;
|
|
41
56
|
|
|
42
57
|
return KasyOverlayScaffold(
|
|
43
58
|
title: tr.title,
|
|
44
59
|
appBarStyle: KasyAppBarStyle.rootTab,
|
|
45
60
|
hideAppBarOnScroll: true,
|
|
46
|
-
trailing: Builder(
|
|
47
|
-
builder: (ctx) => KasyChromeOrbIconButton(
|
|
48
|
-
icon: KasyIcons.logout,
|
|
49
|
-
iconSize: 20,
|
|
50
|
-
foregroundColor: ctx.colors.error,
|
|
51
|
-
onPressed: () => confirmLogout(ctx, ref),
|
|
52
|
-
),
|
|
53
|
-
),
|
|
54
61
|
slivers: [
|
|
55
62
|
SliverToBoxAdapter(
|
|
56
63
|
child: Builder(
|
|
57
64
|
builder: (context) {
|
|
58
|
-
// Breakpoint by viewport width — the industry standard and the
|
|
59
|
-
// project's own `large` cutoff (1024px ≈ Tailwind lg). Two-pane
|
|
60
|
-
// spans the whole desktop range and only collapses to a single
|
|
61
|
-
// column when entering tablet territory (the layout phones and
|
|
62
|
-
// tablets already use), instead of switching while still wide.
|
|
63
|
-
final bool wide = MediaQuery.sizeOf(context).width >= 1024;
|
|
64
|
-
|
|
65
65
|
if (!wide) {
|
|
66
66
|
return Column(
|
|
67
67
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
68
68
|
children: [
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
const _AccountAvatarHeader(),
|
|
70
|
+
const SizedBox(height: KasySpacing.md),
|
|
71
|
+
..._accountFields(
|
|
72
|
+
context,
|
|
73
|
+
userId: userId,
|
|
74
|
+
name: displayName,
|
|
75
|
+
editableName: editableName,
|
|
76
|
+
email: displayEmail,
|
|
72
77
|
isAuthenticated: isAuthenticated,
|
|
73
|
-
|
|
74
|
-
? null
|
|
75
|
-
: () => context.push('/signup'),
|
|
78
|
+
onRegister: () => context.push('/signup'),
|
|
76
79
|
),
|
|
77
80
|
const SizedBox(height: KasySpacing.xl),
|
|
78
81
|
..._sections(
|
|
@@ -80,19 +83,21 @@ class SettingsPage extends ConsumerWidget {
|
|
|
80
83
|
ref,
|
|
81
84
|
isAuthenticated: isAuthenticated,
|
|
82
85
|
isAdmin: user.isAdmin,
|
|
86
|
+
isPhone: isPhone,
|
|
83
87
|
),
|
|
84
88
|
const SizedBox(height: KasySpacing.xl),
|
|
85
89
|
],
|
|
86
90
|
);
|
|
87
91
|
}
|
|
88
92
|
|
|
89
|
-
// Desktop: a SaaS-style master/detail (Vercel/Stripe/Claude) —
|
|
90
|
-
// a section nav on the left, the selected section on the right.
|
|
91
93
|
return _SettingsDesktopView(
|
|
94
|
+
userId: userId,
|
|
92
95
|
name: displayName,
|
|
96
|
+
editableName: editableName,
|
|
93
97
|
email: displayEmail,
|
|
94
98
|
isAuthenticated: isAuthenticated,
|
|
95
99
|
isAdmin: user.isAdmin,
|
|
100
|
+
isPhone: isPhone,
|
|
96
101
|
);
|
|
97
102
|
},
|
|
98
103
|
),
|
|
@@ -101,95 +106,40 @@ class SettingsPage extends ConsumerWidget {
|
|
|
101
106
|
);
|
|
102
107
|
}
|
|
103
108
|
|
|
104
|
-
/// The settings sections
|
|
105
|
-
///
|
|
109
|
+
/// The settings sections below the account block — shared by the single
|
|
110
|
+
/// column (mobile/tablet) layout. Ends with sign-out, delete and version.
|
|
106
111
|
List<Widget> _sections(
|
|
107
112
|
BuildContext context,
|
|
108
113
|
WidgetRef ref, {
|
|
109
114
|
required bool isAuthenticated,
|
|
110
115
|
required bool isAdmin,
|
|
116
|
+
required bool isPhone,
|
|
111
117
|
}) {
|
|
112
118
|
final tr = context.t.settings;
|
|
113
119
|
return [
|
|
114
120
|
_SectionLabel(tr.section_preferences_label),
|
|
115
121
|
const SizedBox(height: KasySpacing.xs),
|
|
116
|
-
|
|
117
|
-
child: Wrap(
|
|
118
|
-
children: [
|
|
119
|
-
const ThemeSwitcher(),
|
|
120
|
-
const SettingsDivider(),
|
|
121
|
-
const HapticFeedbackSwitcher(),
|
|
122
|
-
const SettingsDivider(),
|
|
123
|
-
if (kShowHideChromeOnScrollSetting) ...[
|
|
124
|
-
const HideChromeOnScrollSwitcher(),
|
|
125
|
-
const SettingsDivider(),
|
|
126
|
-
],
|
|
127
|
-
const LanguageSwitcher(),
|
|
128
|
-
if (withLocalReminders) ...[
|
|
129
|
-
const SettingsDivider(),
|
|
130
|
-
SettingsTile(
|
|
131
|
-
icon: KasyIcons.notification,
|
|
132
|
-
title: tr.reminders,
|
|
133
|
-
onTap: () => context.push('/reminder'),
|
|
134
|
-
),
|
|
135
|
-
],
|
|
136
|
-
if (withFeedback) ...[
|
|
137
|
-
const SettingsDivider(),
|
|
138
|
-
SettingsTile(
|
|
139
|
-
icon: KasyIcons.message,
|
|
140
|
-
title: tr.feedback,
|
|
141
|
-
onTap: () => context.push('/feedback'),
|
|
142
|
-
),
|
|
143
|
-
],
|
|
144
|
-
if (withRevenuecat) ...[
|
|
145
|
-
const SettingsDivider(),
|
|
146
|
-
SettingsTile(
|
|
147
|
-
icon: KasyIcons.payment,
|
|
148
|
-
title: tr.premium,
|
|
149
|
-
onTap: () => context.push('/premium'),
|
|
150
|
-
),
|
|
151
|
-
],
|
|
152
|
-
],
|
|
153
|
-
),
|
|
154
|
-
),
|
|
122
|
+
_settingsGroup(_preferenceRows(context, isPhone: isPhone)),
|
|
155
123
|
if (isAuthenticated && !kIsWeb) ...[
|
|
156
124
|
const SizedBox(height: KasySpacing.xl),
|
|
157
125
|
_SectionLabel(tr.section_security_label),
|
|
158
126
|
const SizedBox(height: KasySpacing.xs),
|
|
159
|
-
const SettingsContainer(
|
|
160
|
-
child: BiometricSwitcher(),
|
|
161
|
-
),
|
|
127
|
+
const SettingsContainer(child: BiometricSwitcher()),
|
|
162
128
|
],
|
|
163
129
|
const SizedBox(height: KasySpacing.xl),
|
|
164
130
|
_SectionLabel(tr.section_support_label),
|
|
165
131
|
const SizedBox(height: KasySpacing.xs),
|
|
166
|
-
|
|
167
|
-
child: Wrap(
|
|
168
|
-
children: [
|
|
169
|
-
SettingsTile(
|
|
170
|
-
icon: KasyIcons.privacy,
|
|
171
|
-
title: tr.privacy,
|
|
172
|
-
onTap: () => launchUrl(Uri.parse('https://kasy.dev/privacy/')),
|
|
173
|
-
),
|
|
174
|
-
const SettingsDivider(),
|
|
175
|
-
SettingsTile(
|
|
176
|
-
icon: KasyIcons.help,
|
|
177
|
-
title: tr.support,
|
|
178
|
-
onTap: () => launchUrl(Uri.parse('https://kasy.dev/')),
|
|
179
|
-
),
|
|
180
|
-
],
|
|
181
|
-
),
|
|
182
|
-
),
|
|
132
|
+
_settingsGroup(_supportRows(context)),
|
|
183
133
|
// Admin entry — only for administrators or in development mode.
|
|
184
134
|
if (isAdmin || kDebugMode) ...[
|
|
185
135
|
const SizedBox(height: KasySpacing.xl),
|
|
186
|
-
|
|
187
|
-
|
|
136
|
+
_settingsGroup([
|
|
137
|
+
SettingsTile(
|
|
188
138
|
icon: Icons.admin_panel_settings_outlined,
|
|
189
139
|
title: t.admin_console.settings_entry.title,
|
|
190
140
|
onTap: () => context.push('/admin'),
|
|
191
141
|
),
|
|
192
|
-
),
|
|
142
|
+
]),
|
|
193
143
|
const SizedBox(height: KasySpacing.sm),
|
|
194
144
|
Padding(
|
|
195
145
|
padding: const EdgeInsets.only(left: KasySpacing.xs),
|
|
@@ -202,6 +152,10 @@ class SettingsPage extends ConsumerWidget {
|
|
|
202
152
|
),
|
|
203
153
|
],
|
|
204
154
|
const SizedBox(height: KasySpacing.xxl),
|
|
155
|
+
if (isAuthenticated) ...[
|
|
156
|
+
_settingsGroup([_LogoutRow(onTap: () => confirmLogout(context, ref))]),
|
|
157
|
+
const SizedBox(height: KasySpacing.xl),
|
|
158
|
+
],
|
|
205
159
|
const DeleteUserButton(),
|
|
206
160
|
const SizedBox(height: KasySpacing.xl),
|
|
207
161
|
const _VersionLabel(),
|
|
@@ -209,24 +163,132 @@ class SettingsPage extends ConsumerWidget {
|
|
|
209
163
|
}
|
|
210
164
|
}
|
|
211
165
|
|
|
166
|
+
// ─── Shared building blocks ────────────────────────────────────────────────
|
|
167
|
+
|
|
168
|
+
/// Wraps a list of settings rows in a refined card, inserting hairline
|
|
169
|
+
/// dividers between them. The card matches the design-system elevated surface
|
|
170
|
+
/// (soft shadow + hairline border) instead of the old flat block.
|
|
171
|
+
Widget _settingsGroup(List<Widget> rows) {
|
|
172
|
+
return SettingsContainer(
|
|
173
|
+
child: Column(
|
|
174
|
+
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
175
|
+
children: [
|
|
176
|
+
for (int i = 0; i < rows.length; i++) ...[
|
|
177
|
+
if (i > 0) const SettingsDivider(),
|
|
178
|
+
rows[i],
|
|
179
|
+
],
|
|
180
|
+
],
|
|
181
|
+
),
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/// The Preferences rows, shared between the mobile and desktop layouts.
|
|
186
|
+
/// Platform-aware: haptics only on native, hide-on-scroll only on phones.
|
|
187
|
+
List<Widget> _preferenceRows(BuildContext context, {required bool isPhone}) {
|
|
188
|
+
final tr = context.t.settings;
|
|
189
|
+
return [
|
|
190
|
+
const ThemeSwitcher(),
|
|
191
|
+
if (!kIsWeb) const HapticFeedbackSwitcher(),
|
|
192
|
+
if (kShowHideChromeOnScrollSetting && isPhone)
|
|
193
|
+
const HideChromeOnScrollSwitcher(),
|
|
194
|
+
const LanguageSwitcher(),
|
|
195
|
+
if (withLocalReminders)
|
|
196
|
+
SettingsTile(
|
|
197
|
+
icon: KasyIcons.notification,
|
|
198
|
+
title: tr.reminders,
|
|
199
|
+
onTap: () => context.push('/reminder'),
|
|
200
|
+
),
|
|
201
|
+
if (withFeedback)
|
|
202
|
+
SettingsTile(
|
|
203
|
+
icon: KasyIcons.message,
|
|
204
|
+
title: tr.feedback,
|
|
205
|
+
onTap: () => context.push('/feedback'),
|
|
206
|
+
),
|
|
207
|
+
if (withRevenuecat)
|
|
208
|
+
SettingsTile(
|
|
209
|
+
icon: KasyIcons.payment,
|
|
210
|
+
title: tr.premium,
|
|
211
|
+
onTap: () => context.push('/premium'),
|
|
212
|
+
),
|
|
213
|
+
];
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/// The Support rows, shared between the mobile and desktop layouts.
|
|
217
|
+
List<Widget> _supportRows(BuildContext context) {
|
|
218
|
+
final tr = context.t.settings;
|
|
219
|
+
return [
|
|
220
|
+
SettingsTile(
|
|
221
|
+
icon: KasyIcons.privacy,
|
|
222
|
+
title: tr.privacy,
|
|
223
|
+
onTap: () => launchUrl(Uri.parse('https://kasy.dev/privacy/')),
|
|
224
|
+
),
|
|
225
|
+
SettingsTile(
|
|
226
|
+
icon: KasyIcons.help,
|
|
227
|
+
title: tr.support,
|
|
228
|
+
onTap: () => launchUrl(Uri.parse('https://kasy.dev/')),
|
|
229
|
+
),
|
|
230
|
+
];
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/// The account identity fields, shared between the mobile and desktop layouts.
|
|
234
|
+
/// Signed-in users get an editable Name row and a read-only Email row; guests
|
|
235
|
+
/// get a single Register call to action.
|
|
236
|
+
List<Widget> _accountFields(
|
|
237
|
+
BuildContext context, {
|
|
238
|
+
required String? userId,
|
|
239
|
+
required String name,
|
|
240
|
+
required String editableName,
|
|
241
|
+
required String email,
|
|
242
|
+
required bool isAuthenticated,
|
|
243
|
+
required VoidCallback onRegister,
|
|
244
|
+
}) {
|
|
245
|
+
final tr = context.t.settings;
|
|
246
|
+
if (!isAuthenticated) {
|
|
247
|
+
return [
|
|
248
|
+
KasyButton(label: tr.register, expand: true, onPressed: onRegister),
|
|
249
|
+
];
|
|
250
|
+
}
|
|
251
|
+
return [
|
|
252
|
+
_settingsGroup([
|
|
253
|
+
_FieldRow(
|
|
254
|
+
label: tr.name_label,
|
|
255
|
+
value: name,
|
|
256
|
+
onTap: userId == null
|
|
257
|
+
? null
|
|
258
|
+
: () => showEditNameSheet(
|
|
259
|
+
context,
|
|
260
|
+
userId: userId,
|
|
261
|
+
email: email,
|
|
262
|
+
currentName: editableName,
|
|
263
|
+
),
|
|
264
|
+
),
|
|
265
|
+
_FieldRow(label: tr.email_label, value: email),
|
|
266
|
+
]),
|
|
267
|
+
];
|
|
268
|
+
}
|
|
269
|
+
|
|
212
270
|
class _SectionLabel extends StatelessWidget {
|
|
213
271
|
final String label;
|
|
214
272
|
const _SectionLabel(this.label);
|
|
215
273
|
|
|
216
274
|
@override
|
|
217
275
|
Widget build(BuildContext context) {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
276
|
+
// Quieter than before and aligned with the sidebar's section labels: small,
|
|
277
|
+
// gently tracked, muted — so it never out-shouts the content it heads.
|
|
278
|
+
return Padding(
|
|
279
|
+
padding: const EdgeInsets.only(left: KasySpacing.xs),
|
|
280
|
+
child: Text(
|
|
281
|
+
label,
|
|
282
|
+
style: KasyTextTheme.sectionLabel.copyWith(
|
|
283
|
+
color: context.colors.muted,
|
|
284
|
+
),
|
|
225
285
|
),
|
|
226
286
|
);
|
|
227
287
|
}
|
|
228
288
|
}
|
|
229
289
|
|
|
290
|
+
/// Grouped surface for settings rows — the design-system elevated card with a
|
|
291
|
+
/// settings-density radius.
|
|
230
292
|
class SettingsContainer extends StatelessWidget {
|
|
231
293
|
final Widget child;
|
|
232
294
|
|
|
@@ -234,121 +296,86 @@ class SettingsContainer extends StatelessWidget {
|
|
|
234
296
|
|
|
235
297
|
@override
|
|
236
298
|
Widget build(BuildContext context) {
|
|
237
|
-
return
|
|
299
|
+
return KasyCard(
|
|
300
|
+
borderRadius: BorderRadius.circular(KasyRadius.lg),
|
|
238
301
|
padding: const EdgeInsets.symmetric(
|
|
239
|
-
vertical: KasySpacing.smd,
|
|
240
302
|
horizontal: KasySpacing.md,
|
|
241
|
-
|
|
242
|
-
decoration: BoxDecoration(
|
|
243
|
-
borderRadius: KasyRadius.smBorderRadius,
|
|
244
|
-
color: context.colors.surface,
|
|
303
|
+
vertical: KasySpacing.xs,
|
|
245
304
|
),
|
|
246
305
|
child: child,
|
|
247
306
|
);
|
|
248
307
|
}
|
|
249
308
|
}
|
|
250
309
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
310
|
+
/// A label + value row used in the Account section. With [onTap] it is an
|
|
311
|
+
/// editable field (trailing chevron, keyboard-focusable); without it the value
|
|
312
|
+
/// is read-only (e.g. the email).
|
|
313
|
+
class _FieldRow extends StatelessWidget {
|
|
314
|
+
final String label;
|
|
315
|
+
final String value;
|
|
255
316
|
final VoidCallback? onTap;
|
|
256
317
|
|
|
257
|
-
const
|
|
258
|
-
super.key,
|
|
259
|
-
required this.title,
|
|
260
|
-
required this.subtitle,
|
|
261
|
-
required this.isAuthenticated,
|
|
262
|
-
this.onTap,
|
|
263
|
-
});
|
|
318
|
+
const _FieldRow({required this.label, required this.value, this.onTap});
|
|
264
319
|
|
|
265
320
|
@override
|
|
266
321
|
Widget build(BuildContext context) {
|
|
267
|
-
final Widget
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
children: [
|
|
274
|
-
const EditableUserAvatar(
|
|
275
|
-
diameter: 70,
|
|
322
|
+
final Widget row = Row(
|
|
323
|
+
children: [
|
|
324
|
+
Text(
|
|
325
|
+
label,
|
|
326
|
+
style: context.textTheme.titleSmall?.copyWith(
|
|
327
|
+
color: context.colors.onSurface,
|
|
276
328
|
),
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
applyHeightToFirstAscent: false,
|
|
288
|
-
applyHeightToLastDescent: false,
|
|
289
|
-
),
|
|
290
|
-
style: context.textTheme.titleMedium!.copyWith(
|
|
291
|
-
color: context.colors.onSurface,
|
|
292
|
-
fontWeight: FontWeight.w600,
|
|
293
|
-
height: 1.12,
|
|
294
|
-
),
|
|
295
|
-
),
|
|
296
|
-
if (subtitle.isNotEmpty) ...[
|
|
297
|
-
Text(
|
|
298
|
-
subtitle,
|
|
299
|
-
textHeightBehavior: const TextHeightBehavior(
|
|
300
|
-
applyHeightToFirstAscent: false,
|
|
301
|
-
applyHeightToLastDescent: false,
|
|
302
|
-
),
|
|
303
|
-
style: context.textTheme.bodySmall!.copyWith(
|
|
304
|
-
color: context.colors.muted,
|
|
305
|
-
height: 1.15,
|
|
306
|
-
),
|
|
307
|
-
),
|
|
308
|
-
],
|
|
309
|
-
if (!isAuthenticated) ...[
|
|
310
|
-
Text(
|
|
311
|
-
context.t.settings.register,
|
|
312
|
-
textHeightBehavior: const TextHeightBehavior(
|
|
313
|
-
applyHeightToFirstAscent: false,
|
|
314
|
-
applyHeightToLastDescent: false,
|
|
315
|
-
),
|
|
316
|
-
style: context.textTheme.bodyLarge!.copyWith(
|
|
317
|
-
color: context.colors.muted,
|
|
318
|
-
fontWeight: FontWeight.w500,
|
|
319
|
-
height: 1.15,
|
|
320
|
-
),
|
|
321
|
-
),
|
|
322
|
-
],
|
|
323
|
-
],
|
|
329
|
+
),
|
|
330
|
+
const SizedBox(width: KasySpacing.md),
|
|
331
|
+
Expanded(
|
|
332
|
+
child: Text(
|
|
333
|
+
value,
|
|
334
|
+
textAlign: TextAlign.right,
|
|
335
|
+
maxLines: 1,
|
|
336
|
+
overflow: TextOverflow.ellipsis,
|
|
337
|
+
style: context.textTheme.bodyMedium?.copyWith(
|
|
338
|
+
color: context.colors.muted,
|
|
324
339
|
),
|
|
325
340
|
),
|
|
326
|
-
|
|
341
|
+
),
|
|
342
|
+
if (onTap != null) ...[
|
|
343
|
+
const SizedBox(width: KasySpacing.xs),
|
|
344
|
+
const SettingsListChevron(),
|
|
327
345
|
],
|
|
328
|
-
|
|
346
|
+
],
|
|
347
|
+
);
|
|
348
|
+
if (onTap == null) {
|
|
349
|
+
return Padding(
|
|
350
|
+
padding: const EdgeInsets.symmetric(vertical: KasySpacing.sm),
|
|
351
|
+
child: row,
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
return KasyHover(
|
|
355
|
+
onTap: onTap!,
|
|
356
|
+
hoverEnabled: false,
|
|
357
|
+
pressEnabled: false,
|
|
358
|
+
focusable: true,
|
|
359
|
+
borderRadius: KasyRadius.smBorderRadius,
|
|
360
|
+
semanticLabel: label,
|
|
361
|
+
padding: const EdgeInsets.symmetric(vertical: KasySpacing.sm),
|
|
362
|
+
child: row,
|
|
329
363
|
);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
330
366
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
// The InkWell keeps its tap ripple but yields focus to the ring,
|
|
344
|
-
// so the keyboard outline matches every other Kasy control.
|
|
345
|
-
child: InkWell(
|
|
346
|
-
canRequestFocus: false,
|
|
347
|
-
onTap: onTap,
|
|
348
|
-
child: content,
|
|
349
|
-
),
|
|
350
|
-
)
|
|
351
|
-
: content,
|
|
367
|
+
/// The avatar header shown at the top of the Account block on the single-column
|
|
368
|
+
/// (mobile/tablet) layout. Tapping the avatar changes the photo; identity text
|
|
369
|
+
/// lives in the editable fields below, so nothing is shown twice.
|
|
370
|
+
class _AccountAvatarHeader extends StatelessWidget {
|
|
371
|
+
const _AccountAvatarHeader();
|
|
372
|
+
|
|
373
|
+
@override
|
|
374
|
+
Widget build(BuildContext context) {
|
|
375
|
+
return const Center(
|
|
376
|
+
child: Padding(
|
|
377
|
+
padding: EdgeInsets.only(top: KasySpacing.sm),
|
|
378
|
+
child: EditableUserAvatar(diameter: 64),
|
|
352
379
|
),
|
|
353
380
|
);
|
|
354
381
|
}
|
|
@@ -391,16 +418,22 @@ String _titleCase(String s) =>
|
|
|
391
418
|
}
|
|
392
419
|
|
|
393
420
|
class _SettingsDesktopView extends ConsumerStatefulWidget {
|
|
421
|
+
final String? userId;
|
|
394
422
|
final String name;
|
|
423
|
+
final String editableName;
|
|
395
424
|
final String email;
|
|
396
425
|
final bool isAuthenticated;
|
|
397
426
|
final bool isAdmin;
|
|
427
|
+
final bool isPhone;
|
|
398
428
|
|
|
399
429
|
const _SettingsDesktopView({
|
|
430
|
+
required this.userId,
|
|
400
431
|
required this.name,
|
|
432
|
+
required this.editableName,
|
|
401
433
|
required this.email,
|
|
402
434
|
required this.isAuthenticated,
|
|
403
435
|
required this.isAdmin,
|
|
436
|
+
required this.isPhone,
|
|
404
437
|
});
|
|
405
438
|
|
|
406
439
|
@override
|
|
@@ -450,12 +483,15 @@ class _SettingsDesktopViewState extends ConsumerState<_SettingsDesktopView> {
|
|
|
450
483
|
child: Align(
|
|
451
484
|
alignment: Alignment.topLeft,
|
|
452
485
|
child: ConstrainedBox(
|
|
453
|
-
constraints: const BoxConstraints(maxWidth:
|
|
486
|
+
constraints: const BoxConstraints(maxWidth: 600),
|
|
454
487
|
child: _DesktopDetail(
|
|
455
488
|
section: _selected,
|
|
489
|
+
userId: widget.userId,
|
|
456
490
|
name: widget.name,
|
|
491
|
+
editableName: widget.editableName,
|
|
457
492
|
email: widget.email,
|
|
458
493
|
isAuthenticated: widget.isAuthenticated,
|
|
494
|
+
isPhone: widget.isPhone,
|
|
459
495
|
),
|
|
460
496
|
),
|
|
461
497
|
),
|
|
@@ -579,7 +615,7 @@ class _NavTile extends StatelessWidget {
|
|
|
579
615
|
),
|
|
580
616
|
child: Row(
|
|
581
617
|
children: [
|
|
582
|
-
Icon(icon, size:
|
|
618
|
+
Icon(icon, size: KasyIconSize.rowLeading, color: fg),
|
|
583
619
|
const SizedBox(width: KasySpacing.sm),
|
|
584
620
|
Text(
|
|
585
621
|
label,
|
|
@@ -597,15 +633,21 @@ class _NavTile extends StatelessWidget {
|
|
|
597
633
|
|
|
598
634
|
class _DesktopDetail extends ConsumerWidget {
|
|
599
635
|
final _DesktopSection section;
|
|
636
|
+
final String? userId;
|
|
600
637
|
final String name;
|
|
638
|
+
final String editableName;
|
|
601
639
|
final String email;
|
|
602
640
|
final bool isAuthenticated;
|
|
641
|
+
final bool isPhone;
|
|
603
642
|
|
|
604
643
|
const _DesktopDetail({
|
|
605
644
|
required this.section,
|
|
645
|
+
required this.userId,
|
|
606
646
|
required this.name,
|
|
647
|
+
required this.editableName,
|
|
607
648
|
required this.email,
|
|
608
649
|
required this.isAuthenticated,
|
|
650
|
+
required this.isPhone,
|
|
609
651
|
});
|
|
610
652
|
|
|
611
653
|
@override
|
|
@@ -622,9 +664,8 @@ class _DesktopDetail extends ConsumerWidget {
|
|
|
622
664
|
),
|
|
623
665
|
child: Text(
|
|
624
666
|
title,
|
|
625
|
-
style:
|
|
667
|
+
style: KasyTextTheme.sectionTitle.copyWith(
|
|
626
668
|
color: context.colors.onSurface,
|
|
627
|
-
fontWeight: FontWeight.w700,
|
|
628
669
|
),
|
|
629
670
|
),
|
|
630
671
|
),
|
|
@@ -634,86 +675,26 @@ class _DesktopDetail extends ConsumerWidget {
|
|
|
634
675
|
}
|
|
635
676
|
|
|
636
677
|
List<Widget> _content(BuildContext context, WidgetRef ref) {
|
|
637
|
-
final tr = context.t.settings;
|
|
638
678
|
switch (section) {
|
|
639
679
|
case _DesktopSection.account:
|
|
640
680
|
return _accountContent(context, ref);
|
|
641
681
|
case _DesktopSection.preferences:
|
|
642
|
-
return [
|
|
643
|
-
SettingsContainer(
|
|
644
|
-
child: Wrap(
|
|
645
|
-
children: [
|
|
646
|
-
const ThemeSwitcher(),
|
|
647
|
-
const SettingsDivider(),
|
|
648
|
-
const HapticFeedbackSwitcher(),
|
|
649
|
-
const SettingsDivider(),
|
|
650
|
-
if (kShowHideChromeOnScrollSetting) ...[
|
|
651
|
-
const HideChromeOnScrollSwitcher(),
|
|
652
|
-
const SettingsDivider(),
|
|
653
|
-
],
|
|
654
|
-
const LanguageSwitcher(),
|
|
655
|
-
if (withLocalReminders) ...[
|
|
656
|
-
const SettingsDivider(),
|
|
657
|
-
SettingsTile(
|
|
658
|
-
icon: KasyIcons.notification,
|
|
659
|
-
title: tr.reminders,
|
|
660
|
-
onTap: () => context.push('/reminder'),
|
|
661
|
-
),
|
|
662
|
-
],
|
|
663
|
-
if (withFeedback) ...[
|
|
664
|
-
const SettingsDivider(),
|
|
665
|
-
SettingsTile(
|
|
666
|
-
icon: KasyIcons.message,
|
|
667
|
-
title: tr.feedback,
|
|
668
|
-
onTap: () => context.push('/feedback'),
|
|
669
|
-
),
|
|
670
|
-
],
|
|
671
|
-
if (withRevenuecat) ...[
|
|
672
|
-
const SettingsDivider(),
|
|
673
|
-
SettingsTile(
|
|
674
|
-
icon: KasyIcons.payment,
|
|
675
|
-
title: tr.premium,
|
|
676
|
-
onTap: () => context.push('/premium'),
|
|
677
|
-
),
|
|
678
|
-
],
|
|
679
|
-
],
|
|
680
|
-
),
|
|
681
|
-
),
|
|
682
|
-
];
|
|
682
|
+
return [_settingsGroup(_preferenceRows(context, isPhone: isPhone))];
|
|
683
683
|
case _DesktopSection.security:
|
|
684
684
|
return const [
|
|
685
685
|
SettingsContainer(child: BiometricSwitcher()),
|
|
686
686
|
];
|
|
687
687
|
case _DesktopSection.support:
|
|
688
|
-
return [
|
|
689
|
-
SettingsContainer(
|
|
690
|
-
child: Wrap(
|
|
691
|
-
children: [
|
|
692
|
-
SettingsTile(
|
|
693
|
-
icon: KasyIcons.privacy,
|
|
694
|
-
title: tr.privacy,
|
|
695
|
-
onTap: () =>
|
|
696
|
-
launchUrl(Uri.parse('https://kasy.dev/privacy/')),
|
|
697
|
-
),
|
|
698
|
-
const SettingsDivider(),
|
|
699
|
-
SettingsTile(
|
|
700
|
-
icon: KasyIcons.help,
|
|
701
|
-
title: tr.support,
|
|
702
|
-
onTap: () => launchUrl(Uri.parse('https://kasy.dev/')),
|
|
703
|
-
),
|
|
704
|
-
],
|
|
705
|
-
),
|
|
706
|
-
),
|
|
707
|
-
];
|
|
688
|
+
return [_settingsGroup(_supportRows(context))];
|
|
708
689
|
case _DesktopSection.admin:
|
|
709
690
|
return [
|
|
710
|
-
|
|
711
|
-
|
|
691
|
+
_settingsGroup([
|
|
692
|
+
SettingsTile(
|
|
712
693
|
icon: Icons.admin_panel_settings_outlined,
|
|
713
694
|
title: t.admin_console.settings_entry.title,
|
|
714
695
|
onTap: () => context.push('/admin'),
|
|
715
696
|
),
|
|
716
|
-
),
|
|
697
|
+
]),
|
|
717
698
|
const SizedBox(height: KasySpacing.sm),
|
|
718
699
|
Padding(
|
|
719
700
|
padding: const EdgeInsets.only(left: KasySpacing.xs),
|
|
@@ -729,51 +710,19 @@ class _DesktopDetail extends ConsumerWidget {
|
|
|
729
710
|
}
|
|
730
711
|
|
|
731
712
|
List<Widget> _accountContent(BuildContext context, WidgetRef ref) {
|
|
732
|
-
final tr = context.t.settings;
|
|
733
713
|
return [
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
child: Column(
|
|
743
|
-
crossAxisAlignment: CrossAxisAlignment.start,
|
|
744
|
-
mainAxisSize: MainAxisSize.min,
|
|
745
|
-
children: [
|
|
746
|
-
Text(
|
|
747
|
-
name,
|
|
748
|
-
style: context.textTheme.titleMedium?.copyWith(
|
|
749
|
-
color: context.colors.onSurface,
|
|
750
|
-
fontWeight: FontWeight.w600,
|
|
751
|
-
),
|
|
752
|
-
),
|
|
753
|
-
if (email.isNotEmpty)
|
|
754
|
-
Text(
|
|
755
|
-
email,
|
|
756
|
-
style: context.textTheme.bodySmall?.copyWith(
|
|
757
|
-
color: context.colors.muted,
|
|
758
|
-
),
|
|
759
|
-
),
|
|
760
|
-
],
|
|
761
|
-
),
|
|
762
|
-
),
|
|
763
|
-
if (!isAuthenticated)
|
|
764
|
-
KasyButton(
|
|
765
|
-
label: tr.register,
|
|
766
|
-
onPressed: () => context.push('/signup'),
|
|
767
|
-
),
|
|
768
|
-
],
|
|
769
|
-
),
|
|
770
|
-
),
|
|
714
|
+
..._accountFields(
|
|
715
|
+
context,
|
|
716
|
+
userId: userId,
|
|
717
|
+
name: name,
|
|
718
|
+
editableName: editableName,
|
|
719
|
+
email: email,
|
|
720
|
+
isAuthenticated: isAuthenticated,
|
|
721
|
+
onRegister: () => context.push('/signup'),
|
|
771
722
|
),
|
|
772
723
|
if (isAuthenticated) ...[
|
|
773
724
|
const SizedBox(height: KasySpacing.xl),
|
|
774
|
-
|
|
775
|
-
child: _LogoutRow(onTap: () => confirmLogout(context, ref)),
|
|
776
|
-
),
|
|
725
|
+
_settingsGroup([_LogoutRow(onTap: () => confirmLogout(context, ref))]),
|
|
777
726
|
],
|
|
778
727
|
const SizedBox(height: KasySpacing.xl),
|
|
779
728
|
const DeleteUserButton(),
|
|
@@ -783,7 +732,7 @@ class _DesktopDetail extends ConsumerWidget {
|
|
|
783
732
|
}
|
|
784
733
|
}
|
|
785
734
|
|
|
786
|
-
/// A danger-tinted "Sign out" row used in the
|
|
735
|
+
/// A danger-tinted "Sign out" row used in the Account section.
|
|
787
736
|
class _LogoutRow extends StatelessWidget {
|
|
788
737
|
final VoidCallback onTap;
|
|
789
738
|
|
|
@@ -801,11 +750,15 @@ class _LogoutRow extends StatelessWidget {
|
|
|
801
750
|
padding: const EdgeInsets.symmetric(vertical: KasySpacing.sm),
|
|
802
751
|
child: Row(
|
|
803
752
|
children: [
|
|
804
|
-
Icon(
|
|
753
|
+
Icon(
|
|
754
|
+
KasyIcons.logout,
|
|
755
|
+
size: KasyIconSize.rowLeading,
|
|
756
|
+
color: context.colors.error,
|
|
757
|
+
),
|
|
805
758
|
const SizedBox(width: KasySpacing.sm),
|
|
806
759
|
Text(
|
|
807
760
|
context.t.settings.logout,
|
|
808
|
-
style: context.textTheme.
|
|
761
|
+
style: context.textTheme.titleSmall?.copyWith(
|
|
809
762
|
color: context.colors.error,
|
|
810
763
|
),
|
|
811
764
|
),
|
|
@@ -874,7 +827,7 @@ class BiometricSwitcher extends ConsumerWidget {
|
|
|
874
827
|
),
|
|
875
828
|
Padding(
|
|
876
829
|
padding: const EdgeInsets.only(
|
|
877
|
-
left:
|
|
830
|
+
left: KasyIconSize.rowLeading + KasySpacing.sm,
|
|
878
831
|
bottom: KasySpacing.xs,
|
|
879
832
|
),
|
|
880
833
|
child: Text(
|
|
@@ -1011,12 +964,16 @@ class ThemeSwitcher extends StatelessWidget {
|
|
|
1011
964
|
padding: const EdgeInsets.symmetric(vertical: KasySpacing.sm),
|
|
1012
965
|
child: Row(
|
|
1013
966
|
children: [
|
|
1014
|
-
Icon(
|
|
967
|
+
Icon(
|
|
968
|
+
KasyIcons.palette,
|
|
969
|
+
size: KasyIconSize.rowLeading,
|
|
970
|
+
color: context.colors.onSurface,
|
|
971
|
+
),
|
|
1015
972
|
const SizedBox(width: KasySpacing.sm),
|
|
1016
973
|
Expanded(
|
|
1017
974
|
child: Text(
|
|
1018
975
|
tr.theme_title,
|
|
1019
|
-
style: context.textTheme.
|
|
976
|
+
style: context.textTheme.titleSmall?.copyWith(
|
|
1020
977
|
color: context.colors.onSurface,
|
|
1021
978
|
),
|
|
1022
979
|
),
|