kasy-cli 1.9.2 → 1.12.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kasy-cli",
3
- "version": "1.9.2",
3
+ "version": "1.12.1",
4
4
  "description": "CLI for scaffolding production-ready Flutter SaaS apps with Firebase, Supabase, or API REST backends.",
5
5
  "bin": {
6
6
  "kasy": "./bin/kasy.js"
@@ -41,7 +41,8 @@
41
41
  "check:firebase": "node ./scripts/check-firebase-template.js",
42
42
  "test:google-ios": "node ./test/google-ios-url-scheme.test.js",
43
43
  "test:apple-release": "node ./test/apple-release.test.js",
44
- "test:localize-docs": "node ./test/localize-release-docs.test.js"
44
+ "test:localize-docs": "node ./test/localize-release-docs.test.js",
45
+ "test:i18n-accents": "node ./test/i18n-accents.test.js"
45
46
  },
46
47
  "dependencies": {
47
48
  "@clack/prompts": "^1.4.0",
@@ -17,7 +17,35 @@ class MyWidgetHomeWidget extends _$MyWidgetHomeWidget
17
17
  static const String _iosWidgetName = 'MyWidgetWidget';
18
18
 
19
19
  @override
20
- void build() {}
20
+ void build() {
21
+ // Auto-refresh the widget whenever user state changes in a way that
22
+ // affects what it renders (login/logout, name, premium status). Without
23
+ // this, the widget would only update via the 15-min background task or
24
+ // a manual trigger — a fresh subscription would not reflect on the home
25
+ // screen until the next background tick.
26
+ ref.listen(userStateNotifierProvider, (previous, next) {
27
+ if (previous == null) return;
28
+ if (_widgetSignature(previous.user) != _widgetSignature(next.user)) {
29
+ update();
30
+ }
31
+ });
32
+ }
33
+
34
+ /// Snapshot of the user fields the widget reads. Used to skip the update
35
+ /// when an unrelated field changes (e.g. lastUpdateDate refresh).
36
+ (String?, String?, bool) _widgetSignature(User user) {
37
+ final isPro = switch (user) {
38
+ AuthenticatedUserData(:final subscription) ||
39
+ AnonymousUserData(:final subscription) =>
40
+ subscription?.isActive ?? false,
41
+ _ => false,
42
+ };
43
+ final name = switch (user) {
44
+ AuthenticatedUserData(:final name) => name,
45
+ _ => null,
46
+ };
47
+ return (user.idOrNull, name, isPro);
48
+ }
21
49
 
22
50
  @override
23
51
  Future<void> update() async {
@@ -3,11 +3,19 @@ import 'package:flutter/foundation.dart';
3
3
  import 'package:flutter_riverpod/flutter_riverpod.dart';
4
4
  import 'package:home_widget/home_widget.dart';
5
5
  import 'package:kasy_kit/core/home_widgets/home_widget_background_task.dart';
6
+ import 'package:kasy_kit/core/home_widgets/home_widget_mywidget_service.dart';
6
7
  import 'package:kasy_kit/core/initializer/onstart_service.dart';
7
8
  import 'package:logger/logger.dart';
8
9
 
9
10
  final homeWidgetsManagerProvider = Provider<HomeWidgetsManager>(
10
- (ref) => HomeWidgetsManager(),
11
+ (ref) {
12
+ // Force-build the widget service at app startup so its user-state
13
+ // listener attaches and the widget auto-refreshes when subscription
14
+ // status or other relevant fields change. Without this read, the
15
+ // listener would only attach on first manual update.
16
+ ref.read(myWidgetHomeWidgetProvider.notifier);
17
+ return HomeWidgetsManager();
18
+ },
11
19
  );
12
20
 
13
21
  const String appGroupId = 'group.com.aicrus.firebase.kit';
@@ -64,7 +64,13 @@ class _InitializerState extends ConsumerState<Initializer> {
64
64
  Sentry.captureException(e, stackTrace: s);
65
65
  ref.read(onStartProvider.notifier).notifyError(e.toString());
66
66
  } finally {
67
- FlutterNativeSplash.remove();
67
+ // Defer splash removal until after the next frame is painted so the
68
+ // onReady widget renders under the splash before it disappears.
69
+ // Without this, the splash is removed before the new frame paints,
70
+ // briefly revealing the onLoading widget (visible flash).
71
+ WidgetsBinding.instance.addPostFrameCallback((_) {
72
+ FlutterNativeSplash.remove();
73
+ });
68
74
  }
69
75
  });
70
76
  }
