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
|
@@ -7,6 +7,12 @@ export interface UserEntityData {
|
|
|
7
7
|
creation_date: Timestamp;
|
|
8
8
|
last_update_date: Timestamp;
|
|
9
9
|
locale?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Set to true once the one-time welcome notification has been sent for this
|
|
12
|
+
* account. Guards against re-sending it when a device is re-registered after
|
|
13
|
+
* a logout/login cycle (which otherwise looks like a brand-new first device).
|
|
14
|
+
*/
|
|
15
|
+
welcome_sent?: boolean;
|
|
10
16
|
}
|
|
11
17
|
|
|
12
18
|
export type NewUserEntityData = Omit<UserEntityData, "id">;
|
|
@@ -20,7 +26,8 @@ export class UserEntity {
|
|
|
20
26
|
email,
|
|
21
27
|
creation_date,
|
|
22
28
|
last_update_date,
|
|
23
|
-
locale
|
|
29
|
+
locale,
|
|
30
|
+
welcome_sent
|
|
24
31
|
}: UserEntityData
|
|
25
32
|
) {
|
|
26
33
|
this.id = id;
|
|
@@ -29,6 +36,7 @@ export class UserEntity {
|
|
|
29
36
|
this.creation_date = creation_date;
|
|
30
37
|
this.last_update_date = last_update_date;
|
|
31
38
|
this.locale = locale;
|
|
39
|
+
this.welcome_sent = welcome_sent;
|
|
32
40
|
}
|
|
33
41
|
|
|
34
42
|
static fromData(data: UserEntityData): UserEntity {
|
|
@@ -46,6 +46,33 @@ export class UserRepository {
|
|
|
46
46
|
});
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Atomically claims the one-time welcome for a user.
|
|
51
|
+
*
|
|
52
|
+
* Returns true for exactly ONE caller per account — the one that should send
|
|
53
|
+
* the welcome notification — and false for every later call (e.g. a device
|
|
54
|
+
* re-registered after a logout/login cycle, or a second device registering at
|
|
55
|
+
* the same time). The flag flip and the read happen inside a transaction, so
|
|
56
|
+
* two concurrent device registrations can never both send the welcome.
|
|
57
|
+
*/
|
|
58
|
+
async claimWelcome(userId: string): Promise<boolean> {
|
|
59
|
+
const ref = this.db.collection("users").doc(userId);
|
|
60
|
+
return this.db.runTransaction(async (tx) => {
|
|
61
|
+
const snapshot = await tx.get(ref);
|
|
62
|
+
if (!snapshot.exists) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
if (snapshot.get("welcome_sent") === true) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
tx.update(ref, {
|
|
69
|
+
"welcome_sent": true,
|
|
70
|
+
"last_update_date": Timestamp.now(),
|
|
71
|
+
});
|
|
72
|
+
return true;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
49
76
|
async list({
|
|
50
77
|
limit = 100,
|
|
51
78
|
startAfter,
|
|
@@ -22,6 +22,7 @@ export interface SubscriptionData {
|
|
|
22
22
|
creationDate: Timestamp;
|
|
23
23
|
lastUpdate: Timestamp;
|
|
24
24
|
expirationDate?: Timestamp;
|
|
25
|
+
trialEnd?: Timestamp;
|
|
25
26
|
store: Stores;
|
|
26
27
|
productId: string;
|
|
27
28
|
// Denormalized copy of the subscriber's email. Firestore is a non-relational
|
|
@@ -41,6 +42,7 @@ export class Subscription {
|
|
|
41
42
|
creationDate,
|
|
42
43
|
lastUpdate,
|
|
43
44
|
expirationDate,
|
|
45
|
+
trialEnd,
|
|
44
46
|
store,
|
|
45
47
|
productId,
|
|
46
48
|
email,
|
|
@@ -52,6 +54,7 @@ export class Subscription {
|
|
|
52
54
|
this.creationDate = creationDate;
|
|
53
55
|
this.lastUpdate = lastUpdate;
|
|
54
56
|
this.expirationDate = expirationDate;
|
|
57
|
+
this.trialEnd = trialEnd;
|
|
55
58
|
this.store = store;
|
|
56
59
|
this.productId = productId;
|
|
57
60
|
this.email = email;
|
|
@@ -275,6 +275,9 @@ async function upsertFromStripeSubscription(sub: Stripe.Subscription): Promise<v
|
|
|
275
275
|
const expiration = periodEnd
|
|
276
276
|
? Timestamp.fromMillis(periodEnd * 1000)
|
|
277
277
|
: undefined;
|
|
278
|
+
const trialEnd = sub.trial_end
|
|
279
|
+
? Timestamp.fromMillis(sub.trial_end * 1000)
|
|
280
|
+
: undefined;
|
|
278
281
|
|
|
279
282
|
const subscription = new Subscription(
|
|
280
283
|
{
|
|
@@ -283,6 +286,7 @@ async function upsertFromStripeSubscription(sub: Stripe.Subscription): Promise<v
|
|
|
283
286
|
creationDate: existing?.creationDate ?? now,
|
|
284
287
|
lastUpdate: now,
|
|
285
288
|
expirationDate: expiration,
|
|
289
|
+
trialEnd,
|
|
286
290
|
store: Stores.STRIPE,
|
|
287
291
|
productId: priceId,
|
|
288
292
|
email: user.email,
|
|
@@ -21,6 +21,7 @@ export 'kasy_date_picker.dart';
|
|
|
21
21
|
export 'kasy_dialog.dart';
|
|
22
22
|
export 'kasy_image_viewer.dart';
|
|
23
23
|
export 'kasy_otp_verification_bottom_sheet.dart';
|
|
24
|
+
export 'kasy_screen.dart';
|
|
24
25
|
export 'kasy_sidebar.dart';
|
|
25
26
|
export 'kasy_skeleton.dart';
|
|
26
27
|
export 'kasy_status_tag.dart';
|
|
@@ -53,7 +53,6 @@ class KasyAlert extends StatelessWidget {
|
|
|
53
53
|
final bool hasMessage = message != null && message!.trim().isNotEmpty;
|
|
54
54
|
final TextStyle titleStyle =
|
|
55
55
|
context.textTheme.titleMedium?.copyWith(
|
|
56
|
-
fontWeight: FontWeight.w600,
|
|
57
56
|
color: emphasizeTitleWithTone
|
|
58
57
|
? palette.accent
|
|
59
58
|
: context.colors.onSurface,
|
|
@@ -186,7 +186,10 @@ class KasyTopScrollFade extends StatelessWidget {
|
|
|
186
186
|
// On web topInset = 0; enforce a 6 px minimum so the strip still exists.
|
|
187
187
|
final double solidHeight = topInset < 6 ? 6.0 : topInset;
|
|
188
188
|
final double totalHeight = solidHeight + _contentFade;
|
|
189
|
-
|
|
189
|
+
// The page background (NOT the bar's `surface`): when the bar hides on
|
|
190
|
+
// scroll, content must melt into the page color, not into a floating sliver
|
|
191
|
+
// of the bar's tint at the top.
|
|
192
|
+
final Color base = context.colors.background;
|
|
190
193
|
|
|
191
194
|
// Same gradient the grid cards use for their caption scrim — strong at the
|
|
192
195
|
// top, melting gradually to fully transparent at the bottom.
|
|
@@ -238,6 +241,12 @@ class KasyAppBar extends StatelessWidget {
|
|
|
238
241
|
/// or wrap a custom 44×44 chrome control with [KasyAppBarChromeTap] for the
|
|
239
242
|
/// same tap target semantics as the built-in orbs.
|
|
240
243
|
final Widget? trailing;
|
|
244
|
+
|
|
245
|
+
/// Replaces the default leading control (back button / spacer) — e.g. a menu
|
|
246
|
+
/// orb that opens a [KasySidebar] drawer. Use [KasyChromeOrbIconButton] so it
|
|
247
|
+
/// matches the trailing orbs' size and tap target.
|
|
248
|
+
final Widget? leading;
|
|
249
|
+
|
|
241
250
|
final bool useSafeArea;
|
|
242
251
|
|
|
243
252
|
/// When set, called instead of [ThemeProvider.toggle] for the theme orb
|
|
@@ -266,6 +275,7 @@ class KasyAppBar extends StatelessWidget {
|
|
|
266
275
|
this.style = KasyAppBarStyle.subpage,
|
|
267
276
|
this.onBack,
|
|
268
277
|
this.trailing,
|
|
278
|
+
this.leading,
|
|
269
279
|
this.useSafeArea = true,
|
|
270
280
|
this.onThemeToggle,
|
|
271
281
|
this.toolbarHeight,
|
|
@@ -285,26 +295,30 @@ class KasyAppBar extends StatelessWidget {
|
|
|
285
295
|
|
|
286
296
|
final double rowHeight =
|
|
287
297
|
toolbarHeight ?? kasyAppBarToolbarRowHeightOf(context);
|
|
288
|
-
final Widget
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
298
|
+
final Widget leadingWidget = leading ??
|
|
299
|
+
switch (style) {
|
|
300
|
+
KasyAppBarStyle.rootTab => SizedBox(
|
|
301
|
+
width: 44,
|
|
302
|
+
height: rowHeight,
|
|
303
|
+
),
|
|
304
|
+
_ => KasyChromeOrbIconButton(
|
|
305
|
+
icon: KasyIcons.arrowBackIos,
|
|
306
|
+
iconSize: KasyIconSize.md,
|
|
307
|
+
foregroundColor: orbFg,
|
|
308
|
+
fillColor: orbFill,
|
|
309
|
+
onPressed: handleBack,
|
|
310
|
+
tooltip: MaterialLocalizations.of(context).backButtonTooltip,
|
|
311
|
+
),
|
|
312
|
+
};
|
|
302
313
|
|
|
303
314
|
final TextStyle? titleRef =
|
|
304
315
|
context.textTheme.headlineSmall ?? context.textTheme.titleLarge;
|
|
305
316
|
final TextStyle? titleStyle = titleRef?.copyWith(
|
|
306
317
|
fontSize: (titleRef.fontSize ?? 24) * kasyAppBarTitleFontScale,
|
|
307
|
-
|
|
318
|
+
// w700 (not w900): a clean page-title weight that matches the Home /
|
|
319
|
+
// sidebar scale. w900 read as a heavy "black" that fought the rest of
|
|
320
|
+
// the chrome.
|
|
321
|
+
fontWeight: FontWeight.w700,
|
|
308
322
|
letterSpacing: -0.35,
|
|
309
323
|
color: context.colors.onBackground,
|
|
310
324
|
);
|
|
@@ -323,7 +337,7 @@ class KasyAppBar extends StatelessWidget {
|
|
|
323
337
|
children: [
|
|
324
338
|
Row(
|
|
325
339
|
children: [
|
|
326
|
-
|
|
340
|
+
leadingWidget,
|
|
327
341
|
const Spacer(),
|
|
328
342
|
_buildTrailing(context, orbFg, orbFill),
|
|
329
343
|
],
|
|
@@ -483,6 +497,10 @@ class KasyOverlayScaffold extends StatelessWidget {
|
|
|
483
497
|
Widget scrollView = CustomScrollView(
|
|
484
498
|
controller: scrollController,
|
|
485
499
|
physics: effectivePhysics,
|
|
500
|
+
// Dragging the form means the user is done with the focused field and
|
|
501
|
+
// wants to see what's below, so dismiss the keyboard. Chat views opt out
|
|
502
|
+
// of this wrapper on purpose (there scrolling means reading, not typing).
|
|
503
|
+
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
|
|
486
504
|
slivers: kasyOverlayPaddedSlivers(
|
|
487
505
|
context,
|
|
488
506
|
contentPadding: contentPadding,
|
|
@@ -105,7 +105,6 @@ class KasyBottomSheet extends StatelessWidget {
|
|
|
105
105
|
title!,
|
|
106
106
|
textAlign: centered ? TextAlign.center : TextAlign.start,
|
|
107
107
|
style: context.textTheme.titleLarge?.copyWith(
|
|
108
|
-
fontWeight: FontWeight.w600,
|
|
109
108
|
color: context.colors.onSurface,
|
|
110
109
|
),
|
|
111
110
|
),
|
|
@@ -1457,7 +1457,6 @@ class _CalendarPanelState extends State<_CalendarPanel> {
|
|
|
1457
1457
|
context.textTheme.labelLarge?.copyWith(
|
|
1458
1458
|
color: context.colors.onSurface,
|
|
1459
1459
|
fontWeight: FontWeight.w700,
|
|
1460
|
-
fontSize: 14,
|
|
1461
1460
|
);
|
|
1462
1461
|
|
|
1463
1462
|
return Column(
|
|
@@ -1623,7 +1622,6 @@ class _MonthGrid extends StatelessWidget {
|
|
|
1623
1622
|
style: context.textTheme.labelLarge?.copyWith(
|
|
1624
1623
|
color: context.colors.onSurface,
|
|
1625
1624
|
fontWeight: FontWeight.w700,
|
|
1626
|
-
fontSize: 14,
|
|
1627
1625
|
),
|
|
1628
1626
|
),
|
|
1629
1627
|
),
|
|
@@ -1688,7 +1686,6 @@ class _CalendarNavRow extends StatelessWidget {
|
|
|
1688
1686
|
style: context.textTheme.labelLarge?.copyWith(
|
|
1689
1687
|
color: c.onSurface,
|
|
1690
1688
|
fontWeight: FontWeight.w700,
|
|
1691
|
-
fontSize: 14,
|
|
1692
1689
|
),
|
|
1693
1690
|
),
|
|
1694
1691
|
const SizedBox(width: 4),
|
|
@@ -1903,7 +1900,6 @@ class _YearPickerContentState extends State<_YearPickerContent> {
|
|
|
1903
1900
|
? const Color(0xFFFCFCFC)
|
|
1904
1901
|
: c.onSurface,
|
|
1905
1902
|
fontWeight: FontWeight.w600,
|
|
1906
|
-
fontSize: 14,
|
|
1907
1903
|
),
|
|
1908
1904
|
),
|
|
1909
1905
|
),
|
|
@@ -2125,7 +2121,6 @@ class _DayCell extends StatelessWidget {
|
|
|
2125
2121
|
style: context.textTheme.labelLarge?.copyWith(
|
|
2126
2122
|
color: textColor,
|
|
2127
2123
|
fontWeight: FontWeight.w600,
|
|
2128
|
-
fontSize: 14,
|
|
2129
2124
|
height: 1.43,
|
|
2130
2125
|
),
|
|
2131
2126
|
),
|
|
@@ -152,7 +152,6 @@ class KasyDialog extends StatelessWidget {
|
|
|
152
152
|
|
|
153
153
|
final TextStyle titleStyle =
|
|
154
154
|
context.textTheme.titleLarge?.copyWith(
|
|
155
|
-
fontWeight: FontWeight.w600,
|
|
156
155
|
color: context.colors.onSurface,
|
|
157
156
|
) ??
|
|
158
157
|
TextStyle(fontWeight: FontWeight.w600, fontSize: 20, color: context.colors.onSurface);
|
|
@@ -111,7 +111,7 @@ class _KasyOtpVerificationBottomSheetState
|
|
|
111
111
|
Text(
|
|
112
112
|
'+1 234 567 8900',
|
|
113
113
|
style: sheetContext.textTheme.titleLarge?.copyWith(
|
|
114
|
-
fontWeight: FontWeight.
|
|
114
|
+
fontWeight: FontWeight.w700,
|
|
115
115
|
color: sheetContext.colors.onSurface,
|
|
116
116
|
),
|
|
117
117
|
textAlign: TextAlign.center,
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import 'package:flutter/material.dart';
|
|
2
|
+
import 'package:kasy_kit/components/kasy_card.dart';
|
|
3
|
+
import 'package:kasy_kit/core/theme/theme.dart';
|
|
4
|
+
import 'package:kasy_kit/core/widgets/page_background.dart';
|
|
5
|
+
|
|
6
|
+
/// Canonical screen scaffold. A new page that uses it inherits the internal
|
|
7
|
+
/// screen contract for free: the page background, a centred content column
|
|
8
|
+
/// capped at [maxContentWidth], the page gutter, scroll handling, and
|
|
9
|
+
/// (optionally) a [KasyCard] wrapper — all from design-system tokens.
|
|
10
|
+
///
|
|
11
|
+
/// Opt-in by design: it is a thin convenience over [Scaffold] (it forwards the
|
|
12
|
+
/// same `appBar`, `floatingActionButton`, `bottomNavigationBar` slots), not a
|
|
13
|
+
/// cage. Screens that need bespoke chrome can keep using [Scaffold] directly —
|
|
14
|
+
/// nothing here is forced, and any value is overridable.
|
|
15
|
+
///
|
|
16
|
+
/// ```dart
|
|
17
|
+
/// KasyScreen(
|
|
18
|
+
/// appBar: KasyAppBar.root(title: 'Settings'),
|
|
19
|
+
/// card: true,
|
|
20
|
+
/// child: Column(children: [...]),
|
|
21
|
+
/// )
|
|
22
|
+
/// ```
|
|
23
|
+
class KasyScreen extends StatelessWidget {
|
|
24
|
+
const KasyScreen({
|
|
25
|
+
super.key,
|
|
26
|
+
required this.child,
|
|
27
|
+
this.appBar,
|
|
28
|
+
this.scrollable = true,
|
|
29
|
+
this.maxContentWidth = 600,
|
|
30
|
+
this.padding,
|
|
31
|
+
this.card = false,
|
|
32
|
+
this.backgroundColor,
|
|
33
|
+
this.resizeToAvoidBottomInset,
|
|
34
|
+
this.floatingActionButton,
|
|
35
|
+
this.bottomNavigationBar,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
/// Page content.
|
|
39
|
+
final Widget child;
|
|
40
|
+
|
|
41
|
+
/// Optional top bar. Same slot as [Scaffold.appBar]; pass any
|
|
42
|
+
/// [PreferredSizeWidget].
|
|
43
|
+
final PreferredSizeWidget? appBar;
|
|
44
|
+
|
|
45
|
+
/// Wrap the content in a scroll view (default true). Set false for screens
|
|
46
|
+
/// that manage their own scrolling (e.g. a full-bleed list).
|
|
47
|
+
final bool scrollable;
|
|
48
|
+
|
|
49
|
+
/// Max width of the content column on wide screens. The internal-screen
|
|
50
|
+
/// contract is ~600; content is centred within the available width so it does
|
|
51
|
+
/// not stretch edge-to-edge on web/desktop.
|
|
52
|
+
final double maxContentWidth;
|
|
53
|
+
|
|
54
|
+
/// Padding around the content. Defaults to the horizontal page gutter plus
|
|
55
|
+
/// vertical breathing room.
|
|
56
|
+
final EdgeInsetsGeometry? padding;
|
|
57
|
+
|
|
58
|
+
/// Wrap the content in an elevated [KasyCard] (radius 16), matching the Home
|
|
59
|
+
/// reference look.
|
|
60
|
+
final bool card;
|
|
61
|
+
|
|
62
|
+
/// Page background. Defaults to the theme background token.
|
|
63
|
+
final Color? backgroundColor;
|
|
64
|
+
|
|
65
|
+
/// Forwarded to [Scaffold.resizeToAvoidBottomInset].
|
|
66
|
+
final bool? resizeToAvoidBottomInset;
|
|
67
|
+
|
|
68
|
+
/// Forwarded to [Scaffold.floatingActionButton].
|
|
69
|
+
final Widget? floatingActionButton;
|
|
70
|
+
|
|
71
|
+
/// Forwarded to [Scaffold.bottomNavigationBar].
|
|
72
|
+
final Widget? bottomNavigationBar;
|
|
73
|
+
|
|
74
|
+
@override
|
|
75
|
+
Widget build(BuildContext context) {
|
|
76
|
+
final EdgeInsetsGeometry resolvedPadding = padding ??
|
|
77
|
+
const EdgeInsets.symmetric(
|
|
78
|
+
horizontal: KasySpacing.pageHorizontalGutter,
|
|
79
|
+
vertical: KasySpacing.lg,
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
Widget content = ConstrainedBox(
|
|
83
|
+
constraints: BoxConstraints(maxWidth: maxContentWidth),
|
|
84
|
+
child: card ? KasyCard(child: child) : child,
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
content = Align(
|
|
88
|
+
alignment: Alignment.topCenter,
|
|
89
|
+
child: Padding(padding: resolvedPadding, child: content),
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
if (scrollable) {
|
|
93
|
+
content = SingleChildScrollView(
|
|
94
|
+
physics: const ClampingScrollPhysics(),
|
|
95
|
+
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
|
|
96
|
+
child: content,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return Scaffold(
|
|
101
|
+
backgroundColor: backgroundColor ?? context.colors.background,
|
|
102
|
+
resizeToAvoidBottomInset: resizeToAvoidBottomInset,
|
|
103
|
+
appBar: appBar,
|
|
104
|
+
floatingActionButton: floatingActionButton,
|
|
105
|
+
bottomNavigationBar: bottomNavigationBar,
|
|
106
|
+
body: Background(
|
|
107
|
+
bgColor: backgroundColor,
|
|
108
|
+
// top:false when an app bar is present — Scaffold already insets the
|
|
109
|
+
// body below it, so SafeArea only needs to guard the bottom/sides.
|
|
110
|
+
child: SafeArea(top: appBar == null, child: content),
|
|
111
|
+
),
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
}
|