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,210 +0,0 @@
|
|
|
1
|
-
/// Application top header for the **web desktop breakpoint** (viewport ≥ 1024px).
|
|
2
|
-
///
|
|
3
|
-
/// This is the *application* chrome (global search, quick-create, notifications,
|
|
4
|
-
/// profile) — distinct from [KasyAppBar], which is *page* chrome (title / back /
|
|
5
|
-
/// theme) used on phone and tablet. On desktop the sidebar handles navigation,
|
|
6
|
-
/// so this header carries global actions instead of a title or back button.
|
|
7
|
-
///
|
|
8
|
-
/// Composed entirely from existing kit widgets: a [KasyTextField] search box
|
|
9
|
-
/// (fixed 220px), a ghost [KasyButton.iconOnly] for notifications, a neutral
|
|
10
|
-
/// pill [KasyButton] for create, and a gradient [KasyAvatar].
|
|
11
|
-
///
|
|
12
|
-
/// Barrel: [components.dart].
|
|
13
|
-
library;
|
|
14
|
-
|
|
15
|
-
import 'package:flutter/material.dart';
|
|
16
|
-
import 'package:kasy_kit/components/kasy_avatar.dart';
|
|
17
|
-
import 'package:kasy_kit/components/kasy_avatar_presets.dart';
|
|
18
|
-
import 'package:kasy_kit/components/kasy_button.dart';
|
|
19
|
-
import 'package:kasy_kit/components/kasy_text_field.dart';
|
|
20
|
-
import 'package:kasy_kit/core/theme/theme.dart';
|
|
21
|
-
|
|
22
|
-
/// Total header height (matches the design: 36px content band + 16px top/bottom).
|
|
23
|
-
const double kasyWebHeaderHeight = 68;
|
|
24
|
-
|
|
25
|
-
/// Fixed width of the leading search field (matches the design).
|
|
26
|
-
const double kasyWebHeaderSearchWidth = 220;
|
|
27
|
-
|
|
28
|
-
/// Desktop application header. Place it at the top of the content area (to the
|
|
29
|
-
/// right of the sidebar) on viewports ≥ 1024px.
|
|
30
|
-
class KasyWebHeader extends StatelessWidget {
|
|
31
|
-
/// Controller for the search field. Optional — omit for a display-only header.
|
|
32
|
-
final TextEditingController? searchController;
|
|
33
|
-
|
|
34
|
-
/// Placeholder shown in the search field.
|
|
35
|
-
final String searchHint;
|
|
36
|
-
|
|
37
|
-
/// Called as the user types in the search field.
|
|
38
|
-
final ValueChanged<String>? onSearchChanged;
|
|
39
|
-
|
|
40
|
-
/// Called when the search field is submitted (Enter).
|
|
41
|
-
final ValueChanged<String>? onSearchSubmitted;
|
|
42
|
-
|
|
43
|
-
/// Notifications (bell) action. When null the bell is disabled.
|
|
44
|
-
final VoidCallback? onNotifications;
|
|
45
|
-
|
|
46
|
-
/// Shows the unread dot on the notifications bell.
|
|
47
|
-
final bool showNotificationBadge;
|
|
48
|
-
|
|
49
|
-
/// Primary quick-create action. When null the button is disabled.
|
|
50
|
-
final VoidCallback? onCreate;
|
|
51
|
-
|
|
52
|
-
/// Label for the create button.
|
|
53
|
-
final String createLabel;
|
|
54
|
-
|
|
55
|
-
/// Gradient used for the profile avatar fallback (when [avatar] is null).
|
|
56
|
-
final KasyAvatarGradientData avatarGradient;
|
|
57
|
-
|
|
58
|
-
/// Custom avatar widget — pass the signed-in user's avatar (e.g.
|
|
59
|
-
/// `KasyUserAvatar`) to show their real photo. When null (and [showAvatar] is
|
|
60
|
-
/// true), a gradient-fill avatar is shown instead.
|
|
61
|
-
final Widget? avatar;
|
|
62
|
-
|
|
63
|
-
/// Whether the profile avatar is shown at all. Set false for a header that
|
|
64
|
-
/// carries no account chip (e.g. when the sidebar already owns the profile).
|
|
65
|
-
final bool showAvatar;
|
|
66
|
-
|
|
67
|
-
/// Profile avatar tap (open menu / profile). When null the avatar is inert.
|
|
68
|
-
final VoidCallback? onAvatarTap;
|
|
69
|
-
|
|
70
|
-
/// Theme toggle. When set, a sun/moon ghost button is shown before the bell
|
|
71
|
-
/// (on desktop the web header replaces the app bar's theme toggle).
|
|
72
|
-
final VoidCallback? onToggleTheme;
|
|
73
|
-
|
|
74
|
-
const KasyWebHeader({
|
|
75
|
-
super.key,
|
|
76
|
-
this.searchController,
|
|
77
|
-
this.searchHint = 'Search...',
|
|
78
|
-
this.onSearchChanged,
|
|
79
|
-
this.onSearchSubmitted,
|
|
80
|
-
this.onNotifications,
|
|
81
|
-
this.showNotificationBadge = false,
|
|
82
|
-
this.onCreate,
|
|
83
|
-
this.createLabel = 'Create',
|
|
84
|
-
this.avatarGradient = KasyAvatarGradients.orange,
|
|
85
|
-
this.avatar,
|
|
86
|
-
this.showAvatar = true,
|
|
87
|
-
this.onAvatarTap,
|
|
88
|
-
this.onToggleTheme,
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
@override
|
|
92
|
-
Widget build(BuildContext context) {
|
|
93
|
-
final KasyColors c = context.colors;
|
|
94
|
-
return DecoratedBox(
|
|
95
|
-
decoration: BoxDecoration(
|
|
96
|
-
// Matches KasySidebar exactly: surface fill + the same `border` hairline
|
|
97
|
-
// used by the sidebar's vertical edge line, at the same 0.5px width — so
|
|
98
|
-
// the header's bottom border and the sidebar's top divider read as one
|
|
99
|
-
// continuous line across the whole chrome in light and dark.
|
|
100
|
-
color: c.surface,
|
|
101
|
-
border: Border(
|
|
102
|
-
bottom: BorderSide(color: c.border, width: 0.5),
|
|
103
|
-
),
|
|
104
|
-
),
|
|
105
|
-
// minHeight (not a fixed height) keeps the band at 68 while letting the
|
|
106
|
-
// search field size naturally, so it never overflows the toolbar row.
|
|
107
|
-
child: ConstrainedBox(
|
|
108
|
-
constraints: const BoxConstraints(minHeight: kasyWebHeaderHeight),
|
|
109
|
-
child: Padding(
|
|
110
|
-
// Design gutters: 20px horizontal; vertical breathes around the field.
|
|
111
|
-
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
|
112
|
-
child: Row(
|
|
113
|
-
children: [
|
|
114
|
-
SizedBox(
|
|
115
|
-
width: kasyWebHeaderSearchWidth,
|
|
116
|
-
child: _buildSearch(context),
|
|
117
|
-
),
|
|
118
|
-
const Spacer(),
|
|
119
|
-
if (onToggleTheme != null) ...[
|
|
120
|
-
_buildThemeToggle(context),
|
|
121
|
-
const SizedBox(width: KasySpacing.md),
|
|
122
|
-
],
|
|
123
|
-
_buildNotifications(context),
|
|
124
|
-
const SizedBox(width: KasySpacing.md),
|
|
125
|
-
KasyButton(
|
|
126
|
-
label: createLabel,
|
|
127
|
-
variant: KasyButtonVariant.neutral,
|
|
128
|
-
size: KasyButtonSize.small,
|
|
129
|
-
onPressed: onCreate,
|
|
130
|
-
),
|
|
131
|
-
if (showAvatar) ...[
|
|
132
|
-
const SizedBox(width: KasySpacing.md),
|
|
133
|
-
avatar ??
|
|
134
|
-
KasyAvatar.gradientFill(
|
|
135
|
-
size: KasyAvatarSize.small,
|
|
136
|
-
diameter: 36,
|
|
137
|
-
gradient: avatarGradient,
|
|
138
|
-
showShadow: false,
|
|
139
|
-
onTap: onAvatarTap,
|
|
140
|
-
),
|
|
141
|
-
],
|
|
142
|
-
],
|
|
143
|
-
),
|
|
144
|
-
),
|
|
145
|
-
),
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
Widget _buildSearch(BuildContext context) {
|
|
150
|
-
return KasyTextField(
|
|
151
|
-
variant: KasyTextFieldVariant.flat,
|
|
152
|
-
controller: searchController,
|
|
153
|
-
hint: searchHint,
|
|
154
|
-
onChanged: onSearchChanged,
|
|
155
|
-
onSubmitted: onSearchSubmitted,
|
|
156
|
-
prefix: Icon(
|
|
157
|
-
KasyIcons.search,
|
|
158
|
-
size: KasyIconSize.md,
|
|
159
|
-
color: context.colors.muted,
|
|
160
|
-
),
|
|
161
|
-
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
Widget _buildThemeToggle(BuildContext context) {
|
|
166
|
-
final bool isDark = Theme.of(context).brightness == Brightness.dark;
|
|
167
|
-
return KasyButton.iconOnly(
|
|
168
|
-
icon: isDark ? KasyIcons.lightMode : KasyIcons.darkMode,
|
|
169
|
-
variant: KasyButtonVariant.ghost,
|
|
170
|
-
size: KasyButtonSize.small,
|
|
171
|
-
iconOnlyLayoutExtent: 36,
|
|
172
|
-
iconGlyphSize: KasyIconSize.md,
|
|
173
|
-
onPressed: onToggleTheme,
|
|
174
|
-
semanticLabel: isDark ? 'Light mode' : 'Dark mode',
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
Widget _buildNotifications(BuildContext context) {
|
|
179
|
-
final KasyColors c = context.colors;
|
|
180
|
-
final Widget bell = KasyButton.iconOnly(
|
|
181
|
-
icon: KasyIcons.notification,
|
|
182
|
-
variant: KasyButtonVariant.ghost,
|
|
183
|
-
size: KasyButtonSize.small,
|
|
184
|
-
iconOnlyLayoutExtent: 36,
|
|
185
|
-
iconGlyphSize: KasyIconSize.md,
|
|
186
|
-
onPressed: onNotifications,
|
|
187
|
-
semanticLabel: 'Notifications',
|
|
188
|
-
);
|
|
189
|
-
if (!showNotificationBadge) return bell;
|
|
190
|
-
return Stack(
|
|
191
|
-
clipBehavior: Clip.none,
|
|
192
|
-
children: [
|
|
193
|
-
bell,
|
|
194
|
-
Positioned(
|
|
195
|
-
top: 8,
|
|
196
|
-
right: 8,
|
|
197
|
-
child: Container(
|
|
198
|
-
width: 8,
|
|
199
|
-
height: 8,
|
|
200
|
-
decoration: BoxDecoration(
|
|
201
|
-
color: c.error,
|
|
202
|
-
shape: BoxShape.circle,
|
|
203
|
-
border: Border.all(color: c.background, width: 1.5),
|
|
204
|
-
),
|
|
205
|
-
),
|
|
206
|
-
),
|
|
207
|
-
],
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import 'package:flutter/material.dart';
|
|
2
|
-
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
3
|
-
import 'package:kasy_kit/features/authentication/providers/models/signin_state.dart';
|
|
4
|
-
import 'package:kasy_kit/features/authentication/providers/signin_state_provider.dart';
|
|
5
|
-
import 'package:kasy_kit/features/authentication/ui/widgets/round_signin.dart';
|
|
6
|
-
|
|
7
|
-
class AppleSigninComponent extends ConsumerWidget {
|
|
8
|
-
const AppleSigninComponent({super.key});
|
|
9
|
-
|
|
10
|
-
@override
|
|
11
|
-
Widget build(BuildContext context, WidgetRef ref) {
|
|
12
|
-
// watch keeps the provider alive during async sign-in
|
|
13
|
-
final state = ref.watch(signinStateProvider);
|
|
14
|
-
final isSending = state is SigninStateSending;
|
|
15
|
-
return SocialSigninButton.apple(
|
|
16
|
-
() { if (!isSending) ref.read(signinStateProvider.notifier).signinWithApple(); },
|
|
17
|
-
);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import 'package:flutter/material.dart';
|
|
2
|
-
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
3
|
-
import 'package:kasy_kit/features/authentication/providers/models/signin_state.dart';
|
|
4
|
-
import 'package:kasy_kit/features/authentication/providers/signin_state_provider.dart';
|
|
5
|
-
import 'package:kasy_kit/features/authentication/ui/widgets/round_signin.dart';
|
|
6
|
-
|
|
7
|
-
class GoogleSignInComponent extends ConsumerWidget {
|
|
8
|
-
const GoogleSignInComponent({super.key});
|
|
9
|
-
|
|
10
|
-
@override
|
|
11
|
-
Widget build(BuildContext context, WidgetRef ref) {
|
|
12
|
-
// watch keeps the provider alive during async sign-in
|
|
13
|
-
final state = ref.watch(signinStateProvider);
|
|
14
|
-
final isSending = state is SigninStateSending;
|
|
15
|
-
return SocialSigninButton.google(
|
|
16
|
-
() { if (!isSending) ref.read(signinStateProvider.notifier).signinWithGoogle(); },
|
|
17
|
-
);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
class GooglePlayGamesSignInComponent extends ConsumerWidget {
|
|
22
|
-
const GooglePlayGamesSignInComponent({super.key});
|
|
23
|
-
|
|
24
|
-
@override
|
|
25
|
-
Widget build(BuildContext context, WidgetRef ref) {
|
|
26
|
-
final state = ref.watch(signinStateProvider);
|
|
27
|
-
final isSending = state is SigninStateSending;
|
|
28
|
-
return SocialSigninButton.googlePlayGames(
|
|
29
|
-
() { if (!isSending) ref.read(signinStateProvider.notifier).signinWithGooglePlayGames(); },
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import 'package:flutter/material.dart';
|
|
2
|
-
import 'package:kasy_kit/core/haptics/kasy_haptics.dart';
|
|
3
|
-
import 'package:kasy_kit/core/theme/theme.dart';
|
|
4
|
-
|
|
5
|
-
typedef SocialSigninCallback = void Function();
|
|
6
|
-
|
|
7
|
-
class SocialSigninButton extends StatelessWidget {
|
|
8
|
-
final Image iconImage;
|
|
9
|
-
final SocialSigninCallback? onPressed;
|
|
10
|
-
|
|
11
|
-
const SocialSigninButton({
|
|
12
|
-
super.key,
|
|
13
|
-
required this.iconImage,
|
|
14
|
-
required this.onPressed,
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
factory SocialSigninButton.google(SocialSigninCallback onPressed) {
|
|
18
|
-
return SocialSigninButton(
|
|
19
|
-
iconImage: Image.asset("assets/icons/google.png", width: 24),
|
|
20
|
-
onPressed: onPressed,
|
|
21
|
-
);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
factory SocialSigninButton.googlePlayGames(SocialSigninCallback onPressed) {
|
|
25
|
-
return SocialSigninButton(
|
|
26
|
-
iconImage: Image.asset("assets/icons/google_play_games.png", width: 24),
|
|
27
|
-
onPressed: onPressed,
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
factory SocialSigninButton.facebook(SocialSigninCallback onPressed) {
|
|
32
|
-
return SocialSigninButton(
|
|
33
|
-
iconImage: Image.asset("assets/icons/facebook.png", width: 24),
|
|
34
|
-
onPressed: onPressed,
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
factory SocialSigninButton.apple(SocialSigninCallback onPressed) {
|
|
39
|
-
return SocialSigninButton(
|
|
40
|
-
iconImage: Image.asset("assets/icons/apple.png", width: 24),
|
|
41
|
-
onPressed: onPressed,
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
@override
|
|
46
|
-
Widget build(BuildContext context) {
|
|
47
|
-
return Container(
|
|
48
|
-
width: 56,
|
|
49
|
-
decoration: BoxDecoration(
|
|
50
|
-
border: Border.all(color: context.colors.outline),
|
|
51
|
-
shape: BoxShape.circle,
|
|
52
|
-
),
|
|
53
|
-
child: RawMaterialButton(
|
|
54
|
-
elevation: 0,
|
|
55
|
-
focusElevation: 0,
|
|
56
|
-
hoverElevation: 0,
|
|
57
|
-
highlightElevation: 0,
|
|
58
|
-
splashColor: Colors.transparent,
|
|
59
|
-
highlightColor: Colors.transparent,
|
|
60
|
-
hoverColor: Colors.transparent,
|
|
61
|
-
focusColor: Colors.transparent,
|
|
62
|
-
onPressed: () {
|
|
63
|
-
KasyHaptics.medium(context);
|
|
64
|
-
onPressed?.call();
|
|
65
|
-
},
|
|
66
|
-
shape: const CircleBorder(),
|
|
67
|
-
fillColor: Colors.transparent,
|
|
68
|
-
padding: const EdgeInsets.all(KasySpacing.md),
|
|
69
|
-
child: iconImage,
|
|
70
|
-
),
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import 'package:flutter/material.dart';
|
|
2
|
-
import 'package:kasy_kit/core/theme/theme.dart';
|
|
3
|
-
import 'package:kasy_kit/core/widgets/kasy_hover.dart';
|
|
4
|
-
|
|
5
|
-
class AddFeatureButton extends StatelessWidget {
|
|
6
|
-
final String title;
|
|
7
|
-
final String description;
|
|
8
|
-
final VoidCallback onPressed;
|
|
9
|
-
|
|
10
|
-
const AddFeatureButton({
|
|
11
|
-
super.key,
|
|
12
|
-
required this.title,
|
|
13
|
-
required this.description,
|
|
14
|
-
required this.onPressed,
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
@override
|
|
18
|
-
Widget build(BuildContext context) {
|
|
19
|
-
return KasyHover(
|
|
20
|
-
onTap: onPressed,
|
|
21
|
-
borderRadius: KasyRadius.mdBorderRadius,
|
|
22
|
-
pressColor: context.colors.onPrimary,
|
|
23
|
-
focusable: true,
|
|
24
|
-
child: Container(
|
|
25
|
-
padding: const EdgeInsets.symmetric(
|
|
26
|
-
horizontal: KasySpacing.lg,
|
|
27
|
-
vertical: KasySpacing.smd,
|
|
28
|
-
),
|
|
29
|
-
decoration: BoxDecoration(
|
|
30
|
-
color: context.colors.primary,
|
|
31
|
-
borderRadius: KasyRadius.mdBorderRadius,
|
|
32
|
-
),
|
|
33
|
-
child: Row(
|
|
34
|
-
children: [
|
|
35
|
-
Expanded(
|
|
36
|
-
child: Column(
|
|
37
|
-
crossAxisAlignment: CrossAxisAlignment.start,
|
|
38
|
-
spacing: KasySpacing.xs,
|
|
39
|
-
children: [
|
|
40
|
-
Text(
|
|
41
|
-
title,
|
|
42
|
-
style: context.textTheme.titleMedium?.copyWith(
|
|
43
|
-
color: context.colors.onPrimary,
|
|
44
|
-
),
|
|
45
|
-
),
|
|
46
|
-
Text(
|
|
47
|
-
description,
|
|
48
|
-
style: context.textTheme.bodySmall?.copyWith(
|
|
49
|
-
color: context.colors.onPrimary.withValues(alpha: 0.72),
|
|
50
|
-
),
|
|
51
|
-
),
|
|
52
|
-
],
|
|
53
|
-
),
|
|
54
|
-
),
|
|
55
|
-
const SizedBox(width: KasySpacing.smd),
|
|
56
|
-
Icon(
|
|
57
|
-
KasyIcons.northEast,
|
|
58
|
-
size: KasyIconSize.rowTrailing,
|
|
59
|
-
color: context.colors.onPrimary.withValues(alpha: 0.55),
|
|
60
|
-
),
|
|
61
|
-
],
|
|
62
|
-
),
|
|
63
|
-
),
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
}
|
package/templates/firebase/lib/features/notifications/ui/components/notification_settings_sheet.dart
DELETED
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
import 'package:flutter/material.dart';
|
|
2
|
-
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
3
|
-
import 'package:kasy_kit/core/theme/theme.dart';
|
|
4
|
-
import 'package:kasy_kit/features/notifications/providers/models/notification.dart';
|
|
5
|
-
import 'package:kasy_kit/features/notifications/providers/notifications_provider.dart';
|
|
6
|
-
import 'package:kasy_kit/features/notifications/repositories/notifications_repository.dart';
|
|
7
|
-
import 'package:kasy_kit/features/settings/ui/widgets/settings_tile.dart';
|
|
8
|
-
import 'package:kasy_kit/i18n/translations.g.dart';
|
|
9
|
-
|
|
10
|
-
void showNotificationSettingsSheet(BuildContext context) {
|
|
11
|
-
showModalBottomSheet<void>(
|
|
12
|
-
context: context,
|
|
13
|
-
useRootNavigator: true,
|
|
14
|
-
backgroundColor: Colors.transparent,
|
|
15
|
-
isScrollControlled: true,
|
|
16
|
-
builder: (_) => const _NotificationSettingsSheet(),
|
|
17
|
-
);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
class _NotificationSettingsSheet extends ConsumerStatefulWidget {
|
|
21
|
-
const _NotificationSettingsSheet();
|
|
22
|
-
|
|
23
|
-
@override
|
|
24
|
-
ConsumerState<_NotificationSettingsSheet> createState() =>
|
|
25
|
-
_NotificationSettingsSheetState();
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
class _NotificationSettingsSheetState
|
|
29
|
-
extends ConsumerState<_NotificationSettingsSheet> {
|
|
30
|
-
NotificationPermission? _permission;
|
|
31
|
-
bool _toggling = false;
|
|
32
|
-
|
|
33
|
-
@override
|
|
34
|
-
void initState() {
|
|
35
|
-
super.initState();
|
|
36
|
-
_loadPermission();
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
Future<void> _loadPermission() async {
|
|
40
|
-
final p =
|
|
41
|
-
await ref.read(notificationRepositoryProvider).getPermissionStatus();
|
|
42
|
-
if (mounted) setState(() => _permission = p);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
bool get _isEnabled => _permission is NotificationPermissionGranted;
|
|
46
|
-
|
|
47
|
-
String _subtitle(BuildContext context) {
|
|
48
|
-
final tr = context.t.notifications;
|
|
49
|
-
return switch (_permission) {
|
|
50
|
-
NotificationPermissionGranted() => tr.push_subtitle_enabled,
|
|
51
|
-
NotificationPermissionDenied() => tr.push_subtitle_disabled,
|
|
52
|
-
_ => tr.push_subtitle_waiting,
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
Future<void> _onToggle(bool value) async {
|
|
57
|
-
if (_toggling || _permission == null) return;
|
|
58
|
-
setState(() => _toggling = true);
|
|
59
|
-
await _permission!.maybeAsk();
|
|
60
|
-
await _loadPermission();
|
|
61
|
-
if (mounted) setState(() => _toggling = false);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
Future<void> _markAllRead() async {
|
|
65
|
-
await ref.read(notificationsProvider.notifier).readAll();
|
|
66
|
-
if (mounted) Navigator.of(context).pop();
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
@override
|
|
70
|
-
Widget build(BuildContext context) {
|
|
71
|
-
final tr = context.t.notifications;
|
|
72
|
-
final notificationsState = ref.watch(notificationsProvider);
|
|
73
|
-
final hasUnread =
|
|
74
|
-
notificationsState.value?.data.any((n) => !n.seen) ?? false;
|
|
75
|
-
|
|
76
|
-
return Container(
|
|
77
|
-
margin: EdgeInsets.only(
|
|
78
|
-
left: KasySpacing.sm,
|
|
79
|
-
right: KasySpacing.sm,
|
|
80
|
-
bottom: MediaQuery.of(context).viewInsets.bottom +
|
|
81
|
-
MediaQuery.of(context).padding.bottom +
|
|
82
|
-
KasySpacing.sm,
|
|
83
|
-
),
|
|
84
|
-
decoration: BoxDecoration(
|
|
85
|
-
color: context.colors.background,
|
|
86
|
-
borderRadius: KasyRadius.lgBorderRadius,
|
|
87
|
-
border: Border.all(
|
|
88
|
-
color: context.colors.outline.withValues(alpha: 0.5),
|
|
89
|
-
),
|
|
90
|
-
),
|
|
91
|
-
child: Column(
|
|
92
|
-
mainAxisSize: MainAxisSize.min,
|
|
93
|
-
children: [
|
|
94
|
-
// drag handle
|
|
95
|
-
Padding(
|
|
96
|
-
padding: const EdgeInsets.only(top: KasySpacing.smd),
|
|
97
|
-
child: Container(
|
|
98
|
-
width: 36,
|
|
99
|
-
height: 4,
|
|
100
|
-
decoration: BoxDecoration(
|
|
101
|
-
color: context.colors.onSurface.withValues(alpha: 0.14),
|
|
102
|
-
borderRadius: KasyRadius.fullBorderRadius,
|
|
103
|
-
),
|
|
104
|
-
),
|
|
105
|
-
),
|
|
106
|
-
Padding(
|
|
107
|
-
padding: const EdgeInsets.fromLTRB(
|
|
108
|
-
KasySpacing.md,
|
|
109
|
-
KasySpacing.md,
|
|
110
|
-
KasySpacing.md,
|
|
111
|
-
KasySpacing.smd,
|
|
112
|
-
),
|
|
113
|
-
child: Column(
|
|
114
|
-
crossAxisAlignment: CrossAxisAlignment.start,
|
|
115
|
-
children: [
|
|
116
|
-
// Push toggle row
|
|
117
|
-
SettingsSwitchTile(
|
|
118
|
-
icon: _isEnabled
|
|
119
|
-
? KasyIcons.notificationActive
|
|
120
|
-
: KasyIcons.notification,
|
|
121
|
-
title: tr.push_title,
|
|
122
|
-
value: _isEnabled,
|
|
123
|
-
iconBackgroundColor: _isEnabled
|
|
124
|
-
? const Color(0xFFFF6B35) // design-check: ignore — category accent
|
|
125
|
-
: const Color(0xFF78909C), // design-check: ignore — category accent
|
|
126
|
-
onChanged: _toggling ? (_) {} : _onToggle,
|
|
127
|
-
),
|
|
128
|
-
// subtitle below
|
|
129
|
-
Padding(
|
|
130
|
-
padding: const EdgeInsets.only(
|
|
131
|
-
left: 32 + KasySpacing.sm,
|
|
132
|
-
bottom: KasySpacing.xs,
|
|
133
|
-
),
|
|
134
|
-
child: _permission == null
|
|
135
|
-
? SizedBox(
|
|
136
|
-
height: 13,
|
|
137
|
-
width: 140,
|
|
138
|
-
child: LinearProgressIndicator(
|
|
139
|
-
borderRadius: BorderRadius.circular(4),
|
|
140
|
-
color: context.colors.primary.withValues(alpha: 0.25),
|
|
141
|
-
backgroundColor: context.colors.onSurface
|
|
142
|
-
.withValues(alpha: 0.06),
|
|
143
|
-
),
|
|
144
|
-
)
|
|
145
|
-
: Text(
|
|
146
|
-
_subtitle(context),
|
|
147
|
-
style: context.textTheme.bodySmall?.copyWith(
|
|
148
|
-
color: context.colors.muted,
|
|
149
|
-
),
|
|
150
|
-
),
|
|
151
|
-
),
|
|
152
|
-
if (hasUnread) ...[
|
|
153
|
-
const SettingsDivider(),
|
|
154
|
-
const SizedBox(height: KasySpacing.xs),
|
|
155
|
-
SettingsTile(
|
|
156
|
-
icon: KasyIcons.checkCircle,
|
|
157
|
-
title: tr.mark_all_read,
|
|
158
|
-
iconBackgroundColor: context.colors.primary,
|
|
159
|
-
onTap: _markAllRead,
|
|
160
|
-
),
|
|
161
|
-
],
|
|
162
|
-
],
|
|
163
|
-
),
|
|
164
|
-
),
|
|
165
|
-
],
|
|
166
|
-
),
|
|
167
|
-
);
|
|
168
|
-
}
|
|
169
|
-
}
|
package/templates/firebase/lib/features/notifications/ui/components/push_notification_switcher.dart
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import 'package:flutter/material.dart';
|
|
2
|
-
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
3
|
-
import 'package:kasy_kit/core/theme/theme.dart';
|
|
4
|
-
import 'package:kasy_kit/features/notifications/providers/models/notification.dart';
|
|
5
|
-
import 'package:kasy_kit/features/notifications/repositories/notifications_repository.dart';
|
|
6
|
-
import 'package:kasy_kit/features/settings/ui/widgets/settings_tile.dart';
|
|
7
|
-
import 'package:kasy_kit/i18n/translations.g.dart';
|
|
8
|
-
|
|
9
|
-
class PushNotificationSwitcher extends ConsumerStatefulWidget {
|
|
10
|
-
const PushNotificationSwitcher({super.key});
|
|
11
|
-
|
|
12
|
-
@override
|
|
13
|
-
ConsumerState<PushNotificationSwitcher> createState() =>
|
|
14
|
-
_PushNotificationSwitcherState();
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
class _PushNotificationSwitcherState
|
|
18
|
-
extends ConsumerState<PushNotificationSwitcher> {
|
|
19
|
-
NotificationPermission? _permission;
|
|
20
|
-
bool _loading = false;
|
|
21
|
-
|
|
22
|
-
@override
|
|
23
|
-
void initState() {
|
|
24
|
-
super.initState();
|
|
25
|
-
_loadPermission();
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
Future<void> _loadPermission() async {
|
|
29
|
-
final p =
|
|
30
|
-
await ref.read(notificationRepositoryProvider).getPermissionStatus();
|
|
31
|
-
if (mounted) setState(() => _permission = p);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
bool get _isEnabled => _permission is NotificationPermissionGranted;
|
|
35
|
-
|
|
36
|
-
String _subtitle(BuildContext context) {
|
|
37
|
-
final tr = context.t.notifications;
|
|
38
|
-
return switch (_permission) {
|
|
39
|
-
NotificationPermissionGranted() => tr.push_subtitle_enabled,
|
|
40
|
-
NotificationPermissionDenied() => tr.push_subtitle_disabled,
|
|
41
|
-
_ => tr.push_subtitle_waiting,
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
Future<void> _onChanged(bool value) async {
|
|
46
|
-
if (_loading || _permission == null) return;
|
|
47
|
-
setState(() => _loading = true);
|
|
48
|
-
await _permission!.maybeAsk();
|
|
49
|
-
await _loadPermission();
|
|
50
|
-
if (mounted) setState(() => _loading = false);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
@override
|
|
54
|
-
Widget build(BuildContext context) {
|
|
55
|
-
final tr = context.t.notifications;
|
|
56
|
-
return Container(
|
|
57
|
-
padding: const EdgeInsets.symmetric(
|
|
58
|
-
vertical: KasySpacing.smd,
|
|
59
|
-
horizontal: KasySpacing.md,
|
|
60
|
-
),
|
|
61
|
-
decoration: BoxDecoration(
|
|
62
|
-
borderRadius: KasyRadius.smBorderRadius,
|
|
63
|
-
color: context.colors.surface,
|
|
64
|
-
),
|
|
65
|
-
child: Column(
|
|
66
|
-
crossAxisAlignment: CrossAxisAlignment.start,
|
|
67
|
-
children: [
|
|
68
|
-
SettingsSwitchTile(
|
|
69
|
-
icon: _isEnabled
|
|
70
|
-
? KasyIcons.notificationActive
|
|
71
|
-
: KasyIcons.notification,
|
|
72
|
-
title: tr.push_title,
|
|
73
|
-
value: _isEnabled,
|
|
74
|
-
iconBackgroundColor: _isEnabled
|
|
75
|
-
? const Color(0xFFFF6B35) // design-check: ignore — category accent
|
|
76
|
-
: const Color(0xFF78909C), // design-check: ignore — category accent
|
|
77
|
-
onChanged: _loading ? (_) {} : _onChanged,
|
|
78
|
-
),
|
|
79
|
-
Padding(
|
|
80
|
-
padding: const EdgeInsets.only(
|
|
81
|
-
left: 32 + KasySpacing.sm,
|
|
82
|
-
bottom: KasySpacing.xs,
|
|
83
|
-
),
|
|
84
|
-
child: _permission == null
|
|
85
|
-
? SizedBox(
|
|
86
|
-
height: 14,
|
|
87
|
-
width: 120,
|
|
88
|
-
child: LinearProgressIndicator(
|
|
89
|
-
borderRadius: BorderRadius.circular(4),
|
|
90
|
-
color: context.colors.primary.withValues(alpha: 0.3),
|
|
91
|
-
backgroundColor:
|
|
92
|
-
context.colors.onSurface.withValues(alpha: 0.06),
|
|
93
|
-
),
|
|
94
|
-
)
|
|
95
|
-
: Text(
|
|
96
|
-
_subtitle(context),
|
|
97
|
-
style: context.textTheme.bodySmall?.copyWith(
|
|
98
|
-
color: context.colors.muted,
|
|
99
|
-
),
|
|
100
|
-
),
|
|
101
|
-
),
|
|
102
|
-
],
|
|
103
|
-
),
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
}
|