kasy-cli 1.37.1 → 1.39.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/scaffold/CHANGELOG.json +23 -0
- package/lib/scaffold/backends/api/patch/README.md +15 -0
- package/lib/scaffold/backends/api/patch/lib/core/data/api/user_api.dart +18 -0
- package/lib/scaffold/backends/api/patch/lib/features/notifications/api/device_api.dart +11 -6
- package/lib/scaffold/backends/api/patch/lib/features/settings/ui/components/admin/admin_users_api.dart +9 -1
- package/lib/scaffold/backends/api/pubspec.yaml.tpl +1 -0
- package/lib/scaffold/backends/patch-base-hashes.json +6 -6
- package/lib/scaffold/backends/supabase/edge-functions/admin-list-users/index.ts +3 -1
- package/lib/scaffold/backends/supabase/edge-functions/send-push-notification/index.ts +3 -0
- package/lib/scaffold/backends/supabase/migrations/20240101000012_welcome_decouple_from_push.sql +62 -0
- package/lib/scaffold/backends/supabase/patch/lib/core/data/api/user_api.dart +8 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/notifications/api/device_api.dart +11 -6
- package/lib/scaffold/backends/supabase/patch/lib/features/settings/ui/components/admin/admin_users_api.dart +7 -0
- package/lib/scaffold/backends/supabase/pubspec.yaml.tpl +1 -0
- package/lib/scaffold/shared/generator-utils.js +12 -6
- package/package.json +1 -1
- package/templates/firebase/.firebase/hosting.YnVpbGQvd2Vi.cache +23 -23
- package/templates/firebase/AGENTS.md +7 -1
- package/templates/firebase/DESIGN_SYSTEM.md +35 -8
- package/templates/firebase/assets/icons/apple_black.svg +3 -0
- package/templates/firebase/assets/icons/apple_white.svg +4 -0
- package/templates/firebase/assets/icons/facebook.svg +49 -0
- package/templates/firebase/assets/icons/google.svg +1 -0
- package/templates/firebase/functions/src/admin/functions.ts +2 -0
- package/templates/firebase/functions/src/authentication/functions.ts +13 -7
- package/templates/firebase/functions/src/notifications/triggers.ts +6 -2
- package/templates/firebase/lib/components/components.dart +1 -1
- package/templates/firebase/lib/components/kasy_app_bar.dart +361 -20
- package/templates/firebase/lib/components/kasy_bottom_sheet.dart +283 -66
- package/templates/firebase/lib/components/kasy_card.dart +4 -0
- package/templates/firebase/lib/components/kasy_date_picker.dart +61 -46
- package/templates/firebase/lib/components/kasy_drop_down.dart +584 -0
- package/templates/firebase/lib/components/kasy_sidebar.dart +412 -31
- package/templates/firebase/lib/components/kasy_tabs.dart +31 -10
- package/templates/firebase/lib/components/kasy_text_field.dart +29 -7
- package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +29 -231
- package/templates/firebase/lib/core/bottom_menu/sidebar_focus.dart +224 -0
- package/templates/firebase/lib/core/bottom_menu/web_content_wrapper.dart +19 -9
- package/templates/firebase/lib/core/chrome/app_bar_config.dart +214 -0
- package/templates/firebase/lib/core/chrome/app_bar_scope.dart +102 -0
- package/templates/firebase/lib/core/data/api/user_api.dart +15 -0
- package/templates/firebase/lib/core/data/repositories/user_repository.dart +5 -0
- package/templates/firebase/lib/core/dev_inspector/dev_inspector.dart +525 -65
- package/templates/firebase/lib/core/dev_inspector/dev_inspector_info.dart +47 -0
- package/templates/firebase/lib/core/dev_inspector/dev_inspector_service.dart +55 -15
- package/templates/firebase/lib/core/icons/kasy_icons.dart +16 -1
- package/templates/firebase/lib/core/rating/widgets/review_popup.dart +18 -35
- package/templates/firebase/lib/core/shared_preferences/shared_preferences.dart +11 -0
- package/templates/firebase/lib/core/states/logout_action.dart +11 -1
- package/templates/firebase/lib/core/states/user_state_notifier.dart +69 -1
- package/templates/firebase/lib/core/theme/texts.dart +21 -6
- package/templates/firebase/lib/core/theme/type_scale.dart +34 -15
- package/templates/firebase/lib/core/theme/universal_theme.dart +9 -0
- package/templates/firebase/lib/core/web_device_preview/png_clipboard.dart +14 -0
- package/templates/firebase/lib/core/web_device_preview/png_clipboard_io.dart +9 -0
- package/templates/firebase/lib/core/web_device_preview/png_clipboard_web.dart +36 -0
- package/templates/firebase/lib/core/web_device_preview/png_export_result.dart +2 -0
- package/templates/firebase/lib/core/web_device_preview/web_device_preview.dart +547 -483
- package/templates/firebase/lib/core/web_viewport_scale.dart +64 -35
- package/templates/firebase/lib/core/widgets/kasy_pressable_depth.dart +14 -3
- package/templates/firebase/lib/core/widgets/responsive_layout.dart +8 -0
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_chat_composer.dart +1 -1
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_chat_conversation_view.dart +52 -35
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_list.dart +1 -1
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_tile.dart +18 -8
- package/templates/firebase/lib/features/authentication/ui/signin_page.dart +11 -61
- package/templates/firebase/lib/features/authentication/ui/signup_page.dart +11 -61
- package/templates/firebase/lib/features/authentication/ui/widgets/auth_card_scaffold.dart +7 -5
- package/templates/firebase/lib/features/authentication/ui/widgets/social_auth_tile.dart +83 -0
- package/templates/firebase/lib/features/feedbacks/ui/widgets/feature_card.dart +4 -4
- package/templates/firebase/lib/features/home/home_components_page.dart +264 -126
- package/templates/firebase/lib/features/home/home_components_preview_registry.dart +231 -57
- package/templates/firebase/lib/features/home/home_feed.dart +2 -2
- package/templates/firebase/lib/features/home/home_image_grid.dart +3 -3
- package/templates/firebase/lib/features/local_reminders/providers/reminder_notifier.dart +8 -1
- package/templates/firebase/lib/features/local_reminders/ui/reminder_page.dart +118 -57
- package/templates/firebase/lib/features/notifications/api/device_api.dart +11 -3
- package/templates/firebase/lib/features/notifications/ui/notifications_page.dart +19 -4
- package/templates/firebase/lib/features/notifications/ui/widgets/empty_notifications.dart +7 -56
- package/templates/firebase/lib/features/notifications/ui/widgets/notification_tile.dart +5 -5
- package/templates/firebase/lib/features/notifications/ui/widgets/push_permission_banner.dart +163 -0
- package/templates/firebase/lib/features/notifications/ui/widgets/web_notifications_bell.dart +262 -0
- package/templates/firebase/lib/features/onboarding/providers/onboarding_model.dart +9 -0
- package/templates/firebase/lib/features/onboarding/providers/onboarding_provider.dart +28 -0
- package/templates/firebase/lib/features/onboarding/ui/onboarding_page.dart +51 -12
- package/templates/firebase/lib/features/onboarding/ui/widgets/selectable_row_tile.dart +90 -32
- package/templates/firebase/lib/features/settings/settings_page.dart +99 -65
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_page.dart +1379 -422
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_routes.dart +14 -4
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_api.dart +7 -0
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_tab.dart +445 -233
- package/templates/firebase/lib/features/settings/ui/components/admin/send_push_notification_page.dart +404 -149
- package/templates/firebase/lib/features/settings/ui/components/avatar_component.dart +24 -31
- package/templates/firebase/lib/features/settings/ui/components/delete_user_component.dart +9 -1
- package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +77 -95
- package/templates/firebase/lib/features/settings/ui/widgets/settings_bottom_sheet_option_tile.dart +21 -18
- package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +25 -10
- package/templates/firebase/lib/i18n/en.i18n.json +749 -698
- package/templates/firebase/lib/i18n/es.i18n.json +749 -698
- package/templates/firebase/lib/i18n/pt.i18n.json +749 -698
- package/templates/firebase/lib/main.dart +20 -7
- package/templates/firebase/lib/router.dart +70 -46
- package/templates/firebase/pubspec.yaml +2 -1
- package/templates/firebase/test/admin_shell_chrome_test.dart +110 -0
- package/templates/firebase/test/components/kasy_text_field_height_test.dart +77 -0
- package/templates/firebase/test/core/web_viewport_scale_test.dart +23 -16
- package/templates/firebase/test/features/authentication/data/api/user_api_fake.dart +3 -0
- package/templates/firebase/tool/design_check.dart +9 -0
- package/templates/firebase/assets/icons/apple.png +0 -0
- package/templates/firebase/assets/icons/facebook.png +0 -0
- package/templates/firebase/assets/icons/google.png +0 -0
- package/templates/firebase/assets/icons/google_play_games.png +0 -0
- package/templates/firebase/lib/components/kasy_web_header.dart +0 -210
- package/templates/firebase/lib/features/authentication/ui/components/apple_signin.dart +0 -19
- package/templates/firebase/lib/features/authentication/ui/components/google_signin.dart +0 -32
- package/templates/firebase/lib/features/authentication/ui/widgets/round_signin.dart +0 -73
- package/templates/firebase/lib/features/feedbacks/ui/widgets/add_feature_button.dart +0 -66
- package/templates/firebase/lib/features/notifications/ui/components/notification_settings_sheet.dart +0 -169
- package/templates/firebase/lib/features/notifications/ui/components/push_notification_switcher.dart +0 -106
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_paywalls.dart +0 -53
|
@@ -199,13 +199,26 @@ class _MyAppState extends ConsumerState<MyApp> {
|
|
|
199
199
|
facebookEventApiProvider,
|
|
200
200
|
],
|
|
201
201
|
onReady: FocusVisibility(
|
|
202
|
-
child:
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
202
|
+
child: ValueListenableBuilder<bool>(
|
|
203
|
+
valueListenable: webDevicePreviewActiveNotifier,
|
|
204
|
+
builder: (context, devicePreviewActive, _) {
|
|
205
|
+
final Widget app = DevicePreview.appBuilder(
|
|
206
|
+
context,
|
|
207
|
+
ResponsiveTextTheme(
|
|
208
|
+
child: child ?? const SizedBox.shrink(),
|
|
209
|
+
),
|
|
210
|
+
);
|
|
211
|
+
// The web render-scale correction applies to the REAL
|
|
212
|
+
// web app on every breakpoint. Once the device preview
|
|
213
|
+
// frame is actually on screen it simulates a NATIVE
|
|
214
|
+
// device, so it renders at native 1.0 — skip the scale.
|
|
215
|
+
// Gated on the *active* (frame-built) notifier, not the
|
|
216
|
+
// raw toggle, so the scale only drops when the frame is
|
|
217
|
+
// up (no flash of big unframed app while it builds).
|
|
218
|
+
return devicePreviewActive
|
|
219
|
+
? app
|
|
220
|
+
: WebViewportScale.wrap(app);
|
|
221
|
+
},
|
|
209
222
|
),
|
|
210
223
|
),
|
|
211
224
|
onError: (_, error) => InitializationErrorPage(error: error),
|
|
@@ -26,9 +26,7 @@ import 'package:kasy_kit/features/local_reminders/ui/reminder_page.dart';
|
|
|
26
26
|
import 'package:kasy_kit/features/onboarding/ui/onboarding_page.dart';
|
|
27
27
|
import 'package:kasy_kit/features/settings/ui/components/admin/admin_home_widgets.dart';
|
|
28
28
|
import 'package:kasy_kit/features/settings/ui/components/admin/admin_page.dart';
|
|
29
|
-
import 'package:kasy_kit/features/settings/ui/components/admin/admin_paywalls.dart';
|
|
30
29
|
import 'package:kasy_kit/features/settings/ui/components/admin/admin_routes.dart';
|
|
31
|
-
import 'package:kasy_kit/features/settings/ui/components/admin/send_push_notification_page.dart';
|
|
32
30
|
import 'package:kasy_kit/features/subscriptions/ui/component/premium_page_factory.dart';
|
|
33
31
|
import 'package:kasy_kit/features/subscriptions/ui/premium_page.dart';
|
|
34
32
|
import 'package:logger/logger.dart';
|
|
@@ -71,6 +69,16 @@ String? _authRedirect(Ref ref, GoRouterState state) {
|
|
|
71
69
|
// redirect yet — we re-run once the state resolves (refresh listenable).
|
|
72
70
|
if (user.isLoading) return null;
|
|
73
71
|
|
|
72
|
+
// Admin area is role-gated at the ROUTING layer (URLs included), not just by
|
|
73
|
+
// hiding UI: in production, /admin and every /admin/* section open only for an
|
|
74
|
+
// authenticated admin (role == "admin"). Anyone else — including someone who
|
|
75
|
+
// types the URL directly — is bounced home. Open in debug for development.
|
|
76
|
+
// This sits on top of the server-side data rules that ultimately protect the
|
|
77
|
+
// data; here it guarantees the screens themselves are never even reachable.
|
|
78
|
+
if (state.uri.path.startsWith('/admin') && !kDebugMode && !user.isAdmin) {
|
|
79
|
+
return '/';
|
|
80
|
+
}
|
|
81
|
+
|
|
74
82
|
final String loc = state.matchedLocation;
|
|
75
83
|
final bool onAuthRoute = _authRoutes.contains(loc);
|
|
76
84
|
final bool onboardingDone = ref
|
|
@@ -212,7 +220,11 @@ GoRouter generateRouter({
|
|
|
212
220
|
path: '/onboarding',
|
|
213
221
|
pageBuilder: (context, state) => kasyTransitionPage(
|
|
214
222
|
key: state.pageKey,
|
|
215
|
-
|
|
223
|
+
// ?preview=true opens the flow as a side-effect-free walkthrough from
|
|
224
|
+
// the admin Debug screen (no account creation, returns to Debug).
|
|
225
|
+
child: OnboardingPage(
|
|
226
|
+
preview: state.uri.queryParameters['preview'] == 'true',
|
|
227
|
+
),
|
|
216
228
|
),
|
|
217
229
|
),
|
|
218
230
|
GoRoute(
|
|
@@ -296,25 +308,65 @@ GoRouter generateRouter({
|
|
|
296
308
|
child: const RecoverPasswordPage(),
|
|
297
309
|
),
|
|
298
310
|
),
|
|
299
|
-
// Admin console:
|
|
300
|
-
//
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
311
|
+
// Admin console: a StatefulShellRoute so every section is a real,
|
|
312
|
+
// URL-addressable screen that keeps its own state while the navigation
|
|
313
|
+
// rail persists across them. The top-level sections (/admin, /admin/users,
|
|
314
|
+
// /admin/requests) and the four "Ferramentas" sub-screens (/admin/tools/*)
|
|
315
|
+
// are ALL branches — each its own URL, reached from the sidebar. Registered
|
|
316
|
+
// always (admins reach it in release too); the redirect above blocks
|
|
317
|
+
// /admin* for non-admins in production. adminSections() is the single
|
|
318
|
+
// source the sidebar reads too, so branches and nav rows stay aligned.
|
|
319
|
+
StatefulShellRoute.indexedStack(
|
|
320
|
+
// Enter /admin with the app's standard page transition
|
|
321
|
+
// (KasyNavigationConfig.push), same as every other route — pageBuilder
|
|
322
|
+
// (not builder), otherwise go_router falls back to its default platform
|
|
323
|
+
// transition. Switching sections is instant (IndexedStack), like tabs.
|
|
324
|
+
pageBuilder: (context, state, navigationShell) => kasyTransitionPage(
|
|
305
325
|
key: state.pageKey,
|
|
306
|
-
child:
|
|
326
|
+
child: AdminShell(navigationShell: navigationShell),
|
|
307
327
|
),
|
|
328
|
+
branches: [
|
|
329
|
+
for (final section in adminSections())
|
|
330
|
+
StatefulShellBranch(
|
|
331
|
+
routes: [
|
|
332
|
+
GoRoute(
|
|
333
|
+
path: section.path,
|
|
334
|
+
pageBuilder: (context, state) => kasyTransitionPage(
|
|
335
|
+
key: state.pageKey,
|
|
336
|
+
child: section.build(),
|
|
337
|
+
),
|
|
338
|
+
),
|
|
339
|
+
],
|
|
340
|
+
),
|
|
341
|
+
],
|
|
308
342
|
),
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
343
|
+
// Drill-downs pushed full-screen from inside the console (their own back
|
|
344
|
+
// button) — the redirect above keeps /admin* admin-only.
|
|
345
|
+
//
|
|
346
|
+
// Paywall variant preview is pushed from the Paywalls section, which ships
|
|
347
|
+
// in production, so it's registered always.
|
|
348
|
+
GoRoute(
|
|
349
|
+
name: 'admin_premium_preview',
|
|
350
|
+
path: '/admin/premium/:variant',
|
|
351
|
+
pageBuilder: (context, state) {
|
|
352
|
+
final paywall = paywallFactoryFromAdminRoute(
|
|
353
|
+
state.pathParameters['variant'],
|
|
354
|
+
);
|
|
355
|
+
if (paywall == null || !withRevenuecat) {
|
|
356
|
+
return kasyTransitionPage(
|
|
357
|
+
key: state.pageKey,
|
|
358
|
+
child: const PageNotFound(),
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
return kasyTransitionPage(
|
|
314
362
|
key: state.pageKey,
|
|
315
|
-
child:
|
|
316
|
-
)
|
|
317
|
-
|
|
363
|
+
child: PremiumPage(paywall: paywall),
|
|
364
|
+
);
|
|
365
|
+
},
|
|
366
|
+
),
|
|
367
|
+
// Home-widgets panel: developer-only, pushed from the Debug section's
|
|
368
|
+
// (debug-gated) tile — its route registers only in kDebugMode.
|
|
369
|
+
if (kDebugMode)
|
|
318
370
|
GoRoute(
|
|
319
371
|
name: 'admin_home_widgets',
|
|
320
372
|
path: adminRouteHomeWidgets,
|
|
@@ -323,34 +375,6 @@ GoRouter generateRouter({
|
|
|
323
375
|
child: const AdminHomeWidgets(),
|
|
324
376
|
),
|
|
325
377
|
),
|
|
326
|
-
GoRoute(
|
|
327
|
-
name: 'admin_premium_preview',
|
|
328
|
-
path: '/admin/premium/:variant',
|
|
329
|
-
pageBuilder: (context, state) {
|
|
330
|
-
final paywall = paywallFactoryFromAdminRoute(
|
|
331
|
-
state.pathParameters['variant'],
|
|
332
|
-
);
|
|
333
|
-
if (paywall == null || !withRevenuecat) {
|
|
334
|
-
return kasyTransitionPage(
|
|
335
|
-
key: state.pageKey,
|
|
336
|
-
child: const PageNotFound(),
|
|
337
|
-
);
|
|
338
|
-
}
|
|
339
|
-
return kasyTransitionPage(
|
|
340
|
-
key: state.pageKey,
|
|
341
|
-
child: PremiumPage(paywall: paywall),
|
|
342
|
-
);
|
|
343
|
-
},
|
|
344
|
-
),
|
|
345
|
-
],
|
|
346
|
-
GoRoute(
|
|
347
|
-
name: 'send_push',
|
|
348
|
-
path: adminRouteSendPush,
|
|
349
|
-
pageBuilder: (context, state) => kasyTransitionPage(
|
|
350
|
-
key: state.pageKey,
|
|
351
|
-
child: const SendPushNotificationPage(),
|
|
352
|
-
),
|
|
353
|
-
),
|
|
354
378
|
GoRoute(
|
|
355
379
|
name: '404',
|
|
356
380
|
path: '/404',
|
|
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|
|
16
16
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
|
17
17
|
# In Windows, build-name is used as the major, minor, and patch parts
|
|
18
18
|
# of the product and file versions while build-number is used as the build suffix.
|
|
19
|
-
version: 1.0.0+
|
|
19
|
+
version: 1.0.0+66
|
|
20
20
|
|
|
21
21
|
environment:
|
|
22
22
|
sdk: ^3.11.0
|
|
@@ -59,6 +59,7 @@ dependencies:
|
|
|
59
59
|
flutter_native_splash: ^2.4.7
|
|
60
60
|
flutter_riverpod: ^3.1.0
|
|
61
61
|
flutter_secure_storage: ^9.2.4
|
|
62
|
+
flutter_svg: ^2.3.0
|
|
62
63
|
flutter_timezone: ^5.0.1
|
|
63
64
|
freezed_annotation: ^3.1.0
|
|
64
65
|
go_router: ^17.1.0
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import 'package:flutter/material.dart';
|
|
2
|
+
import 'package:flutter_test/flutter_test.dart';
|
|
3
|
+
import 'package:go_router/go_router.dart';
|
|
4
|
+
import 'package:kasy_kit/components/kasy_app_bar.dart';
|
|
5
|
+
import 'package:kasy_kit/components/kasy_sidebar.dart';
|
|
6
|
+
import 'package:kasy_kit/core/data/models/user.dart';
|
|
7
|
+
import 'package:kasy_kit/core/states/models/user_state.dart';
|
|
8
|
+
import 'package:kasy_kit/features/settings/ui/components/admin/admin_page.dart';
|
|
9
|
+
import 'package:kasy_kit/features/settings/ui/components/admin/admin_routes.dart';
|
|
10
|
+
import 'package:kasy_kit/i18n/translations.g.dart';
|
|
11
|
+
|
|
12
|
+
import 'test_utils.dart';
|
|
13
|
+
|
|
14
|
+
/// Mirrors the real admin shell: every section (top-level AND the "Ferramentas"
|
|
15
|
+
/// sub-screens) is its own branch, so they all share the same chrome and a
|
|
16
|
+
/// persistent rail. Branch order/paths match [adminSections] so the shell's
|
|
17
|
+
/// currentIndex lines up; the branch bodies are placeholders so the test stays
|
|
18
|
+
/// focused on the chrome (sidebar + header), not on each page's providers.
|
|
19
|
+
GoRouter _adminTestRouter(String initial) => GoRouter(
|
|
20
|
+
initialLocation: initial,
|
|
21
|
+
routes: [
|
|
22
|
+
GoRoute(path: '/', builder: (_, _) => const SizedBox()),
|
|
23
|
+
StatefulShellRoute.indexedStack(
|
|
24
|
+
builder: (c, s, shell) => AdminShell(navigationShell: shell),
|
|
25
|
+
branches: [
|
|
26
|
+
for (final section in adminSections())
|
|
27
|
+
StatefulShellBranch(
|
|
28
|
+
routes: [
|
|
29
|
+
GoRoute(
|
|
30
|
+
path: section.path,
|
|
31
|
+
builder: (_, _) =>
|
|
32
|
+
Center(child: Text('section-${section.id.name}')),
|
|
33
|
+
),
|
|
34
|
+
],
|
|
35
|
+
),
|
|
36
|
+
],
|
|
37
|
+
),
|
|
38
|
+
],
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
UserState _adminUser() => UserState(
|
|
42
|
+
user: User.authenticated(
|
|
43
|
+
id: '1',
|
|
44
|
+
email: 'admin@email.com',
|
|
45
|
+
name: 'Admin',
|
|
46
|
+
onboarded: true,
|
|
47
|
+
role: 'admin',
|
|
48
|
+
creationDate: DateTime.now(),
|
|
49
|
+
),
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
/// The desktop application bar is a [KasyAppBar.application] — same class as the
|
|
53
|
+
/// page bar, told apart by [KasyAppBar.isApplication].
|
|
54
|
+
final Finder _applicationBar = find.byWidgetPredicate(
|
|
55
|
+
(Widget w) => w is KasyAppBar && w.isApplication,
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
void main() {
|
|
59
|
+
testWidgets(
|
|
60
|
+
'admin section (desktop) renders the real KasySidebar + application bar',
|
|
61
|
+
(tester) async {
|
|
62
|
+
tester.view.physicalSize = const Size(1400, 900);
|
|
63
|
+
tester.view.devicePixelRatio = 1.0;
|
|
64
|
+
addTearDown(tester.view.resetPhysicalSize);
|
|
65
|
+
addTearDown(tester.view.resetDevicePixelRatio);
|
|
66
|
+
|
|
67
|
+
await tester.pumpPage(
|
|
68
|
+
userState: _adminUser(),
|
|
69
|
+
routerConfig: _adminTestRouter('/admin'),
|
|
70
|
+
);
|
|
71
|
+
await tester.pump(const Duration(milliseconds: 300));
|
|
72
|
+
|
|
73
|
+
// The console uses the app's real chrome components, not a bespoke copy.
|
|
74
|
+
expect(find.byType(KasySidebar), findsOneWidget);
|
|
75
|
+
expect(_applicationBar, findsOneWidget);
|
|
76
|
+
expect(find.text('section-overview'), findsOneWidget);
|
|
77
|
+
},
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
testWidgets(
|
|
81
|
+
'a Ferramentas sub-screen (desktop) is a real section: same rail + header, '
|
|
82
|
+
'submenu open',
|
|
83
|
+
(tester) async {
|
|
84
|
+
tester.view.physicalSize = const Size(1400, 900);
|
|
85
|
+
tester.view.devicePixelRatio = 1.0;
|
|
86
|
+
addTearDown(tester.view.resetPhysicalSize);
|
|
87
|
+
addTearDown(tester.view.resetDevicePixelRatio);
|
|
88
|
+
|
|
89
|
+
await tester.pumpPage(
|
|
90
|
+
userState: _adminUser(),
|
|
91
|
+
routerConfig: _adminTestRouter(adminRouteSendPush),
|
|
92
|
+
);
|
|
93
|
+
await tester.pump(const Duration(milliseconds: 300));
|
|
94
|
+
|
|
95
|
+
// Tools sub-screens get the SAME chrome as every other section now (the
|
|
96
|
+
// whole point of this change): the persistent rail AND the application bar.
|
|
97
|
+
expect(find.byType(KasySidebar), findsOneWidget);
|
|
98
|
+
expect(_applicationBar, findsOneWidget);
|
|
99
|
+
expect(find.text('section-sendPush'), findsOneWidget);
|
|
100
|
+
|
|
101
|
+
// The rail shows the "Ferramentas" group, auto-expanded because one of its
|
|
102
|
+
// children (Send push) is the active screen.
|
|
103
|
+
expect(find.text(t.admin_console.tabs.tools), findsOneWidget);
|
|
104
|
+
expect(
|
|
105
|
+
find.text(t.home.features_page.send_push_title),
|
|
106
|
+
findsWidgets,
|
|
107
|
+
);
|
|
108
|
+
},
|
|
109
|
+
);
|
|
110
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import 'package:flutter/material.dart';
|
|
2
|
+
import 'package:flutter_test/flutter_test.dart';
|
|
3
|
+
import 'package:kasy_kit/components/kasy_text_field.dart';
|
|
4
|
+
import 'package:kasy_kit/core/theme/colors.dart';
|
|
5
|
+
import 'package:kasy_kit/core/theme/providers/theme_provider.dart';
|
|
6
|
+
import 'package:kasy_kit/core/theme/responsive_text_theme.dart';
|
|
7
|
+
import 'package:kasy_kit/core/theme/texts.dart';
|
|
8
|
+
import 'package:kasy_kit/core/theme/universal_theme.dart';
|
|
9
|
+
import 'package:shared_preferences/shared_preferences.dart';
|
|
10
|
+
|
|
11
|
+
import '../device_test_utils.dart';
|
|
12
|
+
|
|
13
|
+
/// A single-line [KasyTextField] must render at exactly
|
|
14
|
+
/// [KasyTextField.singleLineHeight] on EVERY breakpoint. The height is locked
|
|
15
|
+
/// with a forced strut (see kasy_text_field.dart) precisely so it cannot drift
|
|
16
|
+
/// between renderers (web CanvasKit vs native) or viewport widths. Run this on
|
|
17
|
+
/// the VM and with `--platform chrome` to cover both renderers.
|
|
18
|
+
void main() {
|
|
19
|
+
Widget appWithField() {
|
|
20
|
+
return Builder(builder: (context) {
|
|
21
|
+
return MaterialApp(
|
|
22
|
+
theme: ThemeProvider.of(context).light,
|
|
23
|
+
home: const ResponsiveTextTheme(
|
|
24
|
+
child: Scaffold(
|
|
25
|
+
body: Center(
|
|
26
|
+
child: SizedBox(
|
|
27
|
+
width: 320,
|
|
28
|
+
child: KasyTextField(
|
|
29
|
+
hint: 'Email',
|
|
30
|
+
variant: KasyTextFieldVariant.flat,
|
|
31
|
+
),
|
|
32
|
+
),
|
|
33
|
+
),
|
|
34
|
+
),
|
|
35
|
+
),
|
|
36
|
+
);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
testWidgets('single-line field height stays locked across breakpoints',
|
|
41
|
+
(tester) async {
|
|
42
|
+
SharedPreferences.setMockInitialValues({});
|
|
43
|
+
final sharedPrefs = await SharedPreferences.getInstance();
|
|
44
|
+
|
|
45
|
+
final widget = ThemeProvider(
|
|
46
|
+
notifier: AppTheme.uniform(
|
|
47
|
+
sharedPreferences: sharedPrefs,
|
|
48
|
+
themeFactory: const UniversalThemeFactory(),
|
|
49
|
+
lightColors: KasyColors.light(),
|
|
50
|
+
darkColors: KasyColors.dark(),
|
|
51
|
+
textTheme: KasyTextTheme.build(),
|
|
52
|
+
defaultMode: ThemeMode.light,
|
|
53
|
+
),
|
|
54
|
+
child: appWithField(),
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const breakpoints = <Device>[
|
|
58
|
+
Device(name: 'mobile', width: 375, height: 812, pixelDensity: 3),
|
|
59
|
+
Device(name: 'tablet', width: 800, height: 1024, pixelDensity: 2),
|
|
60
|
+
Device(name: 'desktop', width: 1400, height: 900, pixelDensity: 1),
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
for (final device in breakpoints) {
|
|
64
|
+
await tester.setScreenSize(device);
|
|
65
|
+
await tester.pumpWidget(widget);
|
|
66
|
+
await tester.pumpAndSettle();
|
|
67
|
+
final double boxHeight =
|
|
68
|
+
tester.getSize(find.byType(TextField).first).height;
|
|
69
|
+
expect(
|
|
70
|
+
boxHeight,
|
|
71
|
+
KasyTextField.singleLineHeight,
|
|
72
|
+
reason: 'field box should be ${KasyTextField.singleLineHeight}px on '
|
|
73
|
+
'${device.name} (${device.width}px wide), got $boxHeight',
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
@@ -1,28 +1,34 @@
|
|
|
1
1
|
import 'package:flutter_test/flutter_test.dart';
|
|
2
2
|
import 'package:kasy_kit/core/web_viewport_scale.dart';
|
|
3
3
|
|
|
4
|
-
/// Locks the
|
|
5
|
-
///
|
|
6
|
-
///
|
|
7
|
-
///
|
|
8
|
-
///
|
|
4
|
+
/// Locks the web scaling behaviour so a future change to the constants or the
|
|
5
|
+
/// formula can't silently regress it. Two headline rules:
|
|
6
|
+
/// - the flat cap applies on EVERY web breakpoint (phone/tablet/desktop) so the
|
|
7
|
+
/// whole web app matches native, with no size jump at the desktop breakpoint;
|
|
8
|
+
/// - the desktop high-OS-scale compensation must key off the SCREEN width, NOT
|
|
9
|
+
/// the window width — merely resizing the browser window must not shrink the
|
|
10
|
+
/// UI further; the extra shrink happens only when the screen itself is small.
|
|
9
11
|
void main() {
|
|
10
12
|
group('webViewportEffectiveScale', () {
|
|
11
|
-
test('
|
|
13
|
+
test('flat cap applies on tablet/phone web too (no jump at the breakpoint)',
|
|
12
14
|
() {
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
expect(webViewportEffectiveScale(
|
|
18
|
-
expect(webViewportEffectiveScale(900
|
|
15
|
+
// Below the desktop breakpoint the scale is the flat cap (not 1.0): the
|
|
16
|
+
// same ~10% web oversize is corrected, and there is no size jump when the
|
|
17
|
+
// window crosses the desktop breakpoint. Compensation is desktop-only, so
|
|
18
|
+
// the screen width is ignored here.
|
|
19
|
+
expect(webViewportEffectiveScale(375), kWebViewportScale); // phone
|
|
20
|
+
expect(webViewportEffectiveScale(900), kWebViewportScale); // tablet
|
|
21
|
+
expect(webViewportEffectiveScale(900, screenWidth: 1470), kWebViewportScale);
|
|
22
|
+
// A small screen does NOT compensate below the breakpoint — that is a
|
|
23
|
+
// desktop-only concern; tablet/phone always take the flat cap.
|
|
24
|
+
expect(webViewportEffectiveScale(900, screenWidth: 300), kWebViewportScale);
|
|
19
25
|
expect(
|
|
20
26
|
webViewportEffectiveScale(kWebViewportScaleDesktopBreakpoint - 1),
|
|
21
|
-
|
|
27
|
+
kWebViewportScale,
|
|
22
28
|
);
|
|
23
29
|
});
|
|
24
30
|
|
|
25
|
-
test('desktop on a normal/large screen stays at the flat cap
|
|
31
|
+
test('desktop on a normal/large screen stays at the flat cap', () {
|
|
26
32
|
// Unknown screen (null) → flat cap.
|
|
27
33
|
expect(webViewportEffectiveScale(1280), kWebViewportScale);
|
|
28
34
|
expect(webViewportEffectiveScale(1500), kWebViewportScale);
|
|
@@ -43,7 +49,7 @@ void main() {
|
|
|
43
49
|
});
|
|
44
50
|
|
|
45
51
|
test('high OS scale (small SCREEN) compensates below the cap', () {
|
|
46
|
-
// Drop below
|
|
52
|
+
// Drop below the cap only because the screen is small — independent of window.
|
|
47
53
|
expect(webViewportEffectiveScale(1024, screenWidth: 1024), closeTo(0.80, 1e-4));
|
|
48
54
|
expect(webViewportEffectiveScale(1097, screenWidth: 1097), closeTo(0.857, 1e-3));
|
|
49
55
|
// Even a wide window on a small (high-scale) screen compensates.
|
|
@@ -58,7 +64,8 @@ void main() {
|
|
|
58
64
|
});
|
|
59
65
|
|
|
60
66
|
test('the maxScale override is respected as the cap', () {
|
|
61
|
-
|
|
67
|
+
// Use a non-default cap so this genuinely exercises the override.
|
|
68
|
+
expect(webViewportEffectiveScale(1920, screenWidth: 1920, maxScale: 0.8), 0.8);
|
|
62
69
|
});
|
|
63
70
|
|
|
64
71
|
test('an absurdly small screen is clamped to the 0.5 floor', () {
|
|
@@ -77,6 +77,15 @@ final List<_Rule> _rules = <_Rule>[
|
|
|
77
77
|
RegExp(r'Color\(0x|(?<![A-Za-z])Colors\.(?!transparent\b)(?!white)(?!black)[A-Za-z]'),
|
|
78
78
|
'Use a colour token (context.colors.*) instead of a raw Color/Colors value.',
|
|
79
79
|
),
|
|
80
|
+
_Rule(
|
|
81
|
+
'hardcoded-radius',
|
|
82
|
+
// A numeric literal right after `circular(` — catches BorderRadius.circular(16),
|
|
83
|
+
// Radius.circular(8) and the only/vertical/horizontal forms (they nest a
|
|
84
|
+
// Radius.circular). `circular(KasyRadius.lg)` starts with a letter, so it
|
|
85
|
+
// passes; `circular(size / 2)` likewise (calibrated, not a token miss).
|
|
86
|
+
RegExp(r'\bcircular\(\s*\d'),
|
|
87
|
+
'Use a KasyRadius.* token instead of a literal corner radius.',
|
|
88
|
+
),
|
|
80
89
|
];
|
|
81
90
|
|
|
82
91
|
// The icon rule needs two signals on the same line, so it is handled separately.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|