kasy-cli 1.38.0 → 1.39.1

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.
Files changed (105) hide show
  1. package/lib/scaffold/CHANGELOG.json +23 -0
  2. package/lib/scaffold/backends/api/patch/README.md +15 -0
  3. package/lib/scaffold/backends/api/patch/lib/features/notifications/api/device_api.dart +11 -6
  4. package/lib/scaffold/backends/api/patch/lib/features/settings/ui/components/admin/admin_users_api.dart +9 -1
  5. package/lib/scaffold/backends/api/pubspec.yaml.tpl +1 -0
  6. package/lib/scaffold/backends/patch-base-hashes.json +6 -6
  7. package/lib/scaffold/backends/supabase/edge-functions/admin-list-users/index.ts +3 -1
  8. package/lib/scaffold/backends/supabase/edge-functions/send-push-notification/index.ts +3 -0
  9. package/lib/scaffold/backends/supabase/migrations/20240101000012_welcome_decouple_from_push.sql +62 -0
  10. package/lib/scaffold/backends/supabase/patch/lib/features/notifications/api/device_api.dart +11 -6
  11. package/lib/scaffold/backends/supabase/patch/lib/features/settings/ui/components/admin/admin_users_api.dart +7 -0
  12. package/lib/scaffold/backends/supabase/pubspec.yaml.tpl +1 -0
  13. package/lib/scaffold/shared/generator-utils.js +12 -6
  14. package/package.json +1 -1
  15. package/templates/firebase/.firebase/hosting.YnVpbGQvd2Vi.cache +23 -23
  16. package/templates/firebase/AGENTS.md +2 -2
  17. package/templates/firebase/DESIGN_SYSTEM.md +23 -8
  18. package/templates/firebase/assets/icons/apple_black.svg +3 -0
  19. package/templates/firebase/assets/icons/apple_white.svg +4 -0
  20. package/templates/firebase/assets/icons/facebook.svg +49 -0
  21. package/templates/firebase/assets/icons/google.svg +1 -0
  22. package/templates/firebase/functions/src/admin/functions.ts +2 -0
  23. package/templates/firebase/functions/src/authentication/functions.ts +13 -7
  24. package/templates/firebase/functions/src/notifications/triggers.ts +6 -2
  25. package/templates/firebase/lib/components/components.dart +5 -2
  26. package/templates/firebase/lib/components/kasy_app_bar.dart +325 -15
  27. package/templates/firebase/lib/components/kasy_card.dart +4 -0
  28. package/templates/firebase/lib/components/kasy_drop_down.dart +584 -0
  29. package/templates/firebase/lib/components/kasy_sidebar.dart +18 -6
  30. package/templates/firebase/lib/components/kasy_tabs.dart +31 -10
  31. package/templates/firebase/lib/components/kasy_text_field.dart +29 -7
  32. package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +27 -18
  33. package/templates/firebase/lib/core/bottom_menu/web_content_wrapper.dart +34 -16
  34. package/templates/firebase/lib/core/chrome/app_bar_config.dart +214 -0
  35. package/templates/firebase/lib/core/chrome/app_bar_scope.dart +102 -0
  36. package/templates/firebase/lib/core/data/api/user_api.dart +11 -0
  37. package/templates/firebase/lib/core/dev_inspector/dev_inspector_service.dart +55 -15
  38. package/templates/firebase/lib/core/rating/widgets/review_popup.dart +95 -30
  39. package/templates/firebase/lib/core/shared_preferences/shared_preferences.dart +11 -0
  40. package/templates/firebase/lib/core/states/logout_action.dart +11 -1
  41. package/templates/firebase/lib/core/states/user_state_notifier.dart +28 -1
  42. package/templates/firebase/lib/core/theme/texts.dart +21 -6
  43. package/templates/firebase/lib/core/theme/type_scale.dart +34 -15
  44. package/templates/firebase/lib/core/web_device_preview/web_device_preview.dart +51 -19
  45. package/templates/firebase/lib/core/web_viewport_scale.dart +66 -36
  46. package/templates/firebase/lib/core/widgets/kasy_pressable_depth.dart +14 -3
  47. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_chat_composer.dart +1 -1
  48. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_chat_conversation_view.dart +52 -35
  49. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_list.dart +1 -1
  50. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_tile.dart +18 -8
  51. package/templates/firebase/lib/features/authentication/ui/signin_page.dart +11 -61
  52. package/templates/firebase/lib/features/authentication/ui/signup_page.dart +11 -61
  53. package/templates/firebase/lib/features/authentication/ui/widgets/auth_card_scaffold.dart +7 -5
  54. package/templates/firebase/lib/features/authentication/ui/widgets/social_auth_tile.dart +83 -0
  55. package/templates/firebase/lib/features/feedbacks/ui/widgets/feature_card.dart +4 -4
  56. package/templates/firebase/lib/features/home/home_components_page.dart +253 -125
  57. package/templates/firebase/lib/features/home/home_components_preview_registry.dart +263 -59
  58. package/templates/firebase/lib/features/home/home_feed.dart +2 -2
  59. package/templates/firebase/lib/features/home/home_image_grid.dart +3 -3
  60. package/templates/firebase/lib/features/local_reminders/providers/reminder_notifier.dart +8 -1
  61. package/templates/firebase/lib/features/local_reminders/ui/reminder_page.dart +111 -57
  62. package/templates/firebase/lib/features/notifications/api/device_api.dart +11 -3
  63. package/templates/firebase/lib/features/notifications/ui/notifications_page.dart +16 -4
  64. package/templates/firebase/lib/features/notifications/ui/widgets/empty_notifications.dart +7 -56
  65. package/templates/firebase/lib/features/notifications/ui/widgets/notification_tile.dart +5 -5
  66. package/templates/firebase/lib/features/notifications/ui/widgets/push_permission_banner.dart +163 -0
  67. package/templates/firebase/lib/features/notifications/ui/widgets/web_notifications_bell.dart +2 -2
  68. package/templates/firebase/lib/features/onboarding/providers/onboarding_model.dart +9 -0
  69. package/templates/firebase/lib/features/onboarding/providers/onboarding_provider.dart +28 -0
  70. package/templates/firebase/lib/features/onboarding/ui/onboarding_page.dart +51 -12
  71. package/templates/firebase/lib/features/onboarding/ui/widgets/selectable_row_tile.dart +90 -32
  72. package/templates/firebase/lib/features/settings/settings_page.dart +53 -32
  73. package/templates/firebase/lib/features/settings/ui/components/admin/admin_home_widgets.dart +4 -0
  74. package/templates/firebase/lib/features/settings/ui/components/admin/admin_page.dart +895 -111
  75. package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_api.dart +7 -0
  76. package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_tab.dart +445 -233
  77. package/templates/firebase/lib/features/settings/ui/components/admin/send_push_notification_page.dart +171 -41
  78. package/templates/firebase/lib/features/settings/ui/components/avatar_component.dart +1 -1
  79. package/templates/firebase/lib/features/settings/ui/components/delete_user_component.dart +9 -1
  80. package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +48 -47
  81. package/templates/firebase/lib/features/settings/ui/widgets/settings_bottom_sheet_option_tile.dart +21 -18
  82. package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +25 -10
  83. package/templates/firebase/lib/i18n/en.i18n.json +753 -712
  84. package/templates/firebase/lib/i18n/es.i18n.json +753 -712
  85. package/templates/firebase/lib/i18n/pt.i18n.json +753 -712
  86. package/templates/firebase/lib/main.dart +20 -7
  87. package/templates/firebase/lib/router.dart +32 -26
  88. package/templates/firebase/pubspec.yaml +2 -1
  89. package/templates/firebase/test/admin_shell_chrome_test.dart +11 -5
  90. package/templates/firebase/test/app_bar_config_test.dart +70 -0
  91. package/templates/firebase/test/components/kasy_text_field_height_test.dart +77 -0
  92. package/templates/firebase/test/core/web_viewport_scale_test.dart +23 -16
  93. package/templates/firebase/tool/design_check.dart +9 -0
  94. package/templates/firebase/assets/icons/apple.png +0 -0
  95. package/templates/firebase/assets/icons/facebook.png +0 -0
  96. package/templates/firebase/assets/icons/google.png +0 -0
  97. package/templates/firebase/assets/icons/google_play_games.png +0 -0
  98. package/templates/firebase/lib/components/kasy_web_header.dart +0 -218
  99. package/templates/firebase/lib/core/chrome/web_header_scope.dart +0 -20
  100. package/templates/firebase/lib/features/authentication/ui/components/apple_signin.dart +0 -19
  101. package/templates/firebase/lib/features/authentication/ui/components/google_signin.dart +0 -32
  102. package/templates/firebase/lib/features/authentication/ui/widgets/round_signin.dart +0 -73
  103. package/templates/firebase/lib/features/feedbacks/ui/widgets/add_feature_button.dart +0 -66
  104. package/templates/firebase/lib/features/notifications/ui/components/notification_settings_sheet.dart +0 -179
  105. package/templates/firebase/lib/features/notifications/ui/components/push_notification_switcher.dart +0 -106
