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
|
@@ -1,4 +1,27 @@
|
|
|
1
1
|
{
|
|
2
|
+
"1.39.0": {
|
|
3
|
+
"modules": {
|
|
4
|
+
"core": {
|
|
5
|
+
"pt": "Proporção da interface na web virou um ajuste único e documentado: a web renderiza ~5% menor que o nativo (escala 0.95) em todos os tamanhos de tela, deixando a proporção mais próxima do app nativo. O nativo fica intacto (1.0) e respeitando a fonte do sistema, e dá pra desligar tudo numa linha (kWebViewportScaleEnabled). É uma camada de densidade por cima do design system responsivo, não substitui breakpoints/tipografia.",
|
|
6
|
+
"en": "The web interface proportion is now a single, documented knob: web renders ~5% smaller than native (0.95 scale) at every screen size, bringing it closer to the native app. Native stays untouched (1.0) and respects the system font size, and the whole thing can be turned off in one line (kWebViewportScaleEnabled). It is a density layer on top of the responsive design system, not a replacement for breakpoints/typography.",
|
|
7
|
+
"es": "La proporción de la interfaz en web ahora es un ajuste único y documentado: la web se renderiza ~5% más pequeña que el nativo (escala 0.95) en todos los tamaños de pantalla, acercándola al app nativo. El nativo queda intacto (1.0) y respeta el tamaño de fuente del sistema, y todo se puede apagar en una línea (kWebViewportScaleEnabled). Es una capa de densidad sobre el design system responsivo, no reemplaza breakpoints/tipografía."
|
|
8
|
+
},
|
|
9
|
+
"components": {
|
|
10
|
+
"pt": "Campo de texto com altura idêntica em qualquer renderizador e breakpoint (a linha é travada via strut, então web CanvasKit e nativo batem certinho, sem variar ~1px); e card clicável agora tem destaque ao passar o cursor na web, parecendo um controle interativo de verdade.",
|
|
11
|
+
"en": "Text field with identical height across every renderer and breakpoint (the line is locked via strut, so web CanvasKit and native match exactly, with no ~1px drift); and a tappable card now has a hover highlight on web, feeling like a real interactive control.",
|
|
12
|
+
"es": "Campo de texto con altura idéntica en cualquier renderizador y breakpoint (la línea se fija con strut, así web CanvasKit y nativo coinciden exactamente, sin variar ~1px); y una tarjeta clicable ahora tiene resaltado al pasar el cursor en web, sintiéndose como un control interactivo real."
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"1.38.0": {
|
|
17
|
+
"modules": {
|
|
18
|
+
"components": {
|
|
19
|
+
"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.",
|
|
20
|
+
"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.",
|
|
21
|
+
"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."
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
},
|
|
2
25
|
"1.37.0": {
|
|
3
26
|
"modules": {
|
|
4
27
|
"components": {
|
|
@@ -112,6 +112,7 @@ GET /admin/users
|
|
|
112
112
|
"email": "ana@b.com" | null,
|
|
113
113
|
"name": "Ana" | null,
|
|
114
114
|
"createdAt": 1700000000000, // epoch em milissegundos | null
|
|
115
|
+
"avatarPath": "https://.../thumb.jpg" | null, // foto do usuario, se tiver
|
|
115
116
|
"subscriber": true // tem assinatura ativa?
|
|
116
117
|
}
|
|
117
118
|
],
|
|
@@ -230,6 +231,20 @@ POST /users/{userId}/devices/cleanup-stale → remove devices anti
|
|
|
230
231
|
DELETE /users/{userId}/devices → remove todos os devices do usuário (ex.: no logout)
|
|
231
232
|
```
|
|
232
233
|
|
|
234
|
+
> **Device sem token de push (importante):** o app registra o device **mesmo sem
|
|
235
|
+
> permissão de push** — nesse caso o `token` vem **vazio** (`""`). É de propósito:
|
|
236
|
+
> rastreia a instalação e dispara a boas-vindas (abaixo) sem depender do push; o
|
|
237
|
+
> token é preenchido depois, via `PUT /devices/{deviceId}`, quando o usuário ativar
|
|
238
|
+
> as notificações. Seu backend deve **aceitar token vazio** e, ao enviar push,
|
|
239
|
+
> **pular** os devices com token vazio (não os trate como inválidos nem os apague).
|
|
240
|
+
|
|
241
|
+
> **Notificação de boas-vindas:** crie-a **uma única vez por conta**, no primeiro
|
|
242
|
+
> registro de device (`POST /users/{userId}/devices`), de forma **independente do
|
|
243
|
+
> push** (vale para conta anônima também). Persista só no banco (sem disparar push) e
|
|
244
|
+
> use o `extra_data.deviceLocale` enviado pelo device para localizar a mensagem
|
|
245
|
+
> (`pt`/`es`/`en`). Referência exata: o trigger `trigger_welcome_notification` do
|
|
246
|
+
> backend Supabase e o `onFirstDeviceRegistered` do Firebase.
|
|
247
|
+
|
|
233
248
|
### Endpoints: notifications
|
|
234
249
|
|
|
235
250
|
```
|
|
@@ -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);
|
|
@@ -118,12 +118,17 @@ class FirebaseDeviceApi implements DeviceApi {
|
|
|
118
118
|
await Future.delayed(const Duration(seconds: 1));
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
121
|
+
// Register the installation even without a push token. On iOS the FCM
|
|
122
|
+
// token stays null until the user grants notification permission (and
|
|
123
|
+
// getToken can even throw before APNS is ready). We still create the
|
|
124
|
+
// device row — so the welcome notification fires and the install is
|
|
125
|
+
// tracked — and the token fills in later via onTokenRefresh once push is
|
|
126
|
+
// enabled.
|
|
127
|
+
String token = '';
|
|
128
|
+
try {
|
|
129
|
+
token = await _messaging.getToken() ?? '';
|
|
130
|
+
} catch (_) {
|
|
131
|
+
token = '';
|
|
127
132
|
}
|
|
128
133
|
final os = Platform.isAndroid
|
|
129
134
|
? OperatingSystem.android //
|
|
@@ -16,6 +16,11 @@ class AdminUser {
|
|
|
16
16
|
final String? email;
|
|
17
17
|
final String? name;
|
|
18
18
|
final DateTime? createdAt;
|
|
19
|
+
|
|
20
|
+
/// Public photo URL when the user has an avatar (social login import or a
|
|
21
|
+
/// manual upload). Null/empty for users without a picture — the UI then
|
|
22
|
+
/// falls back to initials.
|
|
23
|
+
final String? avatarPath;
|
|
19
24
|
final bool subscriber;
|
|
20
25
|
|
|
21
26
|
const AdminUser({
|
|
@@ -24,6 +29,7 @@ class AdminUser {
|
|
|
24
29
|
this.email,
|
|
25
30
|
this.name,
|
|
26
31
|
this.createdAt,
|
|
32
|
+
this.avatarPath,
|
|
27
33
|
});
|
|
28
34
|
|
|
29
35
|
factory AdminUser.fromMap(Map<String, dynamic> m) {
|
|
@@ -35,6 +41,7 @@ class AdminUser {
|
|
|
35
41
|
createdAt: createdMillis is num
|
|
36
42
|
? DateTime.fromMillisecondsSinceEpoch(createdMillis.toInt())
|
|
37
43
|
: null,
|
|
44
|
+
avatarPath: m['avatarPath'] as String?,
|
|
38
45
|
subscriber: m['subscriber'] == true,
|
|
39
46
|
);
|
|
40
47
|
}
|
|
@@ -65,7 +72,8 @@ final adminUsersApiProvider = Provider<AdminUsersApi>(
|
|
|
65
72
|
/// Response 200: {
|
|
66
73
|
/// "users": [
|
|
67
74
|
/// {"id": "...", "email": "a@b.com"|null, "name": "Ana"|null,
|
|
68
|
-
/// "createdAt": 1700000000000 (epoch millis)|null,
|
|
75
|
+
/// "createdAt": 1700000000000 (epoch millis)|null,
|
|
76
|
+
/// "avatarPath": "https://.../thumb.jpg"|null, "subscriber": true}
|
|
69
77
|
/// ],
|
|
70
78
|
/// "totalUsers": 142, // true size of the collection
|
|
71
79
|
/// "truncated": false // true when more users exist than returned
|
|
@@ -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": "14166f7e3419884c514f93126a64795264f5f2a743b050a600bb37631e0d7e23",
|
|
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",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"api/lib/features/feedbacks/api/entities/feature_vote_entity.dart": "a13ccfc493d8f5277beffc8e28f77ec322ab0c0a56358c3669353206b7c6756b",
|
|
18
18
|
"api/lib/features/feedbacks/api/feature_request_api.dart": "a741395509b95e8cae135bc8bdcca1ba0d48785b1e1b201732219659d271346b",
|
|
19
19
|
"api/lib/features/feedbacks/api/feature_vote_api.dart": "89dcaeea29460a7f219c2ede3759707b45066272fe6837c29a40f3d79fade4e9",
|
|
20
|
-
"api/lib/features/notifications/api/device_api.dart": "
|
|
20
|
+
"api/lib/features/notifications/api/device_api.dart": "cedc21bf8db4eeaf63f9723d539d6afddde01b30332acfa9c70b22ba7de25698",
|
|
21
21
|
"api/lib/features/notifications/api/entities/device_entity.dart": "54fce6274c5fe60ba663d8693e0c9d88fd6cd6fcd9ec906e021621a2e51176b7",
|
|
22
22
|
"api/lib/features/notifications/api/entities/notifications_entity.dart": "f9ee62111a6f122657e105df317f1f5f803e3a525a96686c0a2728ff02cd8964",
|
|
23
23
|
"api/lib/features/notifications/api/notifications_api.dart": "30cdb1bc52c3249b2cda6b1c7eb7fb6a977db5744da5a9c2980784f41e75d9e7",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"api/lib/features/onboarding/api/user_infos_api.dart": "cf728bcef364259912ee1e87151c4f791faee0c0d49232c9af260bb5681bd429",
|
|
26
26
|
"api/lib/features/onboarding/models/user_info.dart": "946fd34a33b630a34a3b6248594cbca717f4fab9d23f669510145b9032bd1321",
|
|
27
27
|
"api/lib/features/onboarding/repositories/user_infos_repository.dart": "487a5177dcaed7f87e613b2b4f04329c4995274e6a52809d4917c037cb3c3c55",
|
|
28
|
-
"api/lib/features/settings/ui/components/admin/admin_users_api.dart": "
|
|
28
|
+
"api/lib/features/settings/ui/components/admin/admin_users_api.dart": "ed07e674817099691d6131e8ee7ced1332b9291f0eac207a720f20558e20e64a",
|
|
29
29
|
"api/lib/features/settings/ui/widgets/avatar_utils.dart": "bb9126409bbbb245f2dec613bd096ac53c208a56bd55f3d2ab2599e43534904f",
|
|
30
30
|
"api/lib/features/subscriptions/api/entities/subscription_entity.dart": "20c1d75ed9d88acb96e94a592dcb4de0f63c792302ea07df53cebbdeb6d0cf7e",
|
|
31
31
|
"api/lib/features/subscriptions/api/stripe_backend_api.dart": "e370bd05211462f2fc94c69af1749eb5330f997e9ec5e73e5c4e119999bf666f",
|
|
@@ -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": "14166f7e3419884c514f93126a64795264f5f2a743b050a600bb37631e0d7e23",
|
|
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",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"supabase/lib/features/feedbacks/api/entities/feature_vote_entity.dart": "a13ccfc493d8f5277beffc8e28f77ec322ab0c0a56358c3669353206b7c6756b",
|
|
49
49
|
"supabase/lib/features/feedbacks/api/feature_request_api.dart": "a741395509b95e8cae135bc8bdcca1ba0d48785b1e1b201732219659d271346b",
|
|
50
50
|
"supabase/lib/features/feedbacks/api/feature_vote_api.dart": "89dcaeea29460a7f219c2ede3759707b45066272fe6837c29a40f3d79fade4e9",
|
|
51
|
-
"supabase/lib/features/notifications/api/device_api.dart": "
|
|
51
|
+
"supabase/lib/features/notifications/api/device_api.dart": "cedc21bf8db4eeaf63f9723d539d6afddde01b30332acfa9c70b22ba7de25698",
|
|
52
52
|
"supabase/lib/features/notifications/api/entities/device_entity.dart": "54fce6274c5fe60ba663d8693e0c9d88fd6cd6fcd9ec906e021621a2e51176b7",
|
|
53
53
|
"supabase/lib/features/notifications/api/entities/notifications_entity.dart": "f9ee62111a6f122657e105df317f1f5f803e3a525a96686c0a2728ff02cd8964",
|
|
54
54
|
"supabase/lib/features/notifications/api/notifications_api.dart": "30cdb1bc52c3249b2cda6b1c7eb7fb6a977db5744da5a9c2980784f41e75d9e7",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"supabase/lib/features/onboarding/api/user_infos_api.dart": "cf728bcef364259912ee1e87151c4f791faee0c0d49232c9af260bb5681bd429",
|
|
57
57
|
"supabase/lib/features/onboarding/models/user_info.dart": "946fd34a33b630a34a3b6248594cbca717f4fab9d23f669510145b9032bd1321",
|
|
58
58
|
"supabase/lib/features/onboarding/repositories/user_infos_repository.dart": "487a5177dcaed7f87e613b2b4f04329c4995274e6a52809d4917c037cb3c3c55",
|
|
59
|
-
"supabase/lib/features/settings/ui/components/admin/admin_users_api.dart": "
|
|
59
|
+
"supabase/lib/features/settings/ui/components/admin/admin_users_api.dart": "ed07e674817099691d6131e8ee7ced1332b9291f0eac207a720f20558e20e64a",
|
|
60
60
|
"supabase/lib/features/settings/ui/widgets/avatar_utils.dart": "bb9126409bbbb245f2dec613bd096ac53c208a56bd55f3d2ab2599e43534904f",
|
|
61
61
|
"supabase/lib/features/subscriptions/api/entities/subscription_entity.dart": "20c1d75ed9d88acb96e94a592dcb4de0f63c792302ea07df53cebbdeb6d0cf7e",
|
|
62
62
|
"supabase/lib/features/subscriptions/api/stripe_backend_api.dart": "e370bd05211462f2fc94c69af1749eb5330f997e9ec5e73e5c4e119999bf666f",
|
|
@@ -39,6 +39,7 @@ interface AdminUser {
|
|
|
39
39
|
email: string | null;
|
|
40
40
|
name: string | null;
|
|
41
41
|
createdAt: number | null; // epoch millis
|
|
42
|
+
avatarPath: string | null; // public photo URL, when the user has one
|
|
42
43
|
subscriber: boolean;
|
|
43
44
|
}
|
|
44
45
|
|
|
@@ -105,7 +106,7 @@ Deno.serve(async (req: Request) => {
|
|
|
105
106
|
// client re-sorts anyway, so this only decides which rows survive the cap.
|
|
106
107
|
const { data: rows, error: rowsError } = await admin
|
|
107
108
|
.from("users")
|
|
108
|
-
.select("id, email, name, creation_date")
|
|
109
|
+
.select("id, email, name, creation_date, avatar_url")
|
|
109
110
|
.order("creation_date", { ascending: false, nullsFirst: false })
|
|
110
111
|
.limit(MAX_SCAN);
|
|
111
112
|
if (rowsError) throw rowsError;
|
|
@@ -132,6 +133,7 @@ Deno.serve(async (req: Request) => {
|
|
|
132
133
|
email: (d.email as string) || null,
|
|
133
134
|
name: (d.name as string) || null,
|
|
134
135
|
createdAt: d.creation_date ? new Date(d.creation_date).getTime() : null,
|
|
136
|
+
avatarPath: (d.avatar_url as string) || null,
|
|
135
137
|
subscriber: activeSubscribers.has(d.id),
|
|
136
138
|
}));
|
|
137
139
|
|
|
@@ -295,6 +295,9 @@ Deno.serve(async (req: Request) => {
|
|
|
295
295
|
const staleTokenIds: string[] = [];
|
|
296
296
|
|
|
297
297
|
for (const device of devices) {
|
|
298
|
+
// Installs without a push token (notifications not enabled yet) aren't
|
|
299
|
+
// sendable and must NOT be treated as stale/deleted — just skip them.
|
|
300
|
+
if (!device.token) continue;
|
|
298
301
|
const ok = await sendToToken(projectId, accessToken, device.token, notification.title, notification.body, extraData, notification.image_url);
|
|
299
302
|
if (ok) {
|
|
300
303
|
sent++;
|
package/lib/scaffold/backends/supabase/migrations/20240101000012_welcome_decouple_from_push.sql
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
-- Decouple the welcome notification from push permission (parity with Firebase).
|
|
2
|
+
--
|
|
3
|
+
-- Before: the welcome was SKIPPED for anonymous users (no email) and read the
|
|
4
|
+
-- locale from public.users.locale — which is null at signup, so it always fell
|
|
5
|
+
-- back to English. Combined with the fact that a device only registered once a
|
|
6
|
+
-- push token existed (iOS required permission), users who skipped push never got
|
|
7
|
+
-- a welcome at all.
|
|
8
|
+
--
|
|
9
|
+
-- Now: the install registers even without a push token (client change), so this
|
|
10
|
+
-- trigger fires for EVERY account on first device registration, and localises
|
|
11
|
+
-- from the device's own reported locale (device.extra_data.deviceLocale) —
|
|
12
|
+
-- exactly like the Firebase `onFirstDeviceRegistered` trigger. The one-time
|
|
13
|
+
-- `welcome_sent` claim and notify_user = false (DB only, no push) are unchanged.
|
|
14
|
+
|
|
15
|
+
CREATE OR REPLACE FUNCTION public.trigger_welcome_notification()
|
|
16
|
+
RETURNS TRIGGER AS $$
|
|
17
|
+
DECLARE
|
|
18
|
+
v_locale text;
|
|
19
|
+
v_claimed int;
|
|
20
|
+
v_title text;
|
|
21
|
+
v_body text;
|
|
22
|
+
BEGIN
|
|
23
|
+
-- Atomically claim the one-time welcome: flip welcome_sent to true only if it
|
|
24
|
+
-- isn't already. ROW_COUNT is 1 for the very first claim, 0 for any later
|
|
25
|
+
-- device registration — including one re-created after a logout/login cycle.
|
|
26
|
+
UPDATE public.users
|
|
27
|
+
SET welcome_sent = true
|
|
28
|
+
WHERE id = NEW.user_id AND welcome_sent = false;
|
|
29
|
+
|
|
30
|
+
GET DIAGNOSTICS v_claimed = ROW_COUNT;
|
|
31
|
+
IF v_claimed = 0 THEN
|
|
32
|
+
RETURN NEW;
|
|
33
|
+
END IF;
|
|
34
|
+
|
|
35
|
+
-- Locale from the device that just registered (e.g. 'pt_BR' -> 'pt'),
|
|
36
|
+
-- independent of push permission. Falls back to English.
|
|
37
|
+
v_locale := lower(substring(coalesce(NEW.extra_data->>'deviceLocale', 'en') from 1 for 2));
|
|
38
|
+
|
|
39
|
+
IF v_locale = 'pt' THEN
|
|
40
|
+
v_title := 'Bem-vindo!';
|
|
41
|
+
v_body := 'Sua conta está pronta. Aproveite!';
|
|
42
|
+
ELSIF v_locale = 'es' THEN
|
|
43
|
+
v_title := '¡Bienvenido!';
|
|
44
|
+
v_body := 'Tu cuenta está lista. ¡Disfrútala!';
|
|
45
|
+
ELSE
|
|
46
|
+
v_title := 'Welcome!';
|
|
47
|
+
v_body := 'Your account is ready. Enjoy!';
|
|
48
|
+
END IF;
|
|
49
|
+
|
|
50
|
+
-- notify_user = false: user is inside the app, no push needed.
|
|
51
|
+
INSERT INTO public.notifications (user_id, title, body, type, notify_user)
|
|
52
|
+
VALUES (NEW.user_id, v_title, v_body, 'welcome', false);
|
|
53
|
+
|
|
54
|
+
RETURN NEW;
|
|
55
|
+
END;
|
|
56
|
+
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
|
57
|
+
|
|
58
|
+
-- Re-assert the trigger so a database built from these migrations is consistent.
|
|
59
|
+
DROP TRIGGER IF EXISTS on_first_device_registered ON public.devices;
|
|
60
|
+
CREATE TRIGGER on_first_device_registered
|
|
61
|
+
AFTER INSERT ON public.devices
|
|
62
|
+
FOR EACH ROW EXECUTE PROCEDURE public.trigger_welcome_notification();
|
|
@@ -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);
|
|
@@ -98,12 +98,17 @@ class FirebaseDeviceApi implements DeviceApi {
|
|
|
98
98
|
await Future.delayed(const Duration(seconds: 1));
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
101
|
+
// Register the installation even without a push token. On iOS the FCM
|
|
102
|
+
// token stays null until the user grants notification permission (and
|
|
103
|
+
// getToken can even throw before APNS is ready). We still create the
|
|
104
|
+
// device row — so the welcome notification fires and the install is
|
|
105
|
+
// tracked — and the token fills in later via onTokenRefresh once push is
|
|
106
|
+
// enabled.
|
|
107
|
+
String token = '';
|
|
108
|
+
try {
|
|
109
|
+
token = await _messaging.getToken() ?? '';
|
|
110
|
+
} catch (_) {
|
|
111
|
+
token = '';
|
|
107
112
|
}
|
|
108
113
|
final os = Platform.isAndroid
|
|
109
114
|
? OperatingSystem.android //
|
|
@@ -16,6 +16,11 @@ class AdminUser {
|
|
|
16
16
|
final String? email;
|
|
17
17
|
final String? name;
|
|
18
18
|
final DateTime? createdAt;
|
|
19
|
+
|
|
20
|
+
/// Public photo URL when the user has an avatar (social login import or a
|
|
21
|
+
/// manual upload). Null/empty for users without a picture — the UI then
|
|
22
|
+
/// falls back to initials.
|
|
23
|
+
final String? avatarPath;
|
|
19
24
|
final bool subscriber;
|
|
20
25
|
|
|
21
26
|
const AdminUser({
|
|
@@ -24,6 +29,7 @@ class AdminUser {
|
|
|
24
29
|
this.email,
|
|
25
30
|
this.name,
|
|
26
31
|
this.createdAt,
|
|
32
|
+
this.avatarPath,
|
|
27
33
|
});
|
|
28
34
|
|
|
29
35
|
factory AdminUser.fromMap(Map<String, dynamic> m) {
|
|
@@ -35,6 +41,7 @@ class AdminUser {
|
|
|
35
41
|
createdAt: createdMillis is num
|
|
36
42
|
? DateTime.fromMillisecondsSinceEpoch(createdMillis.toInt())
|
|
37
43
|
: null,
|
|
44
|
+
avatarPath: m['avatarPath'] as String?,
|
|
38
45
|
subscriber: m['subscriber'] == true,
|
|
39
46
|
);
|
|
40
47
|
}
|
|
@@ -1381,13 +1381,19 @@ async function removeFacebookSigninFromAuthPages(projectDir) {
|
|
|
1381
1381
|
// Legacy: remove the standalone FacebookSigninComponent + its import, if present.
|
|
1382
1382
|
content = content.replace(/^import 'package:[^']+\/features\/authentication\/ui\/components\/facebook_signin\.dart';\n/m, '');
|
|
1383
1383
|
content = content.replace(/[ \t]*const FacebookSigninComponent\(\),\n/g, '');
|
|
1384
|
-
// Current UI: the Facebook button is an inline
|
|
1385
|
-
// in
|
|
1386
|
-
//
|
|
1387
|
-
// label so the Google/Apple tiles are never touched. Runs before dartFix/format,
|
|
1388
|
-
// it matches the kit's raw formatting verbatim.
|
|
1384
|
+
// Current UI: the Facebook button is an inline SocialAuthButton in the social
|
|
1385
|
+
// row, wrapped in a collection-if (`if (showFacebook) ...[ ... ]`). Strip the
|
|
1386
|
+
// WHOLE if-block — wrapper, SizedBox spacer and tile — anchored on the facebook
|
|
1387
|
+
// label so the Google/Apple tiles are never touched. Runs before dartFix/format,
|
|
1388
|
+
// so it matches the kit's raw formatting verbatim.
|
|
1389
1389
|
content = content.replace(
|
|
1390
|
-
/\n[ \t]*const SizedBox\(width: KasySpacing\.sm\),\n[ \t]*Expanded\(\n[ \t]*child:
|
|
1390
|
+
/\n[ \t]*if \(showFacebook\) \.\.\.\[\n[ \t]*const SizedBox\(width: KasySpacing\.sm\),\n[ \t]*Expanded\(\n[ \t]*child: SocialAuthButton\(\n[ \t]*label: t\.auth\.signin\.facebook,[\s\S]*?\.signinWithFacebook\(\),\n[ \t]*\),\n[ \t]*\),\n[ \t]*\],/,
|
|
1391
|
+
'',
|
|
1392
|
+
);
|
|
1393
|
+
// …then drop the now-unused `showFacebook` declaration (+ its comment), so the
|
|
1394
|
+
// stripped page has no dangling variable / analyzer warning.
|
|
1395
|
+
content = content.replace(
|
|
1396
|
+
/\n[ \t]*\/\/ Facebook on web works[\s\S]*?\n[ \t]*const bool showFacebook = [^;]*;/,
|
|
1391
1397
|
'',
|
|
1392
1398
|
);
|
|
1393
1399
|
await fs.writeFile(p, content, 'utf8');
|
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,1781475668120,4a8e13af757aaae5b1160a761d56555ef4370ca7572732cbfa8d98c989424ab0
|
|
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
|
|
@@ -53,23 +51,25 @@ assets/assets/icons/google_play_games.png,1772398653083,533c1d87d7be8690ab173ef4
|
|
|
53
51
|
assets/assets/icons/google.png,1772398653083,f423e7e7be1e06008d45617d07f095f04da7fdcab9a56523f9e0633828e464e0
|
|
54
52
|
assets/assets/icons/facebook.png,1772398653083,79ac67e449c1db63319d43329ca91f682b4e0bc6a0883b0dfb2a849e13ae6eb6
|
|
55
53
|
assets/assets/icons/apple.png,1772398653083,2a737d8801ca81452b2978bb2e1ac72136acf1a4c338f2de2f77b65e9f6de1fa
|
|
56
|
-
version.json,
|
|
57
|
-
flutter_service_worker.js,
|
|
58
|
-
assets/
|
|
59
|
-
|
|
60
|
-
assets/
|
|
61
|
-
|
|
62
|
-
assets/
|
|
63
|
-
assets/
|
|
64
|
-
assets/
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-
|
|
70
|
-
assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-
|
|
71
|
-
assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-
|
|
72
|
-
assets/packages/lucide_icons_flutter/assets/
|
|
73
|
-
assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-
|
|
74
|
-
assets/
|
|
75
|
-
|
|
54
|
+
version.json,1781481118464,be3307736a7e600c6e90593352859f1c46bc97666d5cf21a16099effd2a0fed6
|
|
55
|
+
flutter_service_worker.js,1781481120062,baeeaf9f4b8e6f40d3b0549429ceb70ca01f120db6a7ef2f60d5809233dc2205
|
|
56
|
+
assets/FontManifest.json,1781481118640,4d84ab517c27984d36f9a3c8be6f2a72788c0c3985c1d5874297fef0a53407ca
|
|
57
|
+
flutter_bootstrap.js,1781481074600,0955c783148666dcda1406de86f477f1f27ec80a4fc8186ba6cd9bd119d242f9
|
|
58
|
+
assets/AssetManifest.bin,1781481118640,78bccb08a36307a400711a1d7e6868bd5f89a24e63439fb5b6747660323e4475
|
|
59
|
+
index.html,1781481074612,bb8142eb84e9e44049957c3edf86006e1ae5f194c397a8326ce505b68fdf58f4
|
|
60
|
+
assets/AssetManifest.bin.json,1781481118640,50a11b51c3fc4cbed1b5ec3a4f466c6a6b75c481d08d4782c8191f99bdbdba9c
|
|
61
|
+
assets/shaders/stretch_effect.frag,1781481118746,1a7d4ac2be40cf0a459dfb390ef08bcd740f37913ffdee8de3c2ea836a18410e
|
|
62
|
+
assets/shaders/ink_sparkle.frag,1781481118746,1c8e222328206d1e06754f76fb53947aad38d62180aafad5298a3c6f510b173d
|
|
63
|
+
assets/packages/cupertino_icons/assets/CupertinoIcons.ttf,1781481119831,12bc6bf55aad4657f62747b6c1c9b5c120a594ed3540db21729b6db3c847340d
|
|
64
|
+
assets/fonts/MaterialIcons-Regular.otf,1781481119828,0d07767255068e38fcf81755fd997bb15d94d6ef83bb81bd0149f974bfe359a0
|
|
65
|
+
main.dart.js_3.part.js,1781481106966,7dd538a2118493eb8ff1a33ec5c2dd6d355a9a61290f1d8d1e6ad7bf8cc0465f
|
|
66
|
+
main.dart.js_1.part.js,1781481106961,a9d49c1a6aff2181d2d352ebe6a64a58c776ece7784ca86546b17be94ffe42b0
|
|
67
|
+
assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-w100.ttf,1781481119830,c16d95412fe2600bda1f53c2b8dc4bd82496f60f7c5a6ecfa180b0f866128712
|
|
68
|
+
assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-w400.ttf,1781481119832,edcd01ee13f6ec2285fd18863c0ce0efb0876b290fb427ad5c052f753b3210c8
|
|
69
|
+
assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-w600.ttf,1781481119834,008f1e974c9ff4e8d40d16221226e0d34d2298a7feb2bdea8c134a9333929c9c
|
|
70
|
+
assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-w500.ttf,1781481119830,3f68f958d0fa13077968a6767339c38ede8705bcadd0382078db6ec4bac96fa0
|
|
71
|
+
assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-w300.ttf,1781481119832,aebf5e4f6b1e6de1e61a866f813aa5b08efb4cdd108cc2831d40aad71398c88f
|
|
72
|
+
assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-w200.ttf,1781481119830,1f798186cfc3e2fcd54b15aa7b8695bc7c29b781c5058029a8d80ecdfa615904
|
|
73
|
+
assets/NOTICES,1781481118640,a18efc42c5ed99b56481537d0f229ddd3add671c548a893aaf8766f30c854158
|
|
74
|
+
assets/packages/lucide_icons_flutter/assets/lucide.ttf,1781481119830,9b000e47cddf9ada4a6b85fc9577fef7ed2ee261c9279e9a44e4753da28f83d3
|
|
75
|
+
main.dart.js,1781481107454,c98fad1aa11ff6a9335379794ac025a501060a6759943e74365f185cc7a5e8ce
|
|
@@ -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).
|
|
@@ -103,11 +103,23 @@ whole app, edit the numbers in `KasyTypeScale` — nothing else. The live ramp i
|
|
|
103
103
|
visible under **Design System → Typography** (tabs per breakpoint).
|
|
104
104
|
|
|
105
105
|
**Web render scale (`WebViewportScale`, `lib/core/web_viewport_scale.dart`).** A
|
|
106
|
-
separate concern from type sizes
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
106
|
+
separate concern from type sizes, sitting in a layer ON TOP of the responsive
|
|
107
|
+
system — it does NOT replace breakpoints, the type ramp or spacing (turn it off
|
|
108
|
+
and the app is still fully responsive, just larger). Flutter web renders ~10%
|
|
109
|
+
larger than an equivalent native/HTML app at 100% zoom, **at every width**. So the
|
|
110
|
+
whole web app — phone, tablet and desktop — is scaled to `0.95` (~5%; a true
|
|
111
|
+
viewport scale, so layout, scrolling and hit-testing stay correct), making web
|
|
112
|
+
read close to the native baseline with **no size jump across breakpoints**. `0.90`
|
|
113
|
+
fully undoes the 10% but reads small on a monitor (native sizes are tuned for a
|
|
114
|
+
phone held close), so `0.95` (the midpoint) is the calibrated default. Two knobs:
|
|
115
|
+
`kWebViewportScaleEnabled` (master on/off — `false` makes web render at native
|
|
116
|
+
`1.0`) and `kWebViewportScale` (the value). Two things are never scaled, both at
|
|
117
|
+
native `1.0`: **native** itself (gated on `kIsWeb`, so iOS/Android/macOS/Windows
|
|
118
|
+
respect the system text-size for accessibility) and the **in-app device preview**
|
|
119
|
+
(it simulates a native device, so the scale is skipped while it is on — a
|
|
120
|
+
developer previewing a phone sees the native size). On desktop a high-OS-scale
|
|
121
|
+
screen compensates below the cap, keyed off the screen width (see
|
|
122
|
+
`kWebViewportScaleTargetWidth`).
|
|
111
123
|
|
|
112
124
|
---
|
|
113
125
|
|
|
@@ -139,7 +151,7 @@ Never pass a numeric `size:` to `Icon`; use a token so re-scaling is one edit.
|
|
|
139
151
|
## Radius — `KasyRadius` (`lib/core/theme/radius.dart`)
|
|
140
152
|
|
|
141
153
|
Semantic: `xs 4 · sm 8 · md 12 · lg 16 · xl 24 · full 999`.
|
|
142
|
-
`KasyCard` defaults to `
|
|
154
|
+
`KasyCard` defaults to `xl` (24).
|
|
143
155
|
|
|
144
156
|
---
|
|
145
157
|
|
|
@@ -150,7 +162,9 @@ Key tokens: `primary`, `onPrimary`, `surface`, `onSurface`, `onBackground`,
|
|
|
150
162
|
`muted`, `fieldLabel`, `outline`, `error` / `onError`, `success`, `warning`,
|
|
151
163
|
`surfacePrimarySoft`, `surfaceNeutralSoft`, `surfaceErrorSoft`.
|
|
152
164
|
|
|
153
|
-
|
|
165
|
+
Accent: HeroUI blue `#0485F7` (white foreground) — the kit's default theme
|
|
166
|
+
accent. The Kasy brand identity (logo, marketing) uses avocado green `#D2F51E`
|
|
167
|
+
and deep green `#011820`; rebranding is a one-place token swap in `KasyColors`.
|
|
154
168
|
Disabled state: blend the colour toward the surface (opaque), never raw opacity.
|
|
155
169
|
|
|
156
170
|
---
|
|
@@ -193,7 +207,7 @@ a mouse click never doubles the keyboard focus ring and the border never drops
|
|
|
193
207
|
while the calendar is open.
|
|
194
208
|
|
|
195
209
|
### `KasyCard`
|
|
196
|
-
Elevated surface panel, radius `KasyRadius.
|
|
210
|
+
Elevated surface panel, radius `KasyRadius.xl` (24). The base for screen content.
|
|
197
211
|
|
|
198
212
|
### `KasyScreen`
|
|
199
213
|
Opt-in screen scaffold. A new page wrapped in it gets the internal-screen
|
|
@@ -222,6 +236,19 @@ New internal screens follow the sidebar / Home ruler:
|
|
|
222
236
|
- Haptics only on native (`if (!kIsWeb)`); "hide chrome on scroll" only on the
|
|
223
237
|
phone breakpoint (`width < 768`).
|
|
224
238
|
|
|
239
|
+
**Content width — two tiers (width follows content type):**
|
|
240
|
+
- *Read / form screens* (Settings, Notifications, Reminder) → **contained + centred**
|
|
241
|
+
at `kKasyContentMaxWidth` (600, in `core/widgets/responsive_layout.dart`). With the
|
|
242
|
+
overlay pattern, set `KasyOverlayScaffold(maxContentWidth: kKasyContentMaxWidth)`
|
|
243
|
+
(centres the column on desktop, keeps the 16 gutter on mobile); with `KasyScreen`
|
|
244
|
+
it is the default. Long lines and lone controls read badly stretched, so cap them.
|
|
245
|
+
The Settings desktop master/detail centres the **nav + detail as one unit**.
|
|
246
|
+
- *Browse / feed / dashboard screens* (Home) → **full width + the 16 gutter, on
|
|
247
|
+
purpose** — width helps a grid/feed. Do **not** contain these.
|
|
248
|
+
- Every tier shares the same family (16 gutter, `KasyCard`, sidebar + web header) —
|
|
249
|
+
only the max width changes. To mirror: copy **Settings / Notifications** for a
|
|
250
|
+
contained screen, **Home** for a full-width one.
|
|
251
|
+
|
|
225
252
|
---
|
|
226
253
|
|
|
227
254
|
## Keeping it consistent — the guard-rail (optional)
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="814" height="1000">
|
|
2
|
+
<path d="M788.1 340.9c-5.8 4.5-108.2 62.2-108.2 190.5 0 148.4 130.3 200.9 134.2 202.2-.6 3.2-20.7 71.9-68.7 141.9-42.8 61.6-87.5 123.1-155.5 123.1s-85.5-39.5-164-39.5c-76.5 0-103.7 40.8-165.9 40.8s-105.6-57-155.5-127C46.7 790.7 0 663 0 541.8c0-194.4 126.4-297.5 250.8-297.5 66.1 0 121.2 43.4 162.7 43.4 39.5 0 101.1-46 176.3-46 28.5 0 130.9 2.6 198.3 99.2zm-234-181.5c31.1-36.9 53.1-88.1 53.1-139.3 0-7.1-.6-14.3-1.9-20.1-50.6 1.9-110.8 33.7-147.1 75.8-28.5 32.4-55.1 83.6-55.1 135.5 0 7.8 1.3 15.6 1.9 18.1 3.2.6 8.4 1.3 13.6 1.3 45.4 0 102.5-30.4 135.5-71.3z"/>
|
|
3
|
+
</svg>
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="842.32007" height="1000.0001">
|
|
3
|
+
<path fill="#fff" d="M824.66636 779.30363c-15.12299 34.93724-33.02368 67.09674-53.7638 96.66374-28.27076 40.3074-51.4182 68.2078-69.25717 83.7012-27.65347 25.4313-57.2822 38.4556-89.00964 39.1963-22.77708 0-50.24539-6.4813-82.21973-19.629-32.07926-13.0861-61.55985-19.5673-88.51583-19.5673-28.27075 0-58.59083 6.4812-91.02193 19.5673-32.48053 13.1477-58.64639 19.9994-78.65196 20.6784-30.42501 1.29623-60.75123-12.0985-91.02193-40.2457-19.32039-16.8514-43.48632-45.7394-72.43607-86.6641-31.060778-43.7024-56.597041-94.37983-76.602609-152.15586C10.740416 658.44309 0 598.01283 0 539.50845c0-67.01648 14.481044-124.8172 43.486336-173.25401C66.28194 327.34823 96.60818 296.6578 134.5638 274.1276c37.95566-22.53016 78.96676-34.01129 123.1321-34.74585 24.16591 0 55.85633 7.47508 95.23784 22.166 39.27042 14.74029 64.48571 22.21538 75.54091 22.21538 8.26518 0 36.27668-8.7405 83.7629-26.16587 44.90607-16.16001 82.80614-22.85118 113.85458-20.21546 84.13326 6.78992 147.34122 39.95559 189.37699 99.70686-75.24463 45.59122-112.46573 109.4473-111.72502 191.36456.67899 63.8067 23.82643 116.90384 69.31888 159.06309 20.61664 19.56727 43.64066 34.69027 69.2571 45.4307-5.55531 16.11062-11.41933 31.54225-17.65372 46.35662zM631.70926 20.0057c0 50.01141-18.27108 96.70693-54.6897 139.92782-43.94932 51.38118-97.10817 81.07162-154.75459 76.38659-.73454-5.99983-1.16045-12.31444-1.16045-18.95003 0-48.01091 20.9006-99.39207 58.01678-141.40314 18.53027-21.27094 42.09746-38.95744 70.67685-53.0663C578.3158 9.00229 605.2903 1.31621 630.65988 0c.74076 6.68575 1.04938 13.37191 1.04938 20.00505z"/>
|
|
4
|
+
</svg>
|