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
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import 'package:flutter/widgets.dart';
|
|
2
|
+
import 'package:kasy_kit/components/kasy_avatar_presets.dart';
|
|
3
|
+
|
|
4
|
+
/// Declares what the desktop application bar ([KasyAppBar.application]) shows for
|
|
5
|
+
/// a given screen.
|
|
6
|
+
///
|
|
7
|
+
/// The shell (`WebContentWrapper`) renders one application bar above all content
|
|
8
|
+
/// and seeds it with an app-wide default. A screen overrides that default by
|
|
9
|
+
/// wrapping its body in a [KasyAppBarConfigurator] (or, for screens built on
|
|
10
|
+
/// `KasyOverlayScaffold`, by passing `applicationBar:`). So each screen decides
|
|
11
|
+
/// its own desktop chrome — search here, just notifications there — instead of
|
|
12
|
+
/// every screen sharing one fixed bar.
|
|
13
|
+
///
|
|
14
|
+
/// Each element has a `showX` flag (presets toggle these) plus the data/callbacks
|
|
15
|
+
/// it needs when shown. A bare [KasyAppBarConfig] mirrors the app default.
|
|
16
|
+
@immutable
|
|
17
|
+
class KasyAppBarConfig {
|
|
18
|
+
// Search ------------------------------------------------------------------
|
|
19
|
+
final bool showSearch;
|
|
20
|
+
final TextEditingController? searchController;
|
|
21
|
+
final String searchHint;
|
|
22
|
+
final ValueChanged<String>? onSearchChanged;
|
|
23
|
+
final ValueChanged<String>? onSearchSubmitted;
|
|
24
|
+
|
|
25
|
+
// Theme toggle ------------------------------------------------------------
|
|
26
|
+
final bool showThemeToggle;
|
|
27
|
+
final VoidCallback? onToggleTheme;
|
|
28
|
+
|
|
29
|
+
// Notifications -----------------------------------------------------------
|
|
30
|
+
final bool showNotifications;
|
|
31
|
+
|
|
32
|
+
/// Custom notifications control (e.g. a data-aware bell). Replaces the built-in
|
|
33
|
+
/// bell when set and [showNotifications] is true.
|
|
34
|
+
final Widget? notifications;
|
|
35
|
+
final VoidCallback? onNotifications;
|
|
36
|
+
final bool showNotificationBadge;
|
|
37
|
+
|
|
38
|
+
// Create ------------------------------------------------------------------
|
|
39
|
+
final bool showCreate;
|
|
40
|
+
final String createLabel;
|
|
41
|
+
final VoidCallback? onCreate;
|
|
42
|
+
|
|
43
|
+
// Profile -----------------------------------------------------------------
|
|
44
|
+
final bool showAvatar;
|
|
45
|
+
final Widget? avatar;
|
|
46
|
+
final KasyAvatarGradientData? avatarGradient;
|
|
47
|
+
final VoidCallback? onAvatarTap;
|
|
48
|
+
|
|
49
|
+
/// Full control: every element shown by default (matches the app shell). Set a
|
|
50
|
+
/// `showX` to false to drop that element, or pass data/callbacks to wire it.
|
|
51
|
+
const KasyAppBarConfig({
|
|
52
|
+
this.showSearch = true,
|
|
53
|
+
this.searchController,
|
|
54
|
+
this.searchHint = 'Search...',
|
|
55
|
+
this.onSearchChanged,
|
|
56
|
+
this.onSearchSubmitted,
|
|
57
|
+
this.showThemeToggle = true,
|
|
58
|
+
this.onToggleTheme,
|
|
59
|
+
this.showNotifications = true,
|
|
60
|
+
this.notifications,
|
|
61
|
+
this.onNotifications,
|
|
62
|
+
this.showNotificationBadge = false,
|
|
63
|
+
this.showCreate = true,
|
|
64
|
+
this.createLabel = 'Create',
|
|
65
|
+
this.onCreate,
|
|
66
|
+
this.showAvatar = false,
|
|
67
|
+
this.avatar,
|
|
68
|
+
this.avatarGradient,
|
|
69
|
+
this.onAvatarTap,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
/// Preset: only the theme toggle — the quietest bar (settings-style screens).
|
|
73
|
+
const KasyAppBarConfig.minimal({VoidCallback? onToggleTheme})
|
|
74
|
+
: this(
|
|
75
|
+
showSearch: false,
|
|
76
|
+
showThemeToggle: true,
|
|
77
|
+
onToggleTheme: onToggleTheme,
|
|
78
|
+
showNotifications: false,
|
|
79
|
+
showCreate: false,
|
|
80
|
+
showAvatar: false,
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
/// Preset: search + theme, no quick-create (browse / list screens).
|
|
84
|
+
const KasyAppBarConfig.search({
|
|
85
|
+
TextEditingController? controller,
|
|
86
|
+
String hint = 'Search...',
|
|
87
|
+
ValueChanged<String>? onChanged,
|
|
88
|
+
ValueChanged<String>? onSubmitted,
|
|
89
|
+
VoidCallback? onToggleTheme,
|
|
90
|
+
}) : this(
|
|
91
|
+
showSearch: true,
|
|
92
|
+
searchController: controller,
|
|
93
|
+
searchHint: hint,
|
|
94
|
+
onSearchChanged: onChanged,
|
|
95
|
+
onSearchSubmitted: onSubmitted,
|
|
96
|
+
showThemeToggle: true,
|
|
97
|
+
onToggleTheme: onToggleTheme,
|
|
98
|
+
showNotifications: false,
|
|
99
|
+
showCreate: false,
|
|
100
|
+
showAvatar: false,
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
/// Show ONLY the listed elements (everything else hidden), keeping each one's
|
|
104
|
+
/// data and callbacks from the shell default — so a screen can say "just the
|
|
105
|
+
/// notifications and theme here" without rebuilding the bell or theme wiring.
|
|
106
|
+
///
|
|
107
|
+
/// ```dart
|
|
108
|
+
/// configure: (bar) => bar.only(search: true), // browse screen
|
|
109
|
+
/// configure: (bar) => bar.only(notifications: true, themeToggle: true),
|
|
110
|
+
/// ```
|
|
111
|
+
KasyAppBarConfig only({
|
|
112
|
+
bool search = false,
|
|
113
|
+
bool themeToggle = false,
|
|
114
|
+
bool notifications = false,
|
|
115
|
+
bool create = false,
|
|
116
|
+
bool avatar = false,
|
|
117
|
+
}) {
|
|
118
|
+
return copyWith(
|
|
119
|
+
showSearch: search,
|
|
120
|
+
showThemeToggle: themeToggle,
|
|
121
|
+
showNotifications: notifications,
|
|
122
|
+
showCreate: create,
|
|
123
|
+
showAvatar: avatar,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/// Copy with selective overrides — handy for adapting the shell default.
|
|
128
|
+
KasyAppBarConfig copyWith({
|
|
129
|
+
bool? showSearch,
|
|
130
|
+
TextEditingController? searchController,
|
|
131
|
+
String? searchHint,
|
|
132
|
+
ValueChanged<String>? onSearchChanged,
|
|
133
|
+
ValueChanged<String>? onSearchSubmitted,
|
|
134
|
+
bool? showThemeToggle,
|
|
135
|
+
VoidCallback? onToggleTheme,
|
|
136
|
+
bool? showNotifications,
|
|
137
|
+
Widget? notifications,
|
|
138
|
+
VoidCallback? onNotifications,
|
|
139
|
+
bool? showNotificationBadge,
|
|
140
|
+
bool? showCreate,
|
|
141
|
+
String? createLabel,
|
|
142
|
+
VoidCallback? onCreate,
|
|
143
|
+
bool? showAvatar,
|
|
144
|
+
Widget? avatar,
|
|
145
|
+
KasyAvatarGradientData? avatarGradient,
|
|
146
|
+
VoidCallback? onAvatarTap,
|
|
147
|
+
}) {
|
|
148
|
+
return KasyAppBarConfig(
|
|
149
|
+
showSearch: showSearch ?? this.showSearch,
|
|
150
|
+
searchController: searchController ?? this.searchController,
|
|
151
|
+
searchHint: searchHint ?? this.searchHint,
|
|
152
|
+
onSearchChanged: onSearchChanged ?? this.onSearchChanged,
|
|
153
|
+
onSearchSubmitted: onSearchSubmitted ?? this.onSearchSubmitted,
|
|
154
|
+
showThemeToggle: showThemeToggle ?? this.showThemeToggle,
|
|
155
|
+
onToggleTheme: onToggleTheme ?? this.onToggleTheme,
|
|
156
|
+
showNotifications: showNotifications ?? this.showNotifications,
|
|
157
|
+
notifications: notifications ?? this.notifications,
|
|
158
|
+
onNotifications: onNotifications ?? this.onNotifications,
|
|
159
|
+
showNotificationBadge: showNotificationBadge ?? this.showNotificationBadge,
|
|
160
|
+
showCreate: showCreate ?? this.showCreate,
|
|
161
|
+
createLabel: createLabel ?? this.createLabel,
|
|
162
|
+
onCreate: onCreate ?? this.onCreate,
|
|
163
|
+
showAvatar: showAvatar ?? this.showAvatar,
|
|
164
|
+
avatar: avatar ?? this.avatar,
|
|
165
|
+
avatarGradient: avatarGradient ?? this.avatarGradient,
|
|
166
|
+
onAvatarTap: onAvatarTap ?? this.onAvatarTap,
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
@override
|
|
171
|
+
bool operator ==(Object other) {
|
|
172
|
+
return other is KasyAppBarConfig &&
|
|
173
|
+
other.showSearch == showSearch &&
|
|
174
|
+
other.searchController == searchController &&
|
|
175
|
+
other.searchHint == searchHint &&
|
|
176
|
+
other.onSearchChanged == onSearchChanged &&
|
|
177
|
+
other.onSearchSubmitted == onSearchSubmitted &&
|
|
178
|
+
other.showThemeToggle == showThemeToggle &&
|
|
179
|
+
other.onToggleTheme == onToggleTheme &&
|
|
180
|
+
other.showNotifications == showNotifications &&
|
|
181
|
+
other.notifications == notifications &&
|
|
182
|
+
other.onNotifications == onNotifications &&
|
|
183
|
+
other.showNotificationBadge == showNotificationBadge &&
|
|
184
|
+
other.showCreate == showCreate &&
|
|
185
|
+
other.createLabel == createLabel &&
|
|
186
|
+
other.onCreate == onCreate &&
|
|
187
|
+
other.showAvatar == showAvatar &&
|
|
188
|
+
other.avatar == avatar &&
|
|
189
|
+
other.avatarGradient == avatarGradient &&
|
|
190
|
+
other.onAvatarTap == onAvatarTap;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
@override
|
|
194
|
+
int get hashCode => Object.hashAll(<Object?>[
|
|
195
|
+
showSearch,
|
|
196
|
+
searchController,
|
|
197
|
+
searchHint,
|
|
198
|
+
onSearchChanged,
|
|
199
|
+
onSearchSubmitted,
|
|
200
|
+
showThemeToggle,
|
|
201
|
+
onToggleTheme,
|
|
202
|
+
showNotifications,
|
|
203
|
+
notifications,
|
|
204
|
+
onNotifications,
|
|
205
|
+
showNotificationBadge,
|
|
206
|
+
showCreate,
|
|
207
|
+
createLabel,
|
|
208
|
+
onCreate,
|
|
209
|
+
showAvatar,
|
|
210
|
+
avatar,
|
|
211
|
+
avatarGradient,
|
|
212
|
+
onAvatarTap,
|
|
213
|
+
]);
|
|
214
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import 'package:flutter/widgets.dart';
|
|
2
|
+
import 'package:kasy_kit/core/chrome/app_bar_config.dart';
|
|
3
|
+
|
|
4
|
+
/// Transforms the shell's default application-bar config into a screen's version.
|
|
5
|
+
/// Receives the shell default (with its wired bell, theme toggle, etc.) so a
|
|
6
|
+
/// screen only toggles/overrides what it needs and inherits the rest.
|
|
7
|
+
typedef KasyAppBarConfigure = KasyAppBarConfig Function(KasyAppBarConfig base);
|
|
8
|
+
|
|
9
|
+
/// Marks the subtree that sits BELOW the desktop application bar
|
|
10
|
+
/// ([KasyAppBar.application]) — i.e. the shell content area, provided by
|
|
11
|
+
/// [WebContentWrapper] — and carries the config the bar renders from.
|
|
12
|
+
///
|
|
13
|
+
/// Two jobs:
|
|
14
|
+
/// 1. Presence ([of]) lets the page-level [KasyAppBar] hide on desktop INSIDE the
|
|
15
|
+
/// shell (where the application bar owns the chrome) but stay visible OUTSIDE
|
|
16
|
+
/// it (a full-screen pushed route), so the back button is never lost.
|
|
17
|
+
/// 2. [config] is the live config the bar listens to; [defaultConfig] is the
|
|
18
|
+
/// shell's app-wide default. A [KasyAppBarConfigurator] in the routed page
|
|
19
|
+
/// publishes `configure(defaultConfig)` into [config] to override the bar for
|
|
20
|
+
/// that screen.
|
|
21
|
+
class KasyAppBarScope extends InheritedWidget {
|
|
22
|
+
/// Live config the application bar renders from. The bar listens; screens write.
|
|
23
|
+
final ValueNotifier<KasyAppBarConfig> config;
|
|
24
|
+
|
|
25
|
+
/// The shell's app-wide default — the starting point screen overrides build on.
|
|
26
|
+
final KasyAppBarConfig defaultConfig;
|
|
27
|
+
|
|
28
|
+
const KasyAppBarScope({
|
|
29
|
+
super.key,
|
|
30
|
+
required this.config,
|
|
31
|
+
required this.defaultConfig,
|
|
32
|
+
required super.child,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
/// True when a [KasyAppBarScope] is an ancestor (no rebuild dependency —
|
|
36
|
+
/// presence is fixed for a given subtree).
|
|
37
|
+
static bool of(BuildContext context) => maybeOf(context) != null;
|
|
38
|
+
|
|
39
|
+
/// The nearest scope, or null outside the shell (e.g. full-screen routes).
|
|
40
|
+
static KasyAppBarScope? maybeOf(BuildContext context) =>
|
|
41
|
+
context.getInheritedWidgetOfExactType<KasyAppBarScope>();
|
|
42
|
+
|
|
43
|
+
@override
|
|
44
|
+
bool updateShouldNotify(KasyAppBarScope oldWidget) =>
|
|
45
|
+
config != oldWidget.config || defaultConfig != oldWidget.defaultConfig;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/// Wrap a routed page's body with this to override the desktop application bar
|
|
49
|
+
/// for that screen. On phone/tablet (no scope above) it is a transparent
|
|
50
|
+
/// pass-through, so it is always safe to add.
|
|
51
|
+
///
|
|
52
|
+
/// ```dart
|
|
53
|
+
/// KasyAppBarConfigurator(
|
|
54
|
+
/// configure: (bar) => bar.only(search: true),
|
|
55
|
+
/// child: MyScreen(),
|
|
56
|
+
/// )
|
|
57
|
+
/// ```
|
|
58
|
+
class KasyAppBarConfigurator extends StatefulWidget {
|
|
59
|
+
/// How this screen adapts the shell default. Return [base] unchanged to keep
|
|
60
|
+
/// the default, or e.g. `base.only(...)` / `base.copyWith(...)`.
|
|
61
|
+
final KasyAppBarConfigure configure;
|
|
62
|
+
final Widget child;
|
|
63
|
+
|
|
64
|
+
const KasyAppBarConfigurator({
|
|
65
|
+
super.key,
|
|
66
|
+
required this.configure,
|
|
67
|
+
required this.child,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
@override
|
|
71
|
+
State<KasyAppBarConfigurator> createState() => _KasyAppBarConfiguratorState();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
class _KasyAppBarConfiguratorState extends State<KasyAppBarConfigurator> {
|
|
75
|
+
void _publish() {
|
|
76
|
+
if (!mounted) return;
|
|
77
|
+
final KasyAppBarScope? scope = KasyAppBarScope.maybeOf(context);
|
|
78
|
+
if (scope == null) return; // phone/tablet: no application bar to configure.
|
|
79
|
+
// Always build from the ORIGINAL default (not the current value), so the
|
|
80
|
+
// override is stable across rebuilds. The setter is a no-op when the result
|
|
81
|
+
// is unchanged (==), so an inline-built config does not churn the bar.
|
|
82
|
+
//
|
|
83
|
+
// Published post-frame: the bar (a sibling above this page in the shell
|
|
84
|
+
// Column) has already built this frame, so it picks the override up next.
|
|
85
|
+
scope.config.value = widget.configure(scope.defaultConfig);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@override
|
|
89
|
+
void didChangeDependencies() {
|
|
90
|
+
super.didChangeDependencies();
|
|
91
|
+
WidgetsBinding.instance.addPostFrameCallback((_) => _publish());
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
@override
|
|
95
|
+
void didUpdateWidget(covariant KasyAppBarConfigurator oldWidget) {
|
|
96
|
+
super.didUpdateWidget(oldWidget);
|
|
97
|
+
WidgetsBinding.instance.addPostFrameCallback((_) => _publish());
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@override
|
|
101
|
+
Widget build(BuildContext context) => widget.child;
|
|
102
|
+
}
|
|
@@ -53,6 +53,10 @@ class UserApi {
|
|
|
53
53
|
);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
Stream<String?> watchRole(String id) {
|
|
57
|
+
return _collection.doc(id).snapshots().map((snap) => snap.data()?.role);
|
|
58
|
+
}
|
|
59
|
+
|
|
56
60
|
Future<void> update(UserEntity user) {
|
|
57
61
|
final data = user.toJson();
|
|
58
62
|
data.removeWhere((key, value) => value == null);
|
|
@@ -74,6 +78,17 @@ class UserApi {
|
|
|
74
78
|
'authFunctions-deleteUserAccount',
|
|
75
79
|
);
|
|
76
80
|
await callable.call();
|
|
81
|
+
} on FirebaseFunctionsException catch (err, stacktrace) {
|
|
82
|
+
// Surface the REAL cause in the logs instead of a generic message, so a
|
|
83
|
+
// failed deletion is diagnosable at a glance:
|
|
84
|
+
// code 'not-found' → the function isn't deployed yet (run a deploy)
|
|
85
|
+
// code 'unauthenticated'→ the user's token didn't reach the function
|
|
86
|
+
// code 'internal' → the function ran but threw (see its own logs)
|
|
87
|
+
_logger.e(
|
|
88
|
+
"delete user error [${err.code}] ${err.message}",
|
|
89
|
+
stackTrace: stacktrace,
|
|
90
|
+
);
|
|
91
|
+
throw ApiError(message: err.message ?? "Error while deleting user");
|
|
77
92
|
} catch (err, stacktrace) {
|
|
78
93
|
_logger.e("delete user error $err", stackTrace: stacktrace);
|
|
79
94
|
throw ApiError(message: "Error while deleting user");
|
|
@@ -59,6 +59,11 @@ class UserRepository {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
/// Streams the user's role so a change made in the backend console (e.g.
|
|
63
|
+
/// promoting someone to "admin") is picked up at runtime without a restart.
|
|
64
|
+
/// Delegates to the backend-specific [UserApi].
|
|
65
|
+
Stream<String?> watchRole(String id) => _userApi.watchRole(id);
|
|
66
|
+
|
|
62
67
|
/// We updates the user avatar
|
|
63
68
|
/// We convert the image to jpeg and resize it to 300px width
|
|
64
69
|
/// and 80% quality to reduce the size of the image
|