@@ -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
- }
@@ -1,179 +0,0 @@
1
- import 'package:flutter/material.dart';
2
- import 'package:flutter_riverpod/flutter_riverpod.dart';
3
- import 'package:kasy_kit/components/kasy_bottom_sheet.dart';
4
- import 'package:kasy_kit/core/theme/theme.dart';
5
- import 'package:kasy_kit/features/notifications/providers/models/notification.dart';
6
- import 'package:kasy_kit/features/notifications/providers/notifications_provider.dart';
7
- import 'package:kasy_kit/features/notifications/repositories/notifications_repository.dart';
8
- import 'package:kasy_kit/features/settings/ui/widgets/settings_tile.dart';
9
- import 'package:kasy_kit/i18n/translations.g.dart';
10
-
11
- void showNotificationSettingsSheet(BuildContext context) {
12
- showKasyBottomSheet<void>(
13
- context: context,
14
- isScrollControlled: true,
15
- builder: (_) => const _NotificationSettingsSheet(),
16
- );
17
- }
18
-
19
- class _NotificationSettingsSheet extends ConsumerStatefulWidget {
20
- const _NotificationSettingsSheet();
21
-
22
- @override
23
- ConsumerState<_NotificationSettingsSheet> createState() =>
24
- _NotificationSettingsSheetState();
25
- }
26
-
27
- class _NotificationSettingsSheetState
28
- extends ConsumerState<_NotificationSettingsSheet> {
29
- NotificationPermission? _permission;
30
- bool _toggling = false;
31
-
32
- @override
33
- void initState() {
34
- super.initState();
35
- _loadPermission();
36
- }
37
-
38
- Future<void> _loadPermission() async {
39
- final p =
40
- await ref.read(notificationRepositoryProvider).getPermissionStatus();
41
- if (mounted) setState(() => _permission = p);
42
- }
43
-
44
- bool get _isEnabled => _permission is NotificationPermissionGranted;
45
-
46
- String _subtitle(BuildContext context) {
47
- final tr = context.t.notifications;
48
- return switch (_permission) {
49
- NotificationPermissionGranted() => tr.push_subtitle_enabled,
50
- NotificationPermissionDenied() => tr.push_subtitle_disabled,
51
- _ => tr.push_subtitle_waiting,
52
- };
53
- }
54
-
55
- Future<void> _onToggle(bool value) async {
56
- if (_toggling || _permission == null) return;
57
- setState(() => _toggling = true);
58
- await _permission!.maybeAsk();
59
- await _loadPermission();
60
- if (mounted) setState(() => _toggling = false);
61
- }
62
-
63
- Future<void> _markAllRead() async {
64
- await ref.read(notificationsProvider.notifier).readAll();
65
- if (mounted) Navigator.of(context).pop();
66
- }
67
-
68
- @override
69
- Widget build(BuildContext context) {
70
- final tr = context.t.notifications;
71
- final notificationsState = ref.watch(notificationsProvider);
72
- final hasUnread =
73
- notificationsState.value?.data.any((n) => !n.seen) ?? false;
74
-
75
- final bool floating = kasySheetIsFloating(context);
76
-
77
- final Widget card = Container(
78
- margin: floating
79
- ? EdgeInsets.zero
80
- : EdgeInsets.only(
81
- left: KasySpacing.sm,
82
- right: KasySpacing.sm,
83
- bottom: MediaQuery.of(context).viewInsets.bottom +
84
- MediaQuery.of(context).padding.bottom +
85
- KasySpacing.sm,
86
- ),
87
- decoration: BoxDecoration(
88
- color: context.colors.background,
89
- borderRadius: KasyRadius.lgBorderRadius,
90
- border: Border.all(
91
- color: context.colors.outline.withValues(alpha: 0.5),
92
- ),
93
- ),
94
- child: Column(
95
- mainAxisSize: MainAxisSize.min,
96
- children: [
97
- // drag handle (bottom-sheet form only)
98
- if (!floating)
99
- Padding(
100
- padding: const EdgeInsets.only(top: KasySpacing.smd),
101
- child: Container(
102
- width: 36,
103
- height: 4,
104
- decoration: BoxDecoration(
105
- color: context.colors.onSurface.withValues(alpha: 0.14),
106
- borderRadius: KasyRadius.fullBorderRadius,
107
- ),
108
- ),
109
- ),
110
- Padding(
111
- padding: const EdgeInsets.fromLTRB(
112
- KasySpacing.md,
113
- KasySpacing.md,
114
- KasySpacing.md,
115
- KasySpacing.smd,
116
- ),
117
- child: Column(
118
- crossAxisAlignment: CrossAxisAlignment.start,
119
- children: [
120
- // Push toggle row
121
- SettingsSwitchTile(
122
- icon: _isEnabled
123
- ? KasyIcons.notificationActive
124
- : KasyIcons.notification,
125
- title: tr.push_title,
126
- value: _isEnabled,
127
- iconBackgroundColor: _isEnabled
128
- ? const Color(0xFFFF6B35) // design-check: ignore — category accent
129
- : const Color(0xFF78909C), // design-check: ignore — category accent
130
- onChanged: _toggling ? (_) {} : _onToggle,
131
- ),
132
- // subtitle below
133
- Padding(
134
- padding: const EdgeInsets.only(
135
- left: 32 + KasySpacing.sm,
136
- bottom: KasySpacing.xs,
137
- ),
138
- child: _permission == null
139
- ? SizedBox(
140
- height: 13,
141
- width: 140,
142
- child: LinearProgressIndicator(
143
- borderRadius: BorderRadius.circular(4),
144
- color: context.colors.primary.withValues(alpha: 0.25),
145
- backgroundColor: context.colors.onSurface
146
- .withValues(alpha: 0.06),
147
- ),
148
- )
149
- : Text(
150
- _subtitle(context),
151
- style: context.textTheme.bodySmall?.copyWith(
152
- color: context.colors.muted,
153
- ),
154
- ),
155
- ),
156
- if (hasUnread) ...[
157
- const SettingsDivider(),
158
- const SizedBox(height: KasySpacing.xs),
159
- SettingsTile(
160
- icon: KasyIcons.checkCircle,
161
- title: tr.mark_all_read,
162
- iconBackgroundColor: context.colors.primary,
163
- onTap: _markAllRead,
164
- ),
165
- ],
166
- ],
167
- ),
168
- ),
169
- ],
170
- ),
171
- );
172
-
173
- if (!floating) return card;
174
- return ConstrainedBox(
175
- constraints: const BoxConstraints(maxWidth: 420),
176
- child: card,
177
- );
178
- }
179
- }
@@ -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
- }