@@ -1,41 +0,0 @@
1
- import 'package:flutter_riverpod/flutter_riverpod.dart';
2
- import 'package:logger/logger.dart';
3
- import 'package:kasy_kit/core/home_widgets/home_widget_mywidget_service.dart';
4
- import 'package:kasy_kit/core/shared_preferences/shared_preferences.dart';
5
- import 'package:kasy_kit/core/states/user_state_notifier.dart';
6
- import 'package:kasy_kit/features/authentication/api/authentication_api.dart';
7
- import 'package:kasy_kit/features/subscription/repositories/subscription_repository.dart';
8
-
9
- /// Generated by Kasy CLI
10
- /// ----
11
- /// Background task callback for workmanager
12
- /// This allows updating home widgets even when the app is in the background
13
- @pragma('vm:entry-point')
14
- Future<void> homeWidgetCallbackDispatcher() async {
15
- try {
16
- Logger().i('🔄 Home Widget background task triggered');
17
- final globalContainer = ProviderContainer();
18
- final sharedPreferences = globalContainer.read(sharedPreferencesProvider);
19
- await sharedPreferences.init();
20
- await globalContainer.read(authenticationApiProvider).init();
21
- await globalContainer.read(subscriptionRepositoryProvider).init();
22
- await globalContainer.read(userStateNotifierProvider.notifier).init();
23
-
24
- /// If you want to restric the background task to only run when the user is subscribed,
25
- /// you can use the subscription variable here.
26
- // final subscription = globalContainer
27
- // .read(userStateNotifierProvider)
28
- // .subscription;
29
-
30
- final myWidgetWidget = globalContainer.read(
31
- myWidgetHomeWidgetProvider.notifier,
32
- );
33
- await myWidgetWidget.update();
34
-
35
- globalContainer.dispose();
36
- return;
37
- } catch (e) {
38
- Logger().e('Error in background task: $e');
39
- return;
40
- }
41
- }
@@ -1,98 +0,0 @@
1
- import 'dart:io';
2
-
3
- import 'package:home_widget/home_widget.dart';
4
- import 'package:kasy_kit/core/data/models/user.dart';
5
- import 'package:kasy_kit/core/home_widgets/home_widget_service.dart';
6
- import 'package:kasy_kit/core/states/user_state_notifier.dart';
7
- import 'package:logger/logger.dart';
8
- import 'package:riverpod_annotation/riverpod_annotation.dart';
9
-
10
- part 'home_widget_mywidget_service.g.dart';
11
-
12
- @Riverpod(keepAlive: true)
13
- class MyWidgetHomeWidget extends _$MyWidgetHomeWidget
14
- implements UpdatableHomeWidget {
15
- static const String _androidWidgetName = 'MyWidgetReceiver';
16
- static const String _iosWidgetName = 'MyWidgetWidget';
17
-
18
- @override
19
- void build() {}
20
-
21
- @override
22
- Future<void> update() {
23
- Logger().i('🔄 Updating MyWidget Home Widget');
24
- final user = ref.read(userStateNotifierProvider).user;
25
-
26
- final name = switch (user) {
27
- AuthenticatedUserData(:final name)
28
- when name != null && name.isNotEmpty =>
29
- name.split(' ').first,
30
- _ => 'there',
31
- };
32
-
33
- final isPro = switch (user) {
34
- AuthenticatedUserData(:final subscription) ||
35
- AnonymousUserData(:final subscription) =>
36
- subscription?.isActive ?? false,
37
- _ => false,
38
- };
39
-
40
- return updateWidget({
41
- 'greeting': _greeting(),
42
- 'name': name,
43
- 'isPro': isPro.toString(),
44
- });
45
- }
46
-
47
- Future<void> updateWidget(Map<String, String> data) async {
48
- await HomeWidget.saveWidgetData<String>(
49
- 'greeting', data['greeting'] ?? 'Good morning');
50
- await HomeWidget.saveWidgetData<String>('name', data['name'] ?? 'there');
51
- await HomeWidget.saveWidgetData<String>(
52
- 'isPro', data['isPro'] ?? 'false');
53
-
54
- await HomeWidget.updateWidget(
55
- name: _androidWidgetName,
56
- iOSName: _iosWidgetName,
57
- );
58
- }
59
-
60
- Future<Map<String, dynamic>> getWidgetData() async {
61
- return {
62
- 'greeting': await HomeWidget.getWidgetData<String>(
63
- 'greeting',
64
- defaultValue: 'Good morning',
65
- ),
66
- 'name': await HomeWidget.getWidgetData<String>(
67
- 'name',
68
- defaultValue: 'there',
69
- ),
70
- 'isPro': await HomeWidget.getWidgetData<String>(
71
- 'isPro',
72
- defaultValue: 'false',
73
- ),
74
- };
75
- }
76
-
77
- // Returns a time-of-day greeting in the device language (pt / es / en).
78
- // Uses Platform.localeName so it works in background isolates without
79
- // requiring the Flutter locale system to be initialized.
80
- static String _greeting() {
81
- final lang = Platform.localeName.split(RegExp(r'[_\-]')).first.toLowerCase();
82
- final hour = DateTime.now().hour;
83
-
84
- return switch (lang) {
85
- 'pt' => hour < 12 ? 'Bom dia' : hour < 18 ? 'Boa tarde' : 'Boa noite',
86
- 'es' => hour < 12
87
- ? 'Buenos días'
88
- : hour < 18
89
- ? 'Buenas tardes'
90
- : 'Buenas noches',
91
- _ => hour < 12
92
- ? 'Good morning'
93
- : hour < 18
94
- ? 'Good afternoon'
95
- : 'Good evening',
96
- };
97
- }
98
- }
@@ -1,54 +0,0 @@
1
- import 'package:background_fetch/background_fetch.dart';
2
- import 'package:flutter/foundation.dart';
3
- import 'package:flutter_riverpod/flutter_riverpod.dart';
4
- import 'package:home_widget/home_widget.dart';
5
- import 'package:logger/logger.dart';
6
- import 'package:kasy_kit/core/home_widgets/home_widget_background_task.dart';
7
- import 'package:kasy_kit/core/initializer/onstart_service.dart';
8
-
9
- final homeWidgetsManagerProvider = Provider<HomeWidgetsManager>(
10
- (ref) => HomeWidgetsManager(),
11
- );
12
-
13
- const String appGroupId = 'group.com.appfirebase.app';
14
-
15
- /// Manager for all home widgets
16
- /// -> Don't modify this file
17
- /// ------------------------------------------------------------
18
- /// will be used to initialize the home widgets and set the app group id
19
- /// Register the background task for the home widgets
20
- class HomeWidgetsManager implements OnStartService {
21
- @override
22
- Future<void> init() async {
23
- if (kIsWeb) return;
24
- try {
25
- await HomeWidget.setAppGroupId(appGroupId);
26
-
27
- final status = await BackgroundFetch.configure(
28
- BackgroundFetchConfig(
29
- minimumFetchInterval: 15,
30
- stopOnTerminate: false,
31
- enableHeadless: true,
32
- requiresBatteryNotLow: false,
33
- requiresCharging: false,
34
- requiresStorageNotLow: false,
35
- requiresDeviceIdle: false,
36
- requiredNetworkType: NetworkType.ANY,
37
- ),
38
- (String taskId) async {
39
- final res = await homeWidgetCallbackDispatcher();
40
- BackgroundFetch.finish(taskId);
41
- return res;
42
- },
43
- );
44
- Logger().i('🔄 - Home widgets manager initialized with status: $status');
45
- await BackgroundFetch.start();
46
- } catch (e, s) {
47
- Logger().e('⚠️ Could not initialize home widgets: $e $s');
48
- }
49
- }
50
- }
51
-
52
- abstract class UpdatableHomeWidget {
53
- Future<void> update();
54
- }
@@ -1,32 +0,0 @@
1
- import 'package:flutter/material.dart';
2
- import 'package:flutter_riverpod/flutter_riverpod.dart';
3
- import 'package:kasy_kit/core/home_widgets/home_widget_mywidget_service.dart';
4
- import 'package:kasy_kit/core/theme/theme.dart';
5
- import 'package:kasy_kit/i18n/translations.g.dart';
6
- import 'package:kasy_kit/features/settings/ui/widgets/admin_card.dart';
7
-
8
- class AdminHomeWidgets extends ConsumerWidget {
9
- const AdminHomeWidgets({super.key});
10
-
11
- @override
12
- Widget build(BuildContext context, WidgetRef ref) {
13
- return Scaffold(
14
- appBar: AppBar(title: Text(t.settings.admin.home_widgets_title)),
15
- body: Padding(
16
- padding: const EdgeInsets.all(KasySpacing.lg),
17
- child: Column(
18
- crossAxisAlignment: CrossAxisAlignment.stretch,
19
- children: [
20
- AdminPanelCard(
21
- title: t.settings.admin.update_mywidget_title,
22
- description: t.settings.admin.update_mywidget_desc,
23
- onTap: () =>
24
- ref.watch(myWidgetHomeWidgetProvider.notifier).update(),
25
- ),
26
- const SizedBox(height: KasySpacing.md),
27
- ],
28
- ),
29
- ),
30
- );
31
- }
32
- }