kasy-cli 1.19.3 → 1.20.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 (83) hide show
  1. package/README.md +11 -3
  2. package/bin/kasy.js +1 -0
  3. package/lib/commands/new.js +87 -37
  4. package/lib/commands/run.js +14 -0
  5. package/lib/scaffold/backends/api/patch/lib/core/data/api/meta_ads_api.dart +1 -1
  6. package/lib/scaffold/backends/api/patch/lib/core/data/api/storage_api.dart +1 -1
  7. package/lib/scaffold/backends/api/patch/lib/core/data/entities/user_entity.dart +1 -1
  8. package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api.dart +4 -5
  9. package/lib/scaffold/backends/api/patch/lib/features/feedbacks/api/feature_request_api.dart +1 -1
  10. package/lib/scaffold/backends/api/patch/lib/features/feedbacks/api/feature_vote_api.dart +1 -1
  11. package/lib/scaffold/backends/api/patch/lib/features/llm_chat/api/llm_chat_api.dart +1 -1
  12. package/lib/scaffold/backends/api/patch/lib/features/llm_chat/providers/llm_chat_notifier.dart +317 -0
  13. package/lib/scaffold/backends/api/patch/lib/features/notifications/api/device_api.dart +40 -1
  14. package/lib/scaffold/backends/api/patch/lib/features/notifications/api/entities/notifications_entity.dart +2 -0
  15. package/lib/scaffold/backends/api/patch/lib/features/onboarding/api/entities/user_info_entity.dart +1 -1
  16. package/lib/scaffold/backends/api/patch/lib/features/subscription/api/entities/subscription_entity.dart +1 -1
  17. package/lib/scaffold/backends/api/patch/lib/features/subscription/shared/maybeshow_premium.dart +0 -2
  18. package/lib/scaffold/backends/api/pubspec.yaml.tpl +2 -0
  19. package/lib/scaffold/backends/firebase/enable-auth-via-cli.js +11 -8
  20. package/lib/scaffold/backends/firebase/setup-from-scratch.js +91 -2
  21. package/lib/scaffold/backends/supabase/deploy.js +56 -3
  22. package/lib/scaffold/backends/supabase/patch/lib/core/data/api/storage_api.dart +5 -11
  23. package/lib/scaffold/backends/supabase/patch/lib/core/data/entities/user_entity.dart +2 -2
  24. package/lib/scaffold/backends/supabase/patch/lib/features/notifications/api/device_api.dart +31 -1
  25. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/api/entities/user_info_entity.dart +1 -1
  26. package/lib/scaffold/backends/supabase/patch/lib/features/subscription/api/entities/subscription_entity.dart +1 -1
  27. package/lib/scaffold/backends/supabase/pubspec.yaml.tpl +2 -0
  28. package/lib/scaffold/catalog.js +2 -2
  29. package/lib/scaffold/engine.js +5 -0
  30. package/lib/scaffold/generate.js +23 -3
  31. package/lib/scaffold/shared/generator-utils.js +303 -56
  32. package/lib/scaffold/shared/post-build.js +11 -0
  33. package/lib/utils/i18n/messages-en.js +6 -1
  34. package/lib/utils/i18n/messages-es.js +6 -1
  35. package/lib/utils/i18n/messages-pt.js +6 -1
  36. package/package.json +1 -1
  37. package/templates/firebase/android/app/src/main/res/drawable/background.png +0 -0
  38. package/templates/firebase/android/app/src/main/res/drawable-night/background.png +0 -0
  39. package/templates/firebase/android/app/src/main/res/drawable-night-v21/background.png +0 -0
  40. package/templates/firebase/android/app/src/main/res/drawable-v21/background.png +0 -0
  41. package/templates/firebase/android/app/src/main/res/values-night-v31/styles.xml +1 -1
  42. package/templates/firebase/android/app/src/main/res/values-v31/styles.xml +1 -1
  43. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png +0 -0
  44. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png +0 -0
  45. package/templates/firebase/lib/components/kasy_date_picker.dart +14 -8
  46. package/templates/firebase/lib/components/kasy_sidebar_pro.dart +1150 -0
  47. package/templates/firebase/lib/components/kasy_tabs.dart +156 -43
  48. package/templates/firebase/lib/components/kasy_text_field.dart +37 -34
  49. package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +13 -82
  50. package/templates/firebase/lib/core/bottom_menu/bottom_router.dart +6 -102
  51. package/templates/firebase/lib/core/bottom_menu/kasy_bottom_bar_factory.dart +8 -1
  52. package/templates/firebase/lib/core/dev_inspector/dev_inspector.dart +433 -243
  53. package/templates/firebase/lib/core/dev_inspector/dev_inspector_service.dart +198 -83
  54. package/templates/firebase/lib/core/icons/kasy_icons.dart +1 -0
  55. package/templates/firebase/lib/core/states/user_state_notifier.dart +8 -10
  56. package/templates/firebase/lib/core/theme/colors.dart +6 -2
  57. package/templates/firebase/lib/core/web_device_preview/web_device_preview.dart +119 -19
  58. package/templates/firebase/lib/core/widgets/kasy_hover.dart +68 -27
  59. package/templates/firebase/lib/features/home/home_components_page.dart +11 -14
  60. package/templates/firebase/lib/features/home/home_components_preview_registry.dart +121 -66
  61. package/templates/firebase/lib/features/home/home_page.dart +7 -8
  62. package/templates/firebase/lib/features/settings/settings_page.dart +27 -146
  63. package/templates/firebase/lib/features/settings/ui/components/admin/admin_bottom_sheet.dart +16 -3
  64. package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +22 -5
  65. package/templates/firebase/lib/i18n/en.i18n.json +3 -1
  66. package/templates/firebase/lib/i18n/es.i18n.json +3 -1
  67. package/templates/firebase/lib/i18n/pt.i18n.json +3 -1
  68. package/templates/firebase/lib/router.dart +60 -0
  69. package/templates/firebase/pubspec.yaml +6 -4
  70. package/templates/firebase/test/core/bottom_menu/detail_route_menu_test.dart +57 -0
  71. package/templates/firebase/web/index.html +7 -17
  72. package/lib/scaffold/backends/api/patch/lib/core/rating/widgets/review_popup.dart +0 -211
  73. package/lib/scaffold/backends/api/patch/lib/features/notifications/providers/models/notification.dart +0 -185
  74. package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +0 -73
  75. package/lib/scaffold/backends/api/patch/lib/main.dart +0 -275
  76. package/lib/scaffold/backends/api/patch/lib/router.dart +0 -133
  77. package/lib/scaffold/backends/supabase/patch/lib/core/rating/widgets/review_popup.dart +0 -211
  78. package/lib/scaffold/backends/supabase/patch/lib/features/feedbacks/ui/component/add_feature_form.dart +0 -199
  79. package/lib/scaffold/backends/supabase/patch/lib/features/notifications/providers/models/notification.dart +0 -174
  80. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +0 -73
  81. package/lib/scaffold/backends/supabase/patch/lib/main.dart +0 -307
  82. package/lib/scaffold/backends/supabase/patch/lib/router.dart +0 -133
  83. package/templates/firebase/lib/firebase_options.dart +0 -75
