kasy-cli 1.37.1 → 1.38.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 +9 -0
- package/lib/scaffold/backends/api/patch/lib/core/data/api/user_api.dart +18 -0
- package/lib/scaffold/backends/patch-base-hashes.json +2 -2
- package/lib/scaffold/backends/supabase/patch/lib/core/data/api/user_api.dart +8 -0
- 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 +13 -0
- package/templates/firebase/lib/components/kasy_app_bar.dart +57 -16
- package/templates/firebase/lib/components/kasy_bottom_sheet.dart +283 -66
- package/templates/firebase/lib/components/kasy_date_picker.dart +61 -46
- package/templates/firebase/lib/components/kasy_sidebar.dart +394 -25
- package/templates/firebase/lib/components/kasy_web_header.dart +11 -3
- package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +2 -213
- package/templates/firebase/lib/core/bottom_menu/sidebar_focus.dart +224 -0
- package/templates/firebase/lib/core/bottom_menu/web_content_wrapper.dart +12 -4
- package/templates/firebase/lib/core/chrome/web_header_scope.dart +20 -0
- package/templates/firebase/lib/core/data/api/user_api.dart +4 -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/icons/kasy_icons.dart +16 -1
- package/templates/firebase/lib/core/states/user_state_notifier.dart +41 -0
- 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 +498 -466
- package/templates/firebase/lib/core/widgets/responsive_layout.dart +8 -0
- package/templates/firebase/lib/features/home/home_components_page.dart +16 -6
- package/templates/firebase/lib/features/local_reminders/ui/reminder_page.dart +7 -0
- package/templates/firebase/lib/features/notifications/ui/components/notification_settings_sheet.dart +23 -13
- package/templates/firebase/lib/features/notifications/ui/notifications_page.dart +3 -0
- package/templates/firebase/lib/features/notifications/ui/widgets/web_notifications_bell.dart +262 -0
- package/templates/firebase/lib/features/settings/settings_page.dart +51 -38
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_page.dart +549 -376
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_routes.dart +14 -4
- package/templates/firebase/lib/features/settings/ui/components/admin/send_push_notification_page.dart +266 -141
- package/templates/firebase/lib/features/settings/ui/components/avatar_component.dart +23 -30
- package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +30 -49
- package/templates/firebase/lib/i18n/en.i18n.json +23 -9
- package/templates/firebase/lib/i18n/es.i18n.json +23 -9
- package/templates/firebase/lib/i18n/pt.i18n.json +23 -9
- package/templates/firebase/lib/router.dart +43 -25
- package/templates/firebase/pubspec.yaml +1 -1
- package/templates/firebase/test/admin_shell_chrome_test.dart +104 -0
- package/templates/firebase/test/features/authentication/data/api/user_api_fake.dart +3 -0
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_paywalls.dart +0 -53
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
{
|
|
2
|
+
"1.38.0": {
|
|
3
|
+
"modules": {
|
|
4
|
+
"components": {
|
|
5
|
+
"pt": "Sidebar agora suporta submenus expansíveis (grupos com sub-itens, como o menu suspenso de Configurações na Home): clicar no grupo abre/fecha os filhos, o item ativo do submenu fica em negrito forte (sem pílula) e o submenu fecha sozinho ao navegar para outra tela.",
|
|
6
|
+
"en": "Sidebar now supports expandable submenus (groups with sub-items, like the Settings dropdown on Home): tapping the group opens/closes its children, the active sub-item shows in strong bold (no pill) and the submenu closes on its own when you navigate to another screen.",
|
|
7
|
+
"es": "La sidebar ahora admite submenús expansibles (grupos con subítems, como el menú desplegable de Ajustes en la Home): tocar el grupo abre/cierra sus hijos, el ítem activo del submenú se muestra en negrita fuerte (sin píldora) y el submenú se cierra solo al navegar a otra pantalla."
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
},
|
|
2
11
|
"1.37.0": {
|
|
3
12
|
"modules": {
|
|
4
13
|
"components": {
|
|
@@ -44,6 +44,24 @@ class UserApi {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
/// Watches the user's role so an admin promotion/demotion is picked up at
|
|
48
|
+
/// runtime (the shared user state refreshes when it changes). A REST backend
|
|
49
|
+
/// has no realtime channel, so this polls: it emits the current role at once
|
|
50
|
+
/// (mirroring the immediate snapshot of the Firebase/Supabase streams, which
|
|
51
|
+
/// the state notifier skips) and then re-checks periodically. The notifier
|
|
52
|
+
/// cancels the subscription on logout/dispose, which ends this generator.
|
|
53
|
+
Stream<String?> watchRole(String id) async* {
|
|
54
|
+
while (true) {
|
|
55
|
+
try {
|
|
56
|
+
final user = await get(id);
|
|
57
|
+
yield user?.role;
|
|
58
|
+
} catch (_) {
|
|
59
|
+
// Ignore transient network errors and keep polling.
|
|
60
|
+
}
|
|
61
|
+
await Future<void>.delayed(const Duration(seconds: 30));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
47
65
|
Future<void> update(UserEntity user) async {
|
|
48
66
|
try {
|
|
49
67
|
final data = user.toJson()..removeWhere((_, v) => v == null);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"api/android/app/src/main/AndroidManifest.xml": "0579c57068ee50d43837ef9c7bee884b6232e92975301c8305ce389282f68291",
|
|
3
3
|
"api/ios/Runner/AppDelegate.swift": "9f1027a03ad1fef2dab45464fbd7e98364b6a9cb6d2a6abe770ee9c45fa19122",
|
|
4
4
|
"api/lib/core/data/api/storage_api.dart": "d77fecc6923ea9544b6ec20571dcab5c8f3d38be81ec618b0cf4f1b08be7bbae",
|
|
5
|
-
"api/lib/core/data/api/user_api.dart": "
|
|
5
|
+
"api/lib/core/data/api/user_api.dart": "0e7d984dceada025b7e998c217c39da9b781de430fd0f4d2267aaa6439391d5f",
|
|
6
6
|
"api/lib/core/data/entities/json_converters.dart": "92a5efd595a03a862fa03e77ab3ca3d556523d0c9e0daca6e296e35d24b760a8",
|
|
7
7
|
"api/lib/core/data/entities/user_entity.dart": "5c1e56e4be3ba2ed95cdcd276112a02b526f2a3fa7ee5bba08a3ec2b51153688",
|
|
8
8
|
"api/lib/environments.dart": "c41f0843a4d627b717579eec5607b7ff5c16369bdd4b2a82e081058a20ffb7a5",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"supabase/android/app/src/main/AndroidManifest.xml": "0579c57068ee50d43837ef9c7bee884b6232e92975301c8305ce389282f68291",
|
|
35
35
|
"supabase/ios/Runner/AppDelegate.swift": "9f1027a03ad1fef2dab45464fbd7e98364b6a9cb6d2a6abe770ee9c45fa19122",
|
|
36
36
|
"supabase/lib/core/data/api/storage_api.dart": "d77fecc6923ea9544b6ec20571dcab5c8f3d38be81ec618b0cf4f1b08be7bbae",
|
|
37
|
-
"supabase/lib/core/data/api/user_api.dart": "
|
|
37
|
+
"supabase/lib/core/data/api/user_api.dart": "0e7d984dceada025b7e998c217c39da9b781de430fd0f4d2267aaa6439391d5f",
|
|
38
38
|
"supabase/lib/core/data/entities/json_converters.dart": "92a5efd595a03a862fa03e77ab3ca3d556523d0c9e0daca6e296e35d24b760a8",
|
|
39
39
|
"supabase/lib/core/data/entities/user_entity.dart": "5c1e56e4be3ba2ed95cdcd276112a02b526f2a3fa7ee5bba08a3ec2b51153688",
|
|
40
40
|
"supabase/lib/environments.dart": "c41f0843a4d627b717579eec5607b7ff5c16369bdd4b2a82e081058a20ffb7a5",
|
|
@@ -40,6 +40,14 @@ class UserApi {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
Stream<String?> watchRole(String id) {
|
|
44
|
+
return _client
|
|
45
|
+
.from('users')
|
|
46
|
+
.stream(primaryKey: ['id'])
|
|
47
|
+
.eq('id', id)
|
|
48
|
+
.map((rows) => rows.isEmpty ? null : rows.first['role'] as String?);
|
|
49
|
+
}
|
|
50
|
+
|
|
43
51
|
Future<void> update(UserEntity user) async {
|
|
44
52
|
try {
|
|
45
53
|
final data = user.toJson()..removeWhere((_, v) => v == null);
|
package/package.json
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
stripe_success.html,1781195415353,677b2fb430839866f32a33a751aae85b4c4819123be820f3f5b2f689b8eed00e
|
|
2
2
|
manifest.json,1779810782774,1b929ac7267ff738d05646106a0fac3591082b6f365051928a62518af30b3fd5
|
|
3
3
|
local_notifications.js,1772398909034,0b44b177c4b8a705ad1b0e3d7532537aad7b230e9472f93c820742b7548797b8
|
|
4
|
-
index.html,1781388256310,bb8142eb84e9e44049957c3edf86006e1ae5f194c397a8326ce505b68fdf58f4
|
|
5
|
-
flutter_bootstrap.js,1781388256056,25f49550b20f234b84194d9e067725abdc33590343251086f6c7d147325dfb41
|
|
6
4
|
flutter.js,1777393608000,b42e549a02f5c1428b0ad85dbdc663d400a6d2db10cb8aefcc0fddd592fd6ceb
|
|
7
5
|
firebase-messaging-sw.js,1772484605561,dba3c939f7c240e62f63d06408d5f70e45f1168d00df98726e6a7a20cbd3b0df
|
|
8
6
|
favicon.png,1779810782545,c967afd3c3d4dd165ef2e5cf3f203c3982dfff6a95881859ed163d9c38d75512
|
|
9
|
-
.last_build_id,
|
|
7
|
+
.last_build_id,1781393235997,3217b88fca927fa7fc5a6d9c24dee562336c5b14ad2c6e8b9b7b1bacff2e4a42
|
|
10
8
|
splash/img/light-4x.png,1780546027751,abf0e55345e532a4645fe14b01c2948b07c78df50cc6da23648837dfb6271c74
|
|
11
9
|
splash/img/light-3x.png,1780546027699,5f02c94032cb7053807ce89603b109f14ea2740eaa3145dc76c924cbe1d534a9
|
|
12
10
|
splash/img/light-2x.png,1780546027647,3c073b9da154ac8be530f8beefa3579246f223acd819e856a2db000fdabcb67e
|
|
@@ -36,6 +34,15 @@ canvaskit/chromium/canvaskit.js.symbols,1777393830000,60722421e56edacbeb674cc3ef
|
|
|
36
34
|
canvaskit/chromium/canvaskit.js,1777393830000,10ea3badcf26e29aba851699c03e09bc35fea65cdc440a035bac263d6d02d665
|
|
37
35
|
assets/.env,1781370223435,777e85eb4de226724b28db8664404d0b73ae37f63fd66c955ac90c89cd491e7e
|
|
38
36
|
assets/packages/mixpanel_flutter/assets/mixpanel.js,1779806094332,1915455df317dd1a9f42c6f3ea5bbe3ee357614f89d994f157a2e821f37956c3
|
|
37
|
+
assets/packages/lucide_icons_flutter/assets/lucide.ttf,1779809590216,3813e3eb113e8be4c36c2082397dc3397d0ea7cd3a3b43eaecd61100b0256513
|
|
38
|
+
assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-w600.ttf,1779809590200,095ae41c43f72ec2997fa59fd91a0c83a36b345915e705f292f414cae6240dab
|
|
39
|
+
assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-w500.ttf,1779809590211,59d94414e1a3dd1732ff0b3ea50389c3e8b3915fe5a9b6163ba256a825e1e05b
|
|
40
|
+
assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-w400.ttf,1779809590202,9342632d6521314eb2ab7e902a8a6fe6c3d62a110f682bcc123e244e12c4a695
|
|
41
|
+
assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-w300.ttf,1779809590205,3bd1b359793f74027dada0e359d7d9dc712afd721db5ef30b095592e2e03e865
|
|
42
|
+
assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-w200.ttf,1779809590198,8026f9e7a751a47ace3ec60fdb7c6843a1d8051699fb28eb0a9fb1bd53904a66
|
|
43
|
+
assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-w100.ttf,1779809590208,1d81fb594eb42c3e8e43a36b89120a0a8a4c2dd858dec1a0c94de0e5b2208388
|
|
44
|
+
assets/packages/cupertino_icons/assets/CupertinoIcons.ttf,1779806141382,010d647b6cab4e1a9b4398957353f43589903ce8dc760d05d894944b664e29bd
|
|
45
|
+
assets/fonts/MaterialIcons-Regular.otf,1662569016000,3a631b0761788d7cf2a71e8732a5b31288d6e7aa996c671c70d53a251bbd3848
|
|
39
46
|
assets/assets/images/splash_logo_light_android12.png,1779867497377,da2caee4f16230230dbb04d84913f0a9df2eae6bcb8f9d1ce83ed556cee6d094
|
|
40
47
|
assets/assets/images/splash_logo_light.png,1779867497286,643a1f45d6ef9458c4c0eed9f4889cc2adbdf1db616913215be66673a05a0066
|
|
41
48
|
assets/assets/images/splash_logo_dark_android12.png,1779867497464,b237d9f9035d419525a10b32889c30d44c706d62990b30bdc6bef911fe9352d6
|
|
@@ -53,23 +60,16 @@ assets/assets/icons/google_play_games.png,1772398653083,533c1d87d7be8690ab173ef4
|
|
|
53
60
|
assets/assets/icons/google.png,1772398653083,f423e7e7be1e06008d45617d07f095f04da7fdcab9a56523f9e0633828e464e0
|
|
54
61
|
assets/assets/icons/facebook.png,1772398653083,79ac67e449c1db63319d43329ca91f682b4e0bc6a0883b0dfb2a849e13ae6eb6
|
|
55
62
|
assets/assets/icons/apple.png,1772398653083,2a737d8801ca81452b2978bb2e1ac72136acf1a4c338f2de2f77b65e9f6de1fa
|
|
56
|
-
version.json,
|
|
57
|
-
flutter_service_worker.js,
|
|
58
|
-
assets/
|
|
59
|
-
|
|
60
|
-
assets/
|
|
61
|
-
assets/
|
|
62
|
-
assets/shaders/stretch_effect.frag,
|
|
63
|
-
assets/
|
|
64
|
-
|
|
65
|
-
main.dart.js_3.part.js,
|
|
66
|
-
main.dart.js_1.part.js,
|
|
67
|
-
assets/
|
|
68
|
-
|
|
69
|
-
assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-w600.ttf,1781388385468,008f1e974c9ff4e8d40d16221226e0d34d2298a7feb2bdea8c134a9333929c9c
|
|
70
|
-
assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-w100.ttf,1781388385446,c16d95412fe2600bda1f53c2b8dc4bd82496f60f7c5a6ecfa180b0f866128712
|
|
71
|
-
assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-w500.ttf,1781388385464,3f68f958d0fa13077968a6767339c38ede8705bcadd0382078db6ec4bac96fa0
|
|
72
|
-
assets/packages/lucide_icons_flutter/assets/lucide.ttf,1781388385448,9b000e47cddf9ada4a6b85fc9577fef7ed2ee261c9279e9a44e4753da28f83d3
|
|
73
|
-
assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-w200.ttf,1781388385451,1f798186cfc3e2fcd54b15aa7b8695bc7c29b781c5058029a8d80ecdfa615904
|
|
74
|
-
assets/NOTICES,1781388383326,a18efc42c5ed99b56481537d0f229ddd3add671c548a893aaf8766f30c854158
|
|
75
|
-
main.dart.js,1781388361400,b625f1b346dc2f1475ee99ebb1d698654e74183d737c91e1bbac020869096a84
|
|
63
|
+
version.json,1781400658272,495754f8591b89616bdad18717ffd8aa9275a8605889167170e6bc98ced40489
|
|
64
|
+
flutter_service_worker.js,1781400658740,baeeaf9f4b8e6f40d3b0549429ceb70ca01f120db6a7ef2f60d5809233dc2205
|
|
65
|
+
assets/FontManifest.json,1781400658381,4d84ab517c27984d36f9a3c8be6f2a72788c0c3985c1d5874297fef0a53407ca
|
|
66
|
+
index.html,1781400619641,bb8142eb84e9e44049957c3edf86006e1ae5f194c397a8326ce505b68fdf58f4
|
|
67
|
+
assets/AssetManifest.bin,1781400658381,78bccb08a36307a400711a1d7e6868bd5f89a24e63439fb5b6747660323e4475
|
|
68
|
+
assets/AssetManifest.bin.json,1781400658381,50a11b51c3fc4cbed1b5ec3a4f466c6a6b75c481d08d4782c8191f99bdbdba9c
|
|
69
|
+
assets/shaders/stretch_effect.frag,1781400658449,1a7d4ac2be40cf0a459dfb390ef08bcd740f37913ffdee8de3c2ea836a18410e
|
|
70
|
+
assets/shaders/ink_sparkle.frag,1781400658449,1c8e222328206d1e06754f76fb53947aad38d62180aafad5298a3c6f510b173d
|
|
71
|
+
flutter_bootstrap.js,1781400619636,371abfca6d227535d1d7bf43ada40ba5256e04ae0aed40e29ccae33f964fa7f7
|
|
72
|
+
main.dart.js_3.part.js,1781400646444,6f52d11c8e140736cd046ce6b541ad8d8bf20e6ef709f39d8ef4eb3dddbece8b
|
|
73
|
+
main.dart.js_1.part.js,1781400646439,42136799a40b5acb14b5acf0a8cef39bbef13b4dc99ce808a7bc8a1f8cdd8b09
|
|
74
|
+
assets/NOTICES,1781400658382,a18efc42c5ed99b56481537d0f229ddd3add671c548a893aaf8766f30c854158
|
|
75
|
+
main.dart.js,1781400646862,c0154549b8162d55ff32593e679d1f3461e6f4ff744d9730cd87953b0244d8c9
|
|
@@ -67,6 +67,11 @@ Typography collapses `large`+`xlarge` into one "desktop" tier (same sizes); the
|
|
|
67
67
|
renders ~10% large, so **desktop only** scales the whole UI to `0.95`. Tablet
|
|
68
68
|
and mobile web render at natural size, like native. This is NOT the typography
|
|
69
69
|
scale — don't conflate the two.
|
|
70
|
+
- **Screen width follows content type** (two tiers): read/form screens (Settings,
|
|
71
|
+
Notifications) are contained + centred at `kKasyContentMaxWidth` (600) — set
|
|
72
|
+
`KasyOverlayScaffold(maxContentWidth: kKasyContentMaxWidth)` or use `KasyScreen`;
|
|
73
|
+
feed/dashboard screens (Home) go full width + the 16 gutter. Mirror
|
|
74
|
+
Settings/Notifications, or Home. Full rule: **`DESIGN_SYSTEM.md`** → Internal-screen contract.
|
|
70
75
|
|
|
71
76
|
To re-tune sizes, edit `KasyTypeScale` in `lib/core/theme/type_scale.dart` —
|
|
72
77
|
nothing else. The live ramp (tabs per breakpoint) is under **Design System →
|
|
@@ -99,7 +104,8 @@ lib/
|
|
|
99
104
|
|
|
100
105
|
1. Read `DESIGN_SYSTEM.md` (tokens, typography roles, components).
|
|
101
106
|
2. Reuse a kit component before building one. Wrap new pages in `KasyScreen`
|
|
102
|
-
(or mirror an existing page's scaffold).
|
|
107
|
+
(or mirror an existing page's scaffold). Pick the width tier — contained
|
|
108
|
+
(mirror Settings/Notifications) vs full-width (mirror Home); see `DESIGN_SYSTEM.md`.
|
|
103
109
|
3. Use semantic typography roles (`pageTitle`, `sectionTitle`, `rowTitle`, …)
|
|
104
110
|
and tokens for **every** size, colour and spacing.
|
|
105
111
|
4. Strings via `context.t.*` (add keys to the i18n JSON, never inline text).
|
|
@@ -222,6 +222,19 @@ New internal screens follow the sidebar / Home ruler:
|
|
|
222
222
|
- Haptics only on native (`if (!kIsWeb)`); "hide chrome on scroll" only on the
|
|
223
223
|
phone breakpoint (`width < 768`).
|
|
224
224
|
|
|
225
|
+
**Content width — two tiers (width follows content type):**
|
|
226
|
+
- *Read / form screens* (Settings, Notifications, Reminder) → **contained + centred**
|
|
227
|
+
at `kKasyContentMaxWidth` (600, in `core/widgets/responsive_layout.dart`). With the
|
|
228
|
+
overlay pattern, set `KasyOverlayScaffold(maxContentWidth: kKasyContentMaxWidth)`
|
|
229
|
+
(centres the column on desktop, keeps the 16 gutter on mobile); with `KasyScreen`
|
|
230
|
+
it is the default. Long lines and lone controls read badly stretched, so cap them.
|
|
231
|
+
The Settings desktop master/detail centres the **nav + detail as one unit**.
|
|
232
|
+
- *Browse / feed / dashboard screens* (Home) → **full width + the 16 gutter, on
|
|
233
|
+
purpose** — width helps a grid/feed. Do **not** contain these.
|
|
234
|
+
- Every tier shares the same family (16 gutter, `KasyCard`, sidebar + web header) —
|
|
235
|
+
only the max width changes. To mirror: copy **Settings / Notifications** for a
|
|
236
|
+
contained screen, **Home** for a full-width one.
|
|
237
|
+
|
|
225
238
|
---
|
|
226
239
|
|
|
227
240
|
## Keeping it consistent — the guard-rail (optional)
|
|
@@ -27,6 +27,7 @@ import 'package:flutter/foundation.dart' show kIsWeb;
|
|
|
27
27
|
import 'package:flutter/material.dart';
|
|
28
28
|
import 'package:flutter/services.dart' show SystemUiOverlayStyle;
|
|
29
29
|
import 'package:kasy_kit/core/chrome/chrome_visibility.dart';
|
|
30
|
+
import 'package:kasy_kit/core/chrome/web_header_scope.dart';
|
|
30
31
|
import 'package:kasy_kit/core/theme/theme.dart';
|
|
31
32
|
import 'package:kasy_kit/core/widgets/kasy_focus_ring.dart';
|
|
32
33
|
import 'package:kasy_kit/core/widgets/responsive_layout.dart';
|
|
@@ -56,8 +57,11 @@ enum KasyAppBarScrollTreatment { overlay, intrinsicScroll }
|
|
|
56
57
|
|
|
57
58
|
/// Vertical inset for scroll/viewport when using [KasyAppBarScrollTreatment.overlay].
|
|
58
59
|
double kasyAppBarBodyTopOverlap(BuildContext context) {
|
|
59
|
-
// On desktop the app bar hides
|
|
60
|
-
|
|
60
|
+
// On desktop the app bar hides only inside the web-header scope (the shell), so
|
|
61
|
+
// no overlap there. Outside it (a full-screen pushed route) the bar is visible,
|
|
62
|
+
// so reserve its height like on phone/tablet.
|
|
63
|
+
if (MediaQuery.sizeOf(context).width >= DeviceType.large.breakpoint &&
|
|
64
|
+
KasyWebHeaderScope.of(context)) {
|
|
61
65
|
return 0;
|
|
62
66
|
}
|
|
63
67
|
return MediaQuery.paddingOf(context).top +
|
|
@@ -74,23 +78,47 @@ List<Widget> kasyOverlayPaddedSlivers(
|
|
|
74
78
|
BuildContext context, {
|
|
75
79
|
required List<Widget> slivers,
|
|
76
80
|
EdgeInsetsGeometry? contentPadding,
|
|
81
|
+
double? maxContentWidth,
|
|
77
82
|
}) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
const double topPad = KasySpacing.belowChromeContentGap;
|
|
84
|
+
final double bottomPad =
|
|
85
|
+
MediaQuery.paddingOf(context).bottom + KasySpacing.pageVerticalGutter;
|
|
86
|
+
const double gutter = KasySpacing.pageHorizontalGutter;
|
|
87
|
+
|
|
88
|
+
final Widget body = SliverMainAxisGroup(slivers: slivers);
|
|
89
|
+
|
|
90
|
+
// An explicit [contentPadding] takes full control; otherwise use the page
|
|
91
|
+
// gutters, optionally centering content within [maxContentWidth] on wide
|
|
92
|
+
// viewports (so lists never stretch edge-to-edge on desktop). Centering is
|
|
93
|
+
// computed from the real content width (SliverLayoutBuilder) so it stays
|
|
94
|
+
// correct inside the desktop shell where the sidebar already took space.
|
|
95
|
+
final Widget paddedGroup;
|
|
96
|
+
if (contentPadding != null) {
|
|
97
|
+
paddedGroup = SliverPadding(padding: contentPadding, sliver: body);
|
|
98
|
+
} else if (maxContentWidth == null) {
|
|
99
|
+
paddedGroup = SliverPadding(
|
|
100
|
+
padding: EdgeInsets.fromLTRB(gutter, topPad, gutter, bottomPad),
|
|
101
|
+
sliver: body,
|
|
102
|
+
);
|
|
103
|
+
} else {
|
|
104
|
+
paddedGroup = SliverLayoutBuilder(
|
|
105
|
+
builder: (context, constraints) {
|
|
106
|
+
final double available = constraints.crossAxisExtent;
|
|
107
|
+
final double horizontal = available - 2 * gutter > maxContentWidth
|
|
108
|
+
? (available - maxContentWidth) / 2
|
|
109
|
+
: gutter;
|
|
110
|
+
return SliverPadding(
|
|
111
|
+
padding: EdgeInsets.fromLTRB(horizontal, topPad, horizontal, bottomPad),
|
|
112
|
+
sliver: body,
|
|
113
|
+
);
|
|
114
|
+
},
|
|
115
|
+
);
|
|
116
|
+
}
|
|
86
117
|
return <Widget>[
|
|
87
118
|
SliverToBoxAdapter(
|
|
88
119
|
child: SizedBox(height: kasyAppBarBodyTopOverlap(context)),
|
|
89
120
|
),
|
|
90
|
-
|
|
91
|
-
padding: pad,
|
|
92
|
-
sliver: SliverMainAxisGroup(slivers: slivers),
|
|
93
|
-
),
|
|
121
|
+
paddedGroup,
|
|
94
122
|
];
|
|
95
123
|
}
|
|
96
124
|
|
|
@@ -285,8 +313,12 @@ class KasyAppBar extends StatelessWidget {
|
|
|
285
313
|
|
|
286
314
|
@override
|
|
287
315
|
Widget build(BuildContext context) {
|
|
288
|
-
// On desktop the web header owns the top chrome, so the page app bar hides
|
|
289
|
-
|
|
316
|
+
// On desktop the web header owns the top chrome, so the page app bar hides —
|
|
317
|
+
// but ONLY when there actually is a web header above (i.e. inside the shell's
|
|
318
|
+
// KasyWebHeaderScope). A full-screen route pushed over the shell has no web
|
|
319
|
+
// header, so the bar stays visible and its back button is never lost.
|
|
320
|
+
if (MediaQuery.sizeOf(context).width >= DeviceType.large.breakpoint &&
|
|
321
|
+
KasyWebHeaderScope.of(context)) {
|
|
290
322
|
return const SizedBox.shrink();
|
|
291
323
|
}
|
|
292
324
|
final Color orbFg = context.colors.onSurface;
|
|
@@ -461,6 +493,13 @@ class KasyOverlayScaffold extends StatelessWidget {
|
|
|
461
493
|
final Widget? trailing;
|
|
462
494
|
final List<Widget> slivers;
|
|
463
495
|
final EdgeInsetsGeometry? contentPadding;
|
|
496
|
+
|
|
497
|
+
/// When set, content is centered within this max width on wide viewports
|
|
498
|
+
/// (desktop) instead of stretching edge-to-edge. Use [kKasyContentMaxWidth]
|
|
499
|
+
/// for the standard single-column internal page. Ignored when
|
|
500
|
+
/// [contentPadding] is provided (that takes full control of the insets).
|
|
501
|
+
final double? maxContentWidth;
|
|
502
|
+
|
|
464
503
|
final ScrollController? scrollController;
|
|
465
504
|
final ScrollPhysics? physics;
|
|
466
505
|
final Color? backgroundColor;
|
|
@@ -480,6 +519,7 @@ class KasyOverlayScaffold extends StatelessWidget {
|
|
|
480
519
|
this.trailing,
|
|
481
520
|
required this.slivers,
|
|
482
521
|
this.contentPadding,
|
|
522
|
+
this.maxContentWidth,
|
|
483
523
|
this.scrollController,
|
|
484
524
|
this.physics,
|
|
485
525
|
this.backgroundColor,
|
|
@@ -504,6 +544,7 @@ class KasyOverlayScaffold extends StatelessWidget {
|
|
|
504
544
|
slivers: kasyOverlayPaddedSlivers(
|
|
505
545
|
context,
|
|
506
546
|
contentPadding: contentPadding,
|
|
547
|
+
maxContentWidth: maxContentWidth,
|
|
507
548
|
slivers: slivers,
|
|
508
549
|
),
|
|
509
550
|
);
|