kasy-cli 1.15.0 → 1.17.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.
Files changed (44) hide show
  1. package/bin/kasy.js +1 -0
  2. package/lib/commands/add.js +45 -12
  3. package/lib/commands/doctor.js +37 -6
  4. package/lib/commands/icon.js +29 -1
  5. package/lib/commands/new.js +34 -8
  6. package/lib/commands/remove.js +14 -3
  7. package/lib/commands/run.js +264 -3
  8. package/lib/scaffold/CHANGELOG.json +9 -0
  9. package/lib/scaffold/backends/api/patch/README.md +3 -2
  10. package/lib/scaffold/backends/api/pubspec.yaml.tpl +2 -0
  11. package/lib/scaffold/backends/supabase/patch/README.md +3 -2
  12. package/lib/scaffold/backends/supabase/pubspec.yaml.tpl +2 -0
  13. package/lib/scaffold/shared/generator-utils.js +52 -8
  14. package/lib/scaffold/shared/post-build.js +105 -31
  15. package/lib/scaffold/shared/template-strings.js +6 -0
  16. package/lib/utils/i18n/messages-en.js +34 -2
  17. package/lib/utils/i18n/messages-es.js +34 -2
  18. package/lib/utils/i18n/messages-pt.js +34 -2
  19. package/lib/utils/png-padding.js +134 -2
  20. package/package.json +1 -1
  21. package/templates/firebase/README.en.md +17 -7
  22. package/templates/firebase/README.es.md +17 -7
  23. package/templates/firebase/README.md +17 -7
  24. package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MainActivity.kt +15 -0
  25. package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MyWidget.kt +3 -19
  26. package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MyWidgetReceiver.kt +37 -0
  27. package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/OpenAppAction.kt +26 -0
  28. package/templates/firebase/android/app/src/main/res/drawable-hdpi/android12splash.png +0 -0
  29. package/templates/firebase/android/app/src/main/res/drawable-mdpi/android12splash.png +0 -0
  30. package/templates/firebase/android/app/src/main/res/drawable-night-hdpi/android12splash.png +0 -0
  31. package/templates/firebase/android/app/src/main/res/drawable-night-mdpi/android12splash.png +0 -0
  32. package/templates/firebase/android/app/src/main/res/drawable-night-xhdpi/android12splash.png +0 -0
  33. package/templates/firebase/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png +0 -0
  34. package/templates/firebase/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png +0 -0
  35. package/templates/firebase/android/app/src/main/res/drawable-xhdpi/android12splash.png +0 -0
  36. package/templates/firebase/android/app/src/main/res/drawable-xxhdpi/android12splash.png +0 -0
  37. package/templates/firebase/android/app/src/main/res/drawable-xxxhdpi/android12splash.png +0 -0
  38. package/templates/firebase/assets/images/splash_logo_dark_android12.png +0 -0
  39. package/templates/firebase/assets/images/splash_logo_light_android12.png +0 -0
  40. package/templates/firebase/docs/revenuecat-setup.es.md +28 -8
  41. package/templates/firebase/docs/revenuecat-setup.pt.md +28 -8
  42. package/templates/firebase/lib/core/home_widgets/home_widget_mywidget_service.dart +39 -41
  43. package/templates/firebase/lib/router.dart +15 -1
  44. package/templates/firebase/web/index.html +3 -0
@@ -1,7 +1,3 @@
1
- import 'dart:async';
2
- import 'dart:io' show Platform;
3
-
4
- import 'package:flutter/foundation.dart' show kIsWeb;
5
1
  import 'package:home_widget/home_widget.dart';
6
2
  import 'package:kasy_kit/core/data/models/user.dart';
7
3
  import 'package:kasy_kit/core/home_widgets/home_widget_service.dart';
@@ -38,12 +34,6 @@ class MyWidgetHomeWidget extends _$MyWidgetHomeWidget
38
34
  /// Snapshot of the user fields the widget reads. Used to skip the update
39
35
  /// when an unrelated field changes (e.g. lastUpdateDate refresh).