@@ -1,199 +0,0 @@
1
- import 'package:flutter/material.dart';
2
- import 'package:flutter_riverpod/flutter_riverpod.dart';
3
- import 'package:go_router/go_router.dart';
4
- import 'package:kasy_kit/components/components.dart';
5
- import 'package:kasy_kit/core/theme/theme.dart';
6
- import 'package:kasy_kit/core/widgets/kasy_scroll_behavior.dart';
7
- import 'package:kasy_kit/features/feedbacks/providers/feedback_page_notifier.dart';
8
- import 'package:kasy_kit/i18n/translations.g.dart';
9
-
10
- Future<void> showAddFeatureBottomSheet(BuildContext context) {
11
- return showModalBottomSheet<bool?>(
12
- context: context,
13
- isScrollControlled: true,
14
- builder: (context) => const FractionallySizedBox(
15
- heightFactor: .8,
16
- child: AddFeatureComponent(),
17
- ),
18
- ).then((res) {
19
- if (!context.mounted) return;
20
- final translations = Translations.of(context).feature_requests.add_feature;
21
- if (res == true) {
22
- showSuccessToast(
23
- context: context,
24
- title: translations.toast_success.title,
25
- text: translations.toast_success.description,
26
- );
27
- }
28
- });
29
- }
30
-
31
- class AddFeatureComponent extends ConsumerStatefulWidget {
32
- const AddFeatureComponent({super.key});
33
-
34
- @override
35
- ConsumerState<ConsumerStatefulWidget> createState() =>
36
- _AddFeatureComponentState();
37
- }
38
-
39
- class _AddFeatureComponentState extends ConsumerState<AddFeatureComponent> {
40
- String title = '';
41
- String description = '';
42
- bool locked = false;
43
-
44
- final titleFocusNode = FocusNode();
45
- final textFocusNode = FocusNode();
46
-
47
- bool get _titleFilled => title.trim().isNotEmpty;
48
- bool get _descriptionValid => description.length >= 40;
49
- bool get canSubmit => _titleFilled && _descriptionValid && !locked;
50
-
51
- void _onSubmitAttempt() {
52
- final tr = Translations.of(context).feature_requests.add_feature;
53
- if (!_titleFilled || description.trim().isEmpty) {
54
- ref.read(toastProvider).error(
55
- title: tr.error_title,
56
- text: tr.error_required,
57
- );
58
- return;
59
- }
60
- if (!_descriptionValid) {
61
- ref.read(toastProvider).error(
62
- title: tr.error_title,
63
- text: tr.error_too_short,
64
- );
65
- return;
66
- }
67
- }
68
-
69
- void _submit() {
70
- if (locked) return;
71
- FocusScope.of(context).unfocus();
72
- locked = true;
73
- final tr = Translations.of(context).feature_requests.add_feature;
74
- ref
75
- .read(feedbackPageProvider.notifier)
76
- .createFeatureSuggestion(
77
- title: title,
78
- description: description,
79
- )
80
- .then(
81
- (_) {
82
- locked = false;
83
- if (!mounted) return;
84
- context.pop(true);
85
- },
86
- onError: (_) {
87
- locked = false;
88
- ref.read(toastProvider).error(
89
- title: tr.error_title,
90
- text: tr.error_sending,
91
- );
92
- },
93
- );
94
- }
95
-
96
- @override
97
- Widget build(BuildContext context) {
98
- final translations = Translations.of(context).feature_requests.add_feature;
99
-
100
- final Widget submitButton = KasyButton(
101
- label: translations.save_button,
102
- expand: true,
103
- onPressed: canSubmit ? _submit : null,
104
- );
105
-
106
- return Scaffold(
107
- body: Stack(
108
- fit: StackFit.expand,
109
- children: [
110
- Positioned.fill(
111
- child: ScrollConfiguration(
112
- behavior: const KasyKitScrollBehavior(),
113
- child: CustomScrollView(
114
- slivers: kasyOverlayPaddedSlivers(
115
- context,
116
- contentPadding: EdgeInsets.fromLTRB(
117
- KasySpacing.pageHorizontalGutter,
118
- KasySpacing.belowChromeContentGap,
119
- KasySpacing.pageHorizontalGutter,
120
- MediaQuery.paddingOf(context).bottom + KasySpacing.xxxl,
121
- ),
122
- slivers: [
123
- SliverToBoxAdapter(
124
- child: Column(
125
- crossAxisAlignment: CrossAxisAlignment.stretch,
126
- children: [
127
- const SizedBox(height: KasySpacing.md),
128
- Text(
129
- translations.description,
130
- style: context.textTheme.bodyMedium?.copyWith(
131
- color: context.colors.muted,
132
- height: 1.5,
133
- ),
134
- ),
135
- const SizedBox(height: KasySpacing.xl),
136
- KasyTextField(
137
- label: translations.title_label,
138
- hint: translations.title_hint,
139
- showRequiredIndicator: true,
140
- variant: KasyTextFieldVariant.secondary,
141
- onChanged: (value) =>
142
- setState(() => title = value),
143
- focusNode: titleFocusNode,
144
- textInputAction: TextInputAction.next,
145
- onSubmitted: (_) {
146
- titleFocusNode.unfocus();
147
- textFocusNode.requestFocus();
148
- },
149
- maxLength: 40,
150
- ),
151
- const SizedBox(height: KasySpacing.md),
152
- KasyTextField(
153
- label: translations.description_label,
154
- hint: translations.description_hint,
155
- showRequiredIndicator: true,
156
- variant: KasyTextFieldVariant.secondary,
157
- minLines: 6,
158
- maxLines: 6,
159
- onChanged: (value) =>
160
- setState(() => description = value),
161
- textInputAction: TextInputAction.done,
162
- onSubmitted: (_) => textFocusNode.unfocus(),
163
- maxLength: 1000,
164
- focusNode: textFocusNode,
165
- ),
166
- const SizedBox(height: KasySpacing.xl),
167
- ],
168
- ),
169
- ),
170
- ],
171
- ),
172
- ),
173
- ),
174
- ),
175
- Positioned(
176
- top: 0,
177
- left: 0,
178
- right: 0,
179
- child: KasyAppBar(
180
- title: translations.title,
181
- onBack: () => context.pop(),
182
- ),
183
- ),
184
- Positioned(
185
- left: KasySpacing.pageHorizontalGutter,
186
- right: KasySpacing.pageHorizontalGutter,
187
- bottom: MediaQuery.paddingOf(context).bottom + KasySpacing.md,
188
- child: canSubmit
189
- ? submitButton
190
- : GestureDetector(
191
- onTap: _onSubmitAttempt,
192
- child: submitButton,
193
- ),
194
- ),
195
- ],
196
- ),
197
- );
198
- }
199
- }
@@ -1,174 +0,0 @@
1
- // ignore_for_file: invalid_annotation_target, constant_identifier_names
2
-
3
- import 'package:freezed_annotation/freezed_annotation.dart';
4
- import 'package:logger/logger.dart';
5
- import 'package:permission_handler/permission_handler.dart';
6
- import 'package:kasy_kit/features/notifications/api/entities/notifications_entity.dart';
7
- import 'package:kasy_kit/features/notifications/api/local_notifier.dart';
8
- import 'package:kasy_kit/features/notifications/repositories/notifications_repository.dart';
9
- import 'package:go_router/go_router.dart';
10
- import 'package:kasy_kit/router.dart';
11
- import 'package:sentry_flutter/sentry_flutter.dart';
12
- import 'package:url_launcher/url_launcher.dart';
13
-
14
- part 'notification.freezed.dart';
15
- part 'notification.g.dart';
16
-
17
- @freezed
18
- sealed class Notification with _$Notification {
19
- const Notification._();
20
-
21
- const factory Notification.withData({
22
- String? id,
23
- required String title,
24
- required String body,
25
- required DateTime createdAt,
26
- DateTime? readAt,
27
- String? imageUrl,
28
- @JsonKey(includeFromJson: false, includeToJson: false)
29
- LocalNotifier? notifier,
30
- @JsonKey(includeFromJson: false, includeToJson: false)
31
- NotificationSettings? notifierSettings,
32
- NotificationTypes? type,
33
- Map<String, dynamic>? data,
34
- }) = NotificationData;
35
-
36
- factory Notification.fromJson(Map<String, dynamic> json) =>
37
- _$NotificationFromJson(json);
38
-
39
- factory Notification.from(
40
- Map<String, dynamic> json, {
41
- String? id,
42
- Map<String, dynamic>? data,
43
- LocalNotifier? notifierApi,
44
- NotificationSettings? notifierSettings,
45
- }) =>
46
- Notification.withData(
47
- id: id,
48
- title: json['title'] as String,
49
- body: json['body'] as String,
50
- imageUrl: json['image'] as String?,
51
- type: data != null && data.containsKey('type')
52
- ? NotificationTypes.values.firstWhere(
53
- (e) => e.name == data['type'],
54
- orElse: () => NotificationTypes.OTHER,
55
- )
56
- : null,
57
- data: data,
58
- createdAt: DateTime.now(),
59
- notifier: notifierApi,
60
- notifierSettings: notifierSettings,
61
- );
62
-
63
- Future<void> show({NotificationSettings? settings}) async {
64
- if (notifier == null) {
65
- throw Exception(
66
- 'You must provide a LocalNotifierApi to show a notification',
67
- );
68
- }
69
- if (notifierSettings != null) {
70
- await notifier!.show(notifierSettings!, this);
71
- return;
72
- } else if (settings != null) {
73
- await notifier!.show(settings, this);
74
- return;
75
- }
76
- throw Exception(
77
- 'You must provide a NotificationSettings to show a notification',
78
- );
79
- }
80
-
81
- bool get seen => readAt != null;
82
-
83
- Future<void> onTap() async {
84
- // if the app is not ready, we store the notification in the pending notification handler
85
- if (navigatorKey.currentContext == null) {
86
- return;
87
- }
88
-
89
- // Open external URL
90
- if (type == NotificationTypes.LINK && data?.containsKey('url') == true) {
91
- try {
92
- launchUrl(Uri.parse(data!['url'] as String));
93
- } catch (e, s) {
94
- Logger().e("error $e");
95
- Sentry.captureException(e, stackTrace: s);
96
- }
97
- return;
98
- }
99
- // Navigate to an internal route sent in the notification data.
100
- // Example: data: {"route": "/premium"} → opens the premium page.
101
- if (data?.containsKey('route') == true) {
102
- final route = data!['route'] as String;
103
- navigatorKey.currentContext!.go(route);
104
- return;
105
- }
106
- // Default: open the notifications list
107
- navigatorKey.currentContext!.go('/notifications');
108
- }
109
- }
110
-
111
- sealed class NotificationPermission {
112
- /// ask for permission if needed
113
- Future<void> maybeAsk() async {
114
- final permission = this;
115
- switch (permission) {
116
- case NotificationPermissionWaiting():
117
- await permission.ask();
118
- case NotificationPermissionDenied():
119
- await permission.ask();
120
- case NotificationPermissionGranted():
121
- }
122
- }
123
- }
124
-
125
- /// we asked for permission and it was granted
126
- class NotificationPermissionGranted extends NotificationPermission {
127
- NotificationPermissionGranted();
128
- }
129
-
130
- /// we asked for permission and it was denied
131
- class NotificationPermissionDenied extends NotificationPermission {
132
- final NotificationSettings? _notificationSettings;
133
- final NotificationsRepository? _repository;
134
-
135
- NotificationPermissionDenied({
136
- NotificationSettings? notificationSettings,
137
- NotificationsRepository? repository,
138
- }) : _repository = repository,
139
- _notificationSettings = notificationSettings;
140
-
141
- Future<void> ask() async {
142
- if (_notificationSettings == null) {
143
- throw Exception("NotificationsApi is null");
144
- }
145
- await _notificationSettings.askPermission();
146
- await _repository!.init();
147
- }
148
-
149
- Future<void> openSettings() async {
150
- await openAppSettings();
151
- }
152
- }
153
-
154
- /// we never asked for permission
155
- class NotificationPermissionWaiting extends NotificationPermission {
156
- final NotificationSettings? _notificationSettings;
157
- final NotificationsRepository? _repository;
158
-
159
- NotificationPermissionWaiting({
160
- NotificationSettings? notificationSettings,
161
- NotificationsRepository? repository,
162
- }) : _notificationSettings = notificationSettings,
163
- _repository = repository;
164
-
165
- Future<void> ask() async {
166
- if (_notificationSettings == null) {
167
- throw Exception("NotificationsApi is null");
168
- }
169
- final granted = await _notificationSettings.askPermission();
170
- if (granted) {
171
- await _repository?.init();
172
- }
173
- }
174
- }
@@ -1,73 +0,0 @@
1
- import 'package:flutter/material.dart';
2
- import 'package:flutter_riverpod/flutter_riverpod.dart';
3
- import 'package:kasy_kit/components/components.dart';
4
- import 'package:kasy_kit/core/data/api/analytics_api.dart';
5
- import 'package:kasy_kit/core/theme/theme.dart';
6
- import 'package:kasy_kit/features/notifications/providers/models/notification.dart';
7
- import 'package:kasy_kit/features/notifications/repositories/notifications_repository.dart';
8
- import 'package:kasy_kit/features/onboarding/providers/onboarding_provider.dart';
9
- import 'package:kasy_kit/features/onboarding/ui/widgets/onboarding_background.dart';
10
- import 'package:kasy_kit/features/onboarding/ui/widgets/onboarding_illustration_scaffold.dart';
11
- import 'package:kasy_kit/i18n/translations.g.dart';
12
-
13
- final _formStep3Key = GlobalKey<FormState>();
14
-
15
- class NotificationsPermissionStep extends ConsumerWidget {
16
- final String nextRoute;
17
-
18
- const NotificationsPermissionStep({
19
- super.key,
20
- required this.nextRoute,
21
- });
22
-
23
- @override
24
- Widget build(BuildContext context, WidgetRef ref) {
25
- final notifRepository = ref.watch(notificationRepositoryProvider);
26
- final permissionFuture = notifRepository.getPermissionStatus();
27
- final translations = Translations.of(context).onboarding.notifications;
28
-
29
- return OnboardingBackground(
30
- child: FutureBuilder<NotificationPermission>(
31
- future: permissionFuture,
32
- builder: (context, snapshot) {
33
- if (!snapshot.hasData) {
34
- return const SizedBox();
35
- }
36
- return Form(
37
- key: _formStep3Key,
38
- child: OnboardingIllustrationScaffold(
39
- progress: 0.9,
40
- title: translations.title,
41
- description: translations.description,
42
- imageAsset: 'assets/images/onboarding/img2.jpg',
43
- footerActions: [
44
- KasyButton(
45
- label: translations.continue_button,
46
- expand: true,
47
- onPressed: () => ref.onboardingNotifier
48
- .setupNotifications()
49
- .then((_) {
50
- if (!context.mounted) return;
51
- Navigator.of(context).pushNamed(nextRoute);
52
- }),
53
- ),
54
- const SizedBox(height: KasySpacing.smd),
55
- KasyButton(
56
- label: translations.skip_button,
57
- variant: KasyButtonVariant.soft,
58
- expand: true,
59
- onPressed: () {
60
- ref
61
- .read(analyticsApiProvider)
62
- .logEvent('setup_notifications_refused', {});
63
- Navigator.of(context).pushNamed(nextRoute);
64
- },
65
- ),
66
- ],
67
- ),
68
- );
69
- },
70
- ),
71
- );
72
- }
73
- }