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/bin/kasy.js +4 -4
- package/lib/commands/check.js +40 -50
- package/lib/commands/deploy.js +25 -25
- package/lib/scaffold/CHANGELOG.json +9 -0
- package/lib/scaffold/features/README.md +1 -1
- package/lib/utils/i18n.js +427 -256
- package/package.json +3 -2
- package/templates/firebase/lib/core/home_widgets/home_widget_mywidget_service.dart +29 -1
- package/templates/firebase/lib/core/home_widgets/home_widget_service.dart +9 -1
- package/templates/firebase/lib/core/initializer/onstart_widget.dart +7 -1
- package/lib/scaffold/features/widget/lib/core/home_widgets/home_widget_background_task.dart +0 -41
- package/lib/scaffold/features/widget/lib/core/home_widgets/home_widget_mywidget_service.dart +0 -98
- package/lib/scaffold/features/widget/lib/core/home_widgets/home_widget_service.dart +0 -54
- package/lib/scaffold/features/widget/lib/features/settings/ui/components/admin/admin_home_widgets.dart +0 -32
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kasy-cli",
|
|
3
|
-
"version": "1.
|
|
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)
|
|
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
|
-
|
|
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
|
-
}
|
package/lib/scaffold/features/widget/lib/core/home_widgets/home_widget_mywidget_service.dart
DELETED
|
@@ -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
|
-
}
|