40
36
  (String?, String?, String?, bool) _widgetSignature(User user) {
41
- final isPro = switch (user) {
42
- AuthenticatedUserData(:final subscription) ||
43
- AnonymousUserData(:final subscription) =>
44
- subscription?.isActive ?? false,
45
- _ => false,
46
- };
47
37
  final name = switch (user) {
48
38
  AuthenticatedUserData(:final name) => name,
49
39
  _ => null,
@@ -52,21 +42,41 @@ class MyWidgetHomeWidget extends _$MyWidgetHomeWidget
52
42
  AuthenticatedUserData(:final email) => email,
53
43
  _ => null,
54
44
  };
55
- return (user.idOrNull, name, email, isPro);
45
+ return (user.idOrNull, name, email, _cachedIsPro(user));
56
46
  }
57
47
 
58
48
  @override
59
- Future<void> update() => updateForLocale(LocaleSettings.currentLocale);
60
-
61
- /// Same as [update] but renders against an explicit locale. Use this
62
- /// from the language picker so the widget never falls one step behind:
63
- /// `LocaleSettings.setLocale` propagates to `currentLocale` over a
64
- /// frame boundary, and an [update] call scheduled at the same time
65
- /// can race with it. Passing the locale removes the race.
66
- Future<void> updateForLocale(AppLocale locale) async {
49
+ Future<void> update() => _renderForLocale(
50
+ LocaleSettings.currentLocale,
51
+ refreshSubscription: true,
52
+ );
53
+
54
+ /// Same as [update] but renders against an explicit locale and skips the
55
+ /// RevenueCat refresh. Two reasons:
56
+ /// 1. `LocaleSettings.setLocale` propagates to `currentLocale` over a
57
+ /// frame boundary, and an [update] call scheduled at the same time
58
+ /// can race with it — passing the locale removes the race.
59
+ /// 2. The network call inside [_resolveIsPro] adds up to 2s of latency
60
+ /// before the new strings reach SharedPreferences, which is what made
61
+ /// the widget look "one step behind" after switching language.
62
+ Future<void> updateForLocale(AppLocale locale) =>
63
+ _renderForLocale(locale, refreshSubscription: false);
64
+
65
+ Future<void> _renderForLocale(
66
+ AppLocale locale, {
67
+ required bool refreshSubscription,
68
+ }) async {
67
69
  final logger = Logger();
68
70
  logger.i('🔄 Updating MyWidget Home Widget (${locale.languageCode})');
69
71
  final user = ref.read(userStateNotifierProvider).user;
72
+
73
+ // Slang lazy-loads non-base locales: AppLocale.pt.translations falls back
74
+ // silently to the base locale (en) until the locale's bundle is loaded
75
+ // into memory. setLocale() kicks off that load asynchronously, so a tap
76
+ // on "Português" immediately followed by updateForLocale(pt) would push
77
+ // English text to the widget on the first try and the correct one on
78
+ // the retry. Awaiting loadLocale here removes the race.
79
+ await LocaleSettings.instance.loadLocale(locale);
70
80
  final t = locale.translations;
71
81
 
72
82
  // "Logged out" = no user id at all (post-logout in authRequired mode, or
@@ -86,7 +96,8 @@ class MyWidgetHomeWidget extends _$MyWidgetHomeWidget
86
96
  _ => null,
87
97
  };
88
98
 
89
- final isPro = !isLoggedOut && await _resolveIsPro(user);
99
+ final isPro = !isLoggedOut &&
100
+ (refreshSubscription ? await _resolveIsPro(user) : _cachedIsPro(user));
90
101
 
91
102
  final greeting = _greeting(t);
92
103
  final title = isLoggedOut
@@ -115,18 +126,20 @@ class MyWidgetHomeWidget extends _$MyWidgetHomeWidget
115
126
  });
116
127
  }
117
128
 
129
+ bool _cachedIsPro(User user) => switch (user) {
130
+ AuthenticatedUserData(:final subscription) ||
131
+ AnonymousUserData(:final subscription) =>
132
+ subscription?.isActive ?? false,
133
+ _ => false,
134
+ };
135
+
118
136
  /// Returns true if the user has an active subscription.
119
137
  /// Queries the SubscriptionRepository directly so the widget reflects the
