kasy-cli 1.34.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 +24 -2
- package/docs/cli-reference.md +7 -7
- package/lib/commands/new.js +11 -9
- package/lib/commands/release-version.js +234 -0
- package/lib/commands/update.js +27 -0
- package/lib/scaffold/CHANGELOG.json +9 -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 +35 -21
- package/lib/scaffold/backends/patch-base-hashes.json +66 -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 +82 -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/utils/i18n/messages-en.js +23 -0
- package/lib/utils/i18n/messages-es.js +23 -0
- package/lib/utils/i18n/messages-pt.js +23 -0
- package/package.json +5 -2
- package/templates/firebase/.firebase/hosting.YnVpbGQvd2Vi.cache +73 -0
- package/templates/firebase/AGENTS.md +83 -0
- package/templates/firebase/DESIGN_SYSTEM.md +37 -2
- package/templates/firebase/docs/auth-setup.en.md +2 -0
- package/templates/firebase/docs/auth-setup.es.md +2 -0
- package/templates/firebase/docs/auth-setup.pt.md +2 -0
- 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/kasy_alert.dart +0 -1
- package/templates/firebase/lib/components/kasy_app_bar.dart +31 -16
- 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_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 +107 -41
- 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/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 +5 -3
- 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/features/ai_chat/ai_chat_page.dart +1 -2
- 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 +205 -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 +59 -0
- 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/home/design_system_page.dart +134 -67
- package/templates/firebase/lib/features/home/home_components_page.dart +4 -3
- package/templates/firebase/lib/features/home/home_components_preview_page.dart +1 -3
- package/templates/firebase/lib/features/home/home_components_preview_registry.dart +154 -56
- package/templates/firebase/lib/features/home/home_page.dart +4 -0
- package/templates/firebase/lib/features/local_reminders/ui/reminder_page.dart +8 -3
- package/templates/firebase/lib/features/notifications/providers/notifications_provider.dart +10 -0
- package/templates/firebase/lib/features/notifications/ui/notifications_page.dart +3 -1
- package/templates/firebase/lib/features/notifications/ui/widgets/notification_tile.dart +8 -3
- 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/settings/settings_page.dart +152 -11
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_page.dart +43 -15
- 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/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 +49 -3
- package/templates/firebase/lib/i18n/es.i18n.json +49 -3
- package/templates/firebase/lib/i18n/pt.i18n.json +49 -3
- package/templates/firebase/lib/main.dart +11 -2
- package/templates/firebase/lib/router.dart +92 -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/web/index.html +162 -14
- package/templates/firebase/lib/core/guards/user_info_guard.dart +0 -61
|
@@ -3,6 +3,7 @@ import 'package:flutter_test/flutter_test.dart';
|
|
|
3
3
|
import 'package:kasy_kit/core/data/api/http_client.dart';
|
|
4
4
|
import 'package:kasy_kit/core/data/models/user.dart';
|
|
5
5
|
import 'package:kasy_kit/core/data/repositories/user_repository.dart';
|
|
6
|
+
import 'package:kasy_kit/core/shared_preferences/shared_preferences.dart';
|
|
6
7
|
import 'package:kasy_kit/core/states/user_state_notifier.dart';
|
|
7
8
|
import 'package:kasy_kit/environments.dart';
|
|
8
9
|
import 'package:kasy_kit/features/authentication/repositories/authentication_repository.dart';
|
|
@@ -66,6 +67,9 @@ void main() {
|
|
|
66
67
|
],
|
|
67
68
|
);
|
|
68
69
|
|
|
70
|
+
// The notifier touches SharedPreferences (onboarding flag, biometric
|
|
71
|
+
// reset) during logout/onboarding, so the builder must be initialized.
|
|
72
|
+
await container.read(sharedPreferencesProvider).init();
|
|
69
73
|
return container;
|
|
70
74
|
}
|
|
71
75
|
|
|
@@ -175,11 +179,14 @@ void main() {
|
|
|
175
179
|
],
|
|
176
180
|
);
|
|
177
181
|
|
|
182
|
+
// The notifier touches SharedPreferences (onboarding flag, biometric
|
|
183
|
+
// reset) during logout/onboarding, so the builder must be initialized.
|
|
184
|
+
await container.read(sharedPreferencesProvider).init();
|
|
178
185
|
return container;
|
|
179
186
|
}
|
|
180
187
|
|
|
181
188
|
test(
|
|
182
|
-
'Should load user at startup,
|
|
189
|
+
'Should load user at startup, no account yet => anonymous guest with no id',
|
|
183
190
|
() async {
|
|
184
191
|
final testContainer = await initTestContainer();
|
|
185
192
|
final userStateNotifier = testContainer.read(
|
|
@@ -196,9 +203,40 @@ void main() {
|
|
|
196
203
|
isA<AnonymousUserData>(),
|
|
197
204
|
reason: 'user should be in unauthenticated state',
|
|
198
205
|
);
|
|
206
|
+
expect(
|
|
207
|
+
userStateNotifier.state.user.idOrNull,
|
|
208
|
+
isNull,
|
|
209
|
+
reason:
|
|
210
|
+
'no anonymous account is created eagerly anymore — it is created '
|
|
211
|
+
'lazily in continueAsGuest (onboarding end / "continue as guest")',
|
|
212
|
+
);
|
|
213
|
+
},
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
test(
|
|
217
|
+
'continueAsGuest => creates anonymous account with id and remembers onboarding',
|
|
218
|
+
() async {
|
|
219
|
+
final testContainer = await initTestContainer();
|
|
220
|
+
final userStateNotifier = testContainer.read(
|
|
221
|
+
userStateNotifierProvider.notifier,
|
|
222
|
+
);
|
|
223
|
+
await userStateNotifier.init();
|
|
224
|
+
await userStateNotifier.continueAsGuest();
|
|
225
|
+
|
|
226
|
+
expect(
|
|
227
|
+
userStateNotifier.state.user,
|
|
228
|
+
isA<AnonymousUserData>(),
|
|
229
|
+
reason: 'guest is anonymous',
|
|
230
|
+
);
|
|
199
231
|
expect(
|
|
200
232
|
userStateNotifier.state.user.idOrThrow,
|
|
201
233
|
'fake-user-id-anonymous',
|
|
234
|
+
reason: 'the anonymous account is created on demand here',
|
|
235
|
+
);
|
|
236
|
+
expect(
|
|
237
|
+
testContainer.read(sharedPreferencesProvider).getOnboardingCompleted(),
|
|
238
|
+
isTrue,
|
|
239
|
+
reason: 'onboarding is remembered so it is never shown again',
|
|
202
240
|
);
|
|
203
241
|
},
|
|
204
242
|
);
|
|
@@ -222,7 +260,7 @@ void main() {
|
|
|
222
260
|
},
|
|
223
261
|
);
|
|
224
262
|
|
|
225
|
-
test('on logout ->
|
|
263
|
+
test('on logout -> anonymous guest with no id (no re-signup)', () async {
|
|
226
264
|
final testContainer = await initTestContainer();
|
|
227
265
|
final userStateNotifier = testContainer.read(
|
|
228
266
|
userStateNotifierProvider.notifier,
|
|
@@ -238,7 +276,13 @@ void main() {
|
|
|
238
276
|
isA<AnonymousUserData>(),
|
|
239
277
|
reason: 'user should be anonymous',
|
|
240
278
|
);
|
|
241
|
-
expect(
|
|
279
|
+
expect(
|
|
280
|
+
userStateNotifier.state.user.idOrNull,
|
|
281
|
+
isNull,
|
|
282
|
+
reason:
|
|
283
|
+
'logout must not recreate an anonymous account (would pile up '
|
|
284
|
+
'orphan users); the user is sent to the sign-in screen instead',
|
|
285
|
+
);
|
|
242
286
|
});
|
|
243
287
|
});
|
|
244
288
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import 'package:flutter_test/flutter_test.dart';
|
|
2
|
+
import 'package:kasy_kit/core/web_viewport_scale.dart';
|
|
3
|
+
|
|
4
|
+
/// Locks the desktop web scaling behaviour so a future change to the constants or
|
|
5
|
+
/// the formula can't silently regress it. The headline rule, and the bug this
|
|
6
|
+
/// guards against: the desktop compensation must key off the SCREEN width (OS
|
|
7
|
+
/// scale), NOT the window width — merely resizing the browser window must not
|
|
8
|
+
/// shrink the UI; the extra shrink happens only when the screen itself is small.
|
|
9
|
+
void main() {
|
|
10
|
+
group('webViewportEffectiveScale', () {
|
|
11
|
+
test('no scaling below the desktop breakpoint (mobile/tablet web == native)',
|
|
12
|
+
() {
|
|
13
|
+
// The window width decides the breakpoint (the layout follows the window),
|
|
14
|
+
// so below it the scale is 1.0 regardless of the screen — same as native
|
|
15
|
+
// and the device preview.
|
|
16
|
+
expect(webViewportEffectiveScale(375), 1.0); // phone
|
|
17
|
+
expect(webViewportEffectiveScale(900), 1.0); // tablet
|
|
18
|
+
expect(webViewportEffectiveScale(900, screenWidth: 1470), 1.0);
|
|
19
|
+
expect(
|
|
20
|
+
webViewportEffectiveScale(kWebViewportScaleDesktopBreakpoint - 1),
|
|
21
|
+
1.0,
|
|
22
|
+
);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('desktop on a normal/large screen stays at the flat cap (0.95)', () {
|
|
26
|
+
// Unknown screen (null) → flat cap.
|
|
27
|
+
expect(webViewportEffectiveScale(1280), kWebViewportScale);
|
|
28
|
+
expect(webViewportEffectiveScale(1500), kWebViewportScale);
|
|
29
|
+
// Known large screen → still the flat cap, no compensation.
|
|
30
|
+
expect(webViewportEffectiveScale(1100, screenWidth: 1470), kWebViewportScale);
|
|
31
|
+
expect(webViewportEffectiveScale(1024, screenWidth: 1920), kWebViewportScale);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('resizing the window does NOT change the scale (this is the fix)', () {
|
|
35
|
+
// Same screen, different window widths → identical scale. The old formula
|
|
36
|
+
// keyed off the window and shrank here; that was the bug.
|
|
37
|
+
const screen = 1470.0;
|
|
38
|
+
expect(
|
|
39
|
+
webViewportEffectiveScale(1280, screenWidth: screen),
|
|
40
|
+
webViewportEffectiveScale(1050, screenWidth: screen),
|
|
41
|
+
);
|
|
42
|
+
expect(webViewportEffectiveScale(1050, screenWidth: screen), kWebViewportScale);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('high OS scale (small SCREEN) compensates below the cap', () {
|
|
46
|
+
// Drop below 0.95 only because the screen is small — independent of window.
|
|
47
|
+
expect(webViewportEffectiveScale(1024, screenWidth: 1024), closeTo(0.80, 1e-4));
|
|
48
|
+
expect(webViewportEffectiveScale(1097, screenWidth: 1097), closeTo(0.857, 1e-3));
|
|
49
|
+
// Even a wide window on a small (high-scale) screen compensates.
|
|
50
|
+
expect(webViewportEffectiveScale(1280, screenWidth: 1024), closeTo(0.80, 1e-4));
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('compensation pins the design width to the target on a small screen', () {
|
|
54
|
+
// scale = screen / target, so screen / scale == target (1280).
|
|
55
|
+
const screen = 1024.0;
|
|
56
|
+
final scale = webViewportEffectiveScale(1100, screenWidth: screen);
|
|
57
|
+
expect(screen / scale, closeTo(kWebViewportScaleTargetWidth, 1e-4));
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('the maxScale override is respected as the cap', () {
|
|
61
|
+
expect(webViewportEffectiveScale(1920, screenWidth: 1920, maxScale: 0.9), 0.9);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('an absurdly small screen is clamped to the 0.5 floor', () {
|
|
65
|
+
expect(webViewportEffectiveScale(1024, screenWidth: 300), 0.5);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
}
|
|
@@ -166,4 +166,19 @@ class FakeAuthenticationApi implements AuthenticationApi {
|
|
|
166
166
|
|
|
167
167
|
@override
|
|
168
168
|
Future<String?> getCurrentUserDisplayName() => Future.value(current != null ? 'Fake User' : null);
|
|
169
|
+
|
|
170
|
+
@override
|
|
171
|
+
Future<String?> getCurrentUserPhotoUrl() => Future.value();
|
|
172
|
+
|
|
173
|
+
@override
|
|
174
|
+
Future<List<String>> getLinkedProviders() => Future.value(const []);
|
|
175
|
+
|
|
176
|
+
@override
|
|
177
|
+
Future<void> setPassword(String password) => Future.value();
|
|
178
|
+
|
|
179
|
+
@override
|
|
180
|
+
Future<List<String>> linkableSocialProviders() => Future.value(const []);
|
|
181
|
+
|
|
182
|
+
@override
|
|
183
|
+
Future<void> linkSocialProvider(String provider) => Future.value();
|
|
169
184
|
}
|
|
@@ -24,6 +24,47 @@
|
|
|
24
24
|
<meta name="apple-mobile-web-app-title" content="kasy">
|
|
25
25
|
<link rel="apple-touch-icon" href="icons/Icon-192.png">
|
|
26
26
|
|
|
27
|
+
<!-- Browser chrome color (Safari address bar / status bar tint). Uses the
|
|
28
|
+
`surface` token — the SAME colour as the app bar that sits directly below
|
|
29
|
+
the status bar — so there is no seam between the Safari bar and the app bar.
|
|
30
|
+
Two media-query metas are the reliable baseline: iOS Safari honours these at
|
|
31
|
+
load and follows the SYSTEM light/dark automatically. At runtime the app
|
|
32
|
+
appends a higher-priority dynamic meta (web_background_sync_web.dart) so a
|
|
33
|
+
theme FORCED inside the app (different from the system) is reflected too. -->
|
|
34
|
+
<meta name="theme-color" content="#FFFFFF" media="(prefers-color-scheme: light)">
|
|
35
|
+
<meta name="theme-color" content="#18181B" media="(prefers-color-scheme: dark)">
|
|
36
|
+
|
|
37
|
+
<script id="theme-pref-fix">
|
|
38
|
+
// Resolve the theme to apply BEFORE Flutter boots, reading the preference the
|
|
39
|
+
// app saved (shared_preferences -> localStorage under "flutter.themeMode",
|
|
40
|
+
// JSON-encoded e.g. "dark"). This drives the splash background + splash logo
|
|
41
|
+
// via the data-theme attribute (CSS reacts to it reliably). "system" or a
|
|
42
|
+
// missing value falls back to the OS preference.
|
|
43
|
+
//
|
|
44
|
+
// NOTE: this does NOT touch <meta name="theme-color">. iOS Safari ignores
|
|
45
|
+
// theme-color created/changed via JS and only honours the STATIC media-query
|
|
46
|
+
// metas above — so removing them made Safari fall back to the page background
|
|
47
|
+
// (the wrong colour). We keep the static metas as the source of truth for the
|
|
48
|
+
// Safari status-bar tint; the Flutter sync still appends a dynamic meta for
|
|
49
|
+
// Chromium-based browsers, which DO honour it.
|
|
50
|
+
(function () {
|
|
51
|
+
var mode = 'system';
|
|
52
|
+
try {
|
|
53
|
+
var raw = window.localStorage.getItem('flutter.themeMode');
|
|
54
|
+
if (raw) { mode = JSON.parse(raw); }
|
|
55
|
+
} catch (e) {}
|
|
56
|
+
var dark = mode === 'dark'
|
|
57
|
+
? true
|
|
58
|
+
: mode === 'light'
|
|
59
|
+
? false
|
|
60
|
+
: !!(window.matchMedia &&
|
|
61
|
+
window.matchMedia('(prefers-color-scheme: dark)').matches);
|
|
62
|
+
|
|
63
|
+
// Drives the splash background + which splash logo shows (see CSS below).
|
|
64
|
+
document.documentElement.setAttribute('data-theme', dark ? 'dark' : 'light');
|
|
65
|
+
})();
|
|
66
|
+
</script>
|
|
67
|
+
|
|
27
68
|
<!-- Favicon -->
|
|
28
69
|
<link rel="icon" type="image/png" href="favicon.png">
|
|
29
70
|
|
|
@@ -31,7 +72,7 @@
|
|
|
31
72
|
<link rel="manifest" href="manifest.json">
|
|
32
73
|
|
|
33
74
|
|
|
34
|
-
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
|
|
75
|
+
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" name="viewport">
|
|
35
76
|
|
|
36
77
|
|
|
37
78
|
|
|
@@ -51,15 +92,60 @@
|
|
|
51
92
|
|
|
52
93
|
|
|
53
94
|
<style id="splash-screen-style">
|
|
95
|
+
:root { --vh: 1vh; }
|
|
96
|
+
|
|
97
|
+
/* iOS Safari sizes the Flutter canvas to documentElement.clientHeight (the
|
|
98
|
+
<html> box). `height: 100%` mis-resolves there and the canvas renders
|
|
99
|
+
into a smaller middle band (white bars top/bottom). Pin <html> to the
|
|
100
|
+
real visible height: 100dvh on Safari 15.4+, the JS-driven --vh on older. */
|
|
101
|
+
/* IMPORTANT: iOS Safari tints its status bar / address bar with the <body>
|
|
102
|
+
(and <html>) background-color — it ignores <meta name="theme-color"> once
|
|
103
|
+
the Flutter canvas covers the viewport. So we paint <html>/<body> with the
|
|
104
|
+
`surface` token (the app bar colour) to make the Safari bars match the app
|
|
105
|
+
bar. The splash and the area behind the canvas get the page `background`
|
|
106
|
+
via the #splash overlay and the Flutter host elements (set in JS), so only
|
|
107
|
+
the Safari chrome ends up surface-coloured. */
|
|
54
108
|
html {
|
|
55
|
-
height:
|
|
109
|
+
height: 100vh;
|
|
110
|
+
height: 100dvh;
|
|
111
|
+
height: calc(var(--vh, 1vh) * 100);
|
|
112
|
+
background-color: #FFFFFF;
|
|
56
113
|
}
|
|
57
114
|
|
|
58
115
|
body {
|
|
59
116
|
margin: 0;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
117
|
+
height: 100vh;
|
|
118
|
+
height: 100dvh;
|
|
119
|
+
height: calc(var(--vh, 1vh) * 100);
|
|
120
|
+
overflow: hidden;
|
|
121
|
+
overscroll-behavior: none;
|
|
122
|
+
background-color: #FFFFFF;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* Surface follows the APP theme (data-theme set by the script above),
|
|
126
|
+
falling back to the system theme when the attribute is absent (JS off). */
|
|
127
|
+
html[data-theme="dark"], html[data-theme="dark"] body {
|
|
128
|
+
background-color: #18181B;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/* Splash overlay keeps the page `background` colour (NOT surface) so the
|
|
132
|
+
loading screen still looks right; it covers the viewport until Flutter
|
|
133
|
+
removes it. */
|
|
134
|
+
#splash {
|
|
135
|
+
position: fixed;
|
|
136
|
+
inset: 0;
|
|
137
|
+
z-index: 2147483647;
|
|
138
|
+
background-color: #F5F5F5;
|
|
139
|
+
}
|
|
140
|
+
html[data-theme="dark"] #splash {
|
|
141
|
+
background-color: #060607;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* Flutter host chain (real tag names for the 3.41.x engine) must fill the
|
|
145
|
+
box so the glass-pane/scene match the now-correct <html> height. */
|
|
146
|
+
flutter-view, flt-glass-pane, flt-scene-host {
|
|
147
|
+
width: 100%;
|
|
148
|
+
height: 100%;
|
|
63
149
|
}
|
|
64
150
|
|
|
65
151
|
.center {
|
|
@@ -108,26 +194,88 @@
|
|
|
108
194
|
right: 0;
|
|
109
195
|
}
|
|
110
196
|
|
|
197
|
+
/* Splash logo: show the light or dark asset per the APP theme (data-theme). */
|
|
198
|
+
#splash .splash-dark { display: none; }
|
|
199
|
+
html[data-theme="dark"] #splash .splash-light { display: none; }
|
|
200
|
+
html[data-theme="dark"] #splash .splash-dark { display: block; }
|
|
201
|
+
|
|
202
|
+
/* Fallback when data-theme is absent (JS disabled): follow the system. */
|
|
111
203
|
@media (prefers-color-scheme: dark) {
|
|
112
|
-
body {
|
|
113
|
-
background-color: #
|
|
114
|
-
|
|
204
|
+
html:not([data-theme]), html:not([data-theme]) body {
|
|
205
|
+
background-color: #18181B;
|
|
206
|
+
}
|
|
207
|
+
html:not([data-theme]) #splash { background-color: #060607; }
|
|
208
|
+
html:not([data-theme]) #splash .splash-light { display: none; }
|
|
209
|
+
html:not([data-theme]) #splash .splash-dark { display: block; }
|
|
115
210
|
}
|
|
116
211
|
</style>
|
|
212
|
+
<script id="app-height-fix">
|
|
213
|
+
// Flutter engine on iOS reads documentElement.clientHeight (not visualViewport)
|
|
214
|
+
// to size the canvas (full_page_dimensions_provider.dart). On iOS Safari,
|
|
215
|
+
// window.innerHeight is the "large viewport" (address bar hidden) which is
|
|
216
|
+
// LARGER than the visible area — causing white bands top/bottom. The correct
|
|
217
|
+
// value is window.visualViewport.height, which tracks the real visible height.
|
|
218
|
+
(function () {
|
|
219
|
+
function visH() {
|
|
220
|
+
return window.visualViewport ? window.visualViewport.height : window.innerHeight;
|
|
221
|
+
}
|
|
222
|
+
function visW() {
|
|
223
|
+
return window.visualViewport ? window.visualViewport.width : window.innerWidth;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Override clientHeight/Width on the <html> element so the Flutter engine
|
|
227
|
+
// receives the real visible viewport size on every measurement.
|
|
228
|
+
try {
|
|
229
|
+
Object.defineProperty(document.documentElement, 'clientHeight', {
|
|
230
|
+
configurable: true,
|
|
231
|
+
get: visH,
|
|
232
|
+
});
|
|
233
|
+
Object.defineProperty(document.documentElement, 'clientWidth', {
|
|
234
|
+
configurable: true,
|
|
235
|
+
get: visW,
|
|
236
|
+
});
|
|
237
|
+
} catch (e) {
|
|
238
|
+
// If Safari refuses the override, the CSS --vh fallback below takes over.
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// --vh drives html { height: calc(var(--vh,1vh)*100) } in the CSS above.
|
|
242
|
+
// This is the CSS-level fallback: even if Object.defineProperty is blocked,
|
|
243
|
+
// the html element's rendered height equals visualViewport.height, so
|
|
244
|
+
// clientHeight resolves correctly after layout.
|
|
245
|
+
function setVh() {
|
|
246
|
+
document.documentElement.style.setProperty('--vh', (visH() * 0.01) + 'px');
|
|
247
|
+
}
|
|
248
|
+
setVh();
|
|
249
|
+
|
|
250
|
+
// Keep --vh in sync as the address bar shows/hides.
|
|
251
|
+
if (window.visualViewport) {
|
|
252
|
+
window.visualViewport.addEventListener('resize', setVh);
|
|
253
|
+
}
|
|
254
|
+
window.addEventListener('resize', setVh);
|
|
255
|
+
window.addEventListener('orientationchange', function () {
|
|
256
|
+
// Give the browser 300 ms to settle after rotation before re-measuring.
|
|
257
|
+
setTimeout(setVh, 300);
|
|
258
|
+
});
|
|
259
|
+
})();
|
|
260
|
+
</script>
|
|
117
261
|
<script id="splash-screen-script">
|
|
118
262
|
function removeSplashFromWeb() {
|
|
119
263
|
document.getElementById("splash")?.remove();
|
|
120
264
|
document.getElementById("splash-branding")?.remove();
|
|
121
|
-
|
|
265
|
+
// Keep the <body> surface-coloured (do NOT make it transparent): iOS Safari
|
|
266
|
+
// reads the <body> background-color for its status/address bar tint.
|
|
122
267
|
}
|
|
123
268
|
</script>
|
|
124
269
|
</head>
|
|
125
270
|
<body>
|
|
126
|
-
<
|
|
127
|
-
<
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
271
|
+
<div id="splash">
|
|
272
|
+
<img class="center splash-light" aria-hidden="true" alt=""
|
|
273
|
+
src="splash/img/light-1x.png"
|
|
274
|
+
srcset="splash/img/light-1x.png 1x, splash/img/light-2x.png 2x, splash/img/light-3x.png 3x, splash/img/light-4x.png 4x">
|
|
275
|
+
<img class="center splash-dark" aria-hidden="true" alt=""
|
|
276
|
+
src="splash/img/dark-1x.png"
|
|
277
|
+
srcset="splash/img/dark-1x.png 1x, splash/img/dark-2x.png 2x, splash/img/dark-3x.png 3x, splash/img/dark-4x.png 4x">
|
|
278
|
+
</div>
|
|
131
279
|
|
|
132
280
|
|
|
133
281
|
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import 'package:flutter/foundation.dart';
|
|
2
|
-
import 'package:flutter/material.dart';
|
|
3
|
-
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
4
|
-
import 'package:kasy_kit/core/config/features.dart';
|
|
5
|
-
import 'package:kasy_kit/core/data/models/user.dart';
|
|
6
|
-
import 'package:kasy_kit/core/guards/guard.dart';
|
|
7
|
-
import 'package:kasy_kit/core/states/user_state_notifier.dart';
|
|
8
|
-
import 'package:kasy_kit/core/theme/theme.dart';
|
|
9
|
-
|
|
10
|
-
class UserInfosGuard extends ConsumerWidget {
|
|
11
|
-
final Widget child;
|
|
12
|
-
final String fallbackRoute;
|
|
13
|
-
|
|
14
|
-
const UserInfosGuard({
|
|
15
|
-
super.key,
|
|
16
|
-
required this.child,
|
|
17
|
-
required this.fallbackRoute,
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
@override
|
|
21
|
-
Widget build(BuildContext context, WidgetRef ref) {
|
|
22
|
-
final authState = ref.watch(userStateNotifierProvider);
|
|
23
|
-
if (authState.user.isLoading) {
|
|
24
|
-
return Material(
|
|
25
|
-
color: context.colors.background,
|
|
26
|
-
child: const Center(
|
|
27
|
-
child: CircularProgressIndicator.adaptive(),
|
|
28
|
-
),
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Web: skip onboarding but require sign-in (or explicit guest choice).
|
|
33
|
-
// The /signin page's "Continue without account" button calls onOnboarded()
|
|
34
|
-
// which marks isOnboarded=true locally, allowing the guard to pass.
|
|
35
|
-
if (kIsWeb && withWeb) {
|
|
36
|
-
return Guard(
|
|
37
|
-
canActivate: Future.value(
|
|
38
|
-
authState.user.idOrNull != null || authState.user.isOnboarded,
|
|
39
|
-
),
|
|
40
|
-
fallbackRoute: '/signin',
|
|
41
|
-
child: child,
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Native: all users (including guests with no ID) go through onboarding.
|
|
46
|
-
// Guests are never forced to /signin here; individual features that
|
|
47
|
-
// require authentication redirect to /signin on their own.
|
|
48
|
-
// Authenticated users skip onboarding: they already committed to an account
|
|
49
|
-
// (e.g. returning user signing back in after the anonymous session was
|
|
50
|
-
// discarded by `credential-already-in-use`).
|
|
51
|
-
return Guard(
|
|
52
|
-
canActivate: Future.value(
|
|
53
|
-
!withOnboarding ||
|
|
54
|
-
authState.user.isOnboarded ||
|
|
55
|
-
authState.user is AuthenticatedUserData,
|
|
56
|
-
),
|
|
57
|
-
fallbackRoute: fallbackRoute,
|
|
58
|
-
child: child,
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
}
|