120
138
  /// most recent state — including the RevenueCat fallback when the backend
121
139
  /// webhook is delayed. Falls back to the in-memory user.subscription if the
122
140
  /// fresh fetch fails (no network, RC not initialised, etc.).
123
141
  Future<bool> _resolveIsPro(User user) async {
124
- final cached = switch (user) {
125
- AuthenticatedUserData(:final subscription) ||
126
- AnonymousUserData(:final subscription) =>
127
- subscription?.isActive ?? false,
128
- _ => false,
129
- };
142
+ final cached = _cachedIsPro(user);
130
143
  final userId = user.idOrNull;
131
144
  if (userId == null) return cached;
132
145
  try {
@@ -152,21 +165,6 @@ class MyWidgetHomeWidget extends _$MyWidgetHomeWidget
152
165
  await HomeWidget.saveWidgetData<String>('isPro', data['isPro'] ?? 'false');
153
166
  await HomeWidget.saveWidgetData<String>('quote', data['quote'] ?? '');
154
167
 
155
- // On Android, saveWidgetData writes via SharedPreferences.apply() which
156
- // is asynchronous. Glance's HomeWidgetGlanceStateDefinition reads from
157
- // the same prefs, but a tight saveWidgetData→updateWidget sequence can
158
- // race the apply() flush — Glance recomposes with the previous values
159
- // (most visible right after a locale change). Fire two updates spaced
160
- // out by 400ms each so even slow devices catch the new state.
161
- if (!kIsWeb && Platform.isAndroid) {
162
- await Future<void>.delayed(const Duration(milliseconds: 400));
163
- await HomeWidget.updateWidget(
164
- name: _androidWidgetName,
165
- iOSName: _iosWidgetName,
166
- );
167
- await Future<void>.delayed(const Duration(milliseconds: 400));
168
- }
169
-
170
168
  await HomeWidget.updateWidget(
171
169
  name: _androidWidgetName,
172
170
  iOSName: _iosWidgetName,
@@ -24,6 +24,7 @@ import 'package:kasy_kit/features/settings/ui/components/admin/admin_routes.dart
24
24
  import 'package:kasy_kit/features/settings/ui/components/admin/send_push_notification_page.dart';
25
25
  import 'package:kasy_kit/features/subscription/ui/component/premium_page_factory.dart';
26
26
  import 'package:kasy_kit/features/subscription/ui/premium_page.dart';
27
+ import 'package:logger/logger.dart';
27
28
 
28
29
  final goRouterProvider = Provider<GoRouter>((ref) => generateRouter());
29
30
 
@@ -41,7 +42,20 @@ GoRouter generateRouter({
41
42
  return GoRouter(
42
43
  initialLocation: '/',
43
44
  navigatorKey: navigatorKey,
44
- errorBuilder: (context, state) => const PageNotFound(),
45
+ // Catches unknown routes (e.g. an Android warm-start from the home widget
46
+ // landed on a stale URI) and silently sends the user home instead of
47
+ // surfacing a dead-end "404" page. We log the offending URI so a real
48
+ // misconfigured route doesn't get masked.
49
+ // Note: GoRouter doesn't accept both onException and errorBuilder, so the
50
+ // /404 GoRoute below is what reaches PageNotFound when explicitly navigated.
51
+ onException: (context, state, router) {
52
+ Logger().w(
53
+ 'GoRouter caught unknown route → "${state.uri}" '
54
+ '(matched: "${state.matchedLocation}", error: ${state.error}). '
55
+ 'Redirecting to "/".',
56
+ );
57
+ router.go('/');
58
+ },
45
59
  observers: [
46
60
  AnalyticsObserver(analyticsApi: MixpanelAnalyticsApi.instance()),
47
61
 
@@ -38,6 +38,8 @@
38
38
 
39
39
 
40
40
 
41
+
42
+
41
43
  <style id="splash-screen-style">
42
44
  html {
43
45
  height: 100%
@@ -120,6 +122,7 @@
120
122
 
121
123
 
122
124
 
125
+
123
126
  <script src="flutter_bootstrap.js" async=""></script>
124
127
  <script src="./local_notifications.js"></script>
125
128