kasy-cli 1.4.2 → 1.5.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/lib/commands/new.js +9 -2
- package/lib/commands/update.js +61 -4
- package/lib/scaffold/CHANGELOG.json +5 -5
- package/lib/scaffold/catalog.js +49 -0
- package/lib/scaffold/features/widget/lib/core/home_widgets/home_widget_mywidget_service.dart +61 -20
- package/lib/utils/i18n.js +12 -0
- package/package.json +1 -1
- package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MyWidget.kt +48 -12
- package/templates/firebase/home_widget_config.json +9 -5
- package/templates/firebase/ios/HomeWidgetExtension/MyWidget.swift +69 -41
- package/templates/firebase/lib/core/home_widgets/home_widget_mywidget_service.dart +60 -19
package/lib/commands/new.js
CHANGED
|
@@ -191,9 +191,16 @@ async function promptOrganizationIfNeeded(tr, onCancel) {
|
|
|
191
191
|
|
|
192
192
|
function printBanner(tr) {
|
|
193
193
|
const bar = kleur.gray('─────────────────────────────────────────────────');
|
|
194
|
-
const
|
|
194
|
+
const logo = [
|
|
195
|
+
' ╦╔═ ╔═╗ ╔═╗ ╦ ╦',
|
|
196
|
+
' ╠╩╗ ╠═╣ ╚═╗ ╚╦╝',
|
|
197
|
+
' ╩ ╩ ╩ ╩ ╚═╝ ╩ ',
|
|
198
|
+
]
|
|
199
|
+
.map((line) => gradient(['#a78bfa', '#60a5fa'])(line))
|
|
200
|
+
.join('\n');
|
|
195
201
|
console.log(`\n${bar}\n`);
|
|
196
|
-
console.log(
|
|
202
|
+
console.log(logo);
|
|
203
|
+
console.log('');
|
|
197
204
|
console.log(` ${kleur.dim(tr('new.subtitle2'))}`);
|
|
198
205
|
console.log(`\n${bar}\n`);
|
|
199
206
|
}
|
package/lib/commands/update.js
CHANGED
|
@@ -12,6 +12,7 @@ const { createTranslator, detectDefaultLanguage } = require('../utils/i18n');
|
|
|
12
12
|
const {
|
|
13
13
|
AVAILABLE_FEATURES,
|
|
14
14
|
BASE_COMPONENT_FILES,
|
|
15
|
+
CORE_FILES,
|
|
15
16
|
CORE_SOURCE_DIR,
|
|
16
17
|
FEATURES_PATCH_DIR,
|
|
17
18
|
normalizeFeature,
|
|
@@ -29,6 +30,7 @@ const NEEDS_BUILD_RUNNER = [
|
|
|
29
30
|
'revenuecat', 'analytics', 'sentry', 'onboarding', 'llm_chat', 'feedback',
|
|
30
31
|
];
|
|
31
32
|
const COMPONENTS_UPDATE_TARGET = 'components';
|
|
33
|
+
const CORE_UPDATE_TARGET = 'core';
|
|
32
34
|
const IOS_RELEASE_UPDATE_TARGET = 'ios-release';
|
|
33
35
|
|
|
34
36
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
@@ -80,6 +82,10 @@ function normalizeUpdateTarget(value) {
|
|
|
80
82
|
if (componentAliases.has(normalized)) {
|
|
81
83
|
return COMPONENTS_UPDATE_TARGET;
|
|
82
84
|
}
|
|
85
|
+
const coreAliases = new Set(['core', 'core_files', 'corefiles']);
|
|
86
|
+
if (coreAliases.has(normalized)) {
|
|
87
|
+
return CORE_UPDATE_TARGET;
|
|
88
|
+
}
|
|
83
89
|
const iosAliases = new Set(['ios_release', 'iosrelease']);
|
|
84
90
|
if (iosAliases.has(normalized) || normalized === 'ios-release') {
|
|
85
91
|
return IOS_RELEASE_UPDATE_TARGET;
|
|
@@ -87,9 +93,9 @@ function normalizeUpdateTarget(value) {
|
|
|
87
93
|
return null;
|
|
88
94
|
}
|
|
89
95
|
|
|
90
|
-
async function
|
|
96
|
+
async function applyFileList(fileList, projectDir) {
|
|
91
97
|
let filesApplied = 0;
|
|
92
|
-
for (const relativePath of
|
|
98
|
+
for (const relativePath of fileList) {
|
|
93
99
|
const sourcePath = path.join(CORE_SOURCE_DIR, relativePath);
|
|
94
100
|
if (!(await fs.pathExists(sourcePath))) {
|
|
95
101
|
continue;
|
|
@@ -102,6 +108,14 @@ async function applyBaseComponents(projectDir) {
|
|
|
102
108
|
return filesApplied;
|
|
103
109
|
}
|
|
104
110
|
|
|
111
|
+
function applyBaseComponents(projectDir) {
|
|
112
|
+
return applyFileList(BASE_COMPONENT_FILES, projectDir);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function applyCoreFiles(projectDir) {
|
|
116
|
+
return applyFileList(CORE_FILES, projectDir);
|
|
117
|
+
}
|
|
118
|
+
|
|
105
119
|
/** Same detection logic used by add.js and remove.js. */
|
|
106
120
|
async function getActiveModules(kitSetup, projectDir) {
|
|
107
121
|
const modules = [];
|
|
@@ -203,6 +217,49 @@ async function runUpdate(module, options = {}) {
|
|
|
203
217
|
return;
|
|
204
218
|
}
|
|
205
219
|
|
|
220
|
+
if (normalizedTarget === CORE_UPDATE_TARGET) {
|
|
221
|
+
if (!options.yes) {
|
|
222
|
+
console.log(kleur.yellow(`\n ⚠ ${t('update.warn.commitComponents')}\n`));
|
|
223
|
+
const { confirmed } = await prompts(
|
|
224
|
+
{ type: 'confirm', name: 'confirmed', message: t('update.confirmCore'), initial: false },
|
|
225
|
+
{ onCancel: () => { throw new Error(t('update.cancelled')); } }
|
|
226
|
+
);
|
|
227
|
+
if (!confirmed) {
|
|
228
|
+
console.log(kleur.dim(`\n${t('update.cancelled')}\n`));
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
console.log('');
|
|
234
|
+
const spinner = ora(t('update.applyingCore')).start();
|
|
235
|
+
try {
|
|
236
|
+
const filesApplied = await applyCoreFiles(projectDir);
|
|
237
|
+
if (filesApplied === 0) {
|
|
238
|
+
spinner.warn(t('update.noComponentFiles'));
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
spinner.succeed(t('update.appliedCore', { count: filesApplied }));
|
|
242
|
+
} catch (err) {
|
|
243
|
+
spinner.fail(t('update.applyComponentsFailed'));
|
|
244
|
+
throw err;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
{
|
|
248
|
+
const spinnerPubGet = ora(t('update.pubGet')).start();
|
|
249
|
+
try {
|
|
250
|
+
await execAsync('flutter pub get', { cwd: projectDir, timeout: 300_000 });
|
|
251
|
+
spinnerPubGet.succeed(t('update.pubGetDone'));
|
|
252
|
+
} catch {
|
|
253
|
+
spinnerPubGet.warn(t('update.pubGetFailed'));
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
kitSetup.cliVersion = currentVersion;
|
|
258
|
+
await fs.outputFile(kitSetupPath, JSON.stringify(kitSetup, null, 2) + '\n', 'utf8');
|
|
259
|
+
console.log(kleur.green(`\n✓ ${t('update.coreSuccess')}\n`));
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
206
263
|
if (normalizedTarget === IOS_RELEASE_UPDATE_TARGET) {
|
|
207
264
|
const patchDir = path.join(FEATURES_PATCH_DIR, 'ios-release');
|
|
208
265
|
if (!(await fs.pathExists(patchDir))) {
|
|
@@ -249,7 +306,7 @@ async function runUpdate(module, options = {}) {
|
|
|
249
306
|
throw new Error(
|
|
250
307
|
t('update.error.unknownTarget', {
|
|
251
308
|
module,
|
|
252
|
-
list: [...AVAILABLE_FEATURES, COMPONENTS_UPDATE_TARGET, IOS_RELEASE_UPDATE_TARGET].join(', '),
|
|
309
|
+
list: [...AVAILABLE_FEATURES, COMPONENTS_UPDATE_TARGET, CORE_UPDATE_TARGET, IOS_RELEASE_UPDATE_TARGET].join(', '),
|
|
253
310
|
})
|
|
254
311
|
);
|
|
255
312
|
}
|
|
@@ -335,7 +392,7 @@ async function runUpdate(module, options = {}) {
|
|
|
335
392
|
|
|
336
393
|
// ── Mode B: show status ──────────────────────────────────────────────────────
|
|
337
394
|
const alreadyUpToDate = projectVersion && !isNewer(currentVersion, projectVersion);
|
|
338
|
-
const modulesForChangelog = [...new Set([...activeModules, COMPONENTS_UPDATE_TARGET])];
|
|
395
|
+
const modulesForChangelog = [...new Set([...activeModules, COMPONENTS_UPDATE_TARGET, CORE_UPDATE_TARGET])];
|
|
339
396
|
const changes = getChangesSince(changelog, projectVersion, modulesForChangelog, options.language);
|
|
340
397
|
|
|
341
398
|
// Modules in this project that have patch dirs (can be re-applied)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
|
-
"1.
|
|
2
|
+
"1.5.0": {
|
|
3
3
|
"modules": {
|
|
4
|
-
"
|
|
5
|
-
"pt": "
|
|
6
|
-
"en": "
|
|
7
|
-
"es": "
|
|
4
|
+
"widget": {
|
|
5
|
+
"pt": "Widget redesenhado: saudação por horário no idioma do usuário (pt/en/es), nome real e status do plano",
|
|
6
|
+
"en": "Redesigned widget: time-of-day greeting in the user's language (pt/en/es), real name and plan status",
|
|
7
|
+
"es": "Widget rediseñado: saludo según el horario en el idioma del usuario (pt/en/es), nombre real y estado del plan"
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
10
|
}
|
package/lib/scaffold/catalog.js
CHANGED
|
@@ -74,6 +74,54 @@ const DEFAULT_FEATURES = [...AVAILABLE_FEATURES];
|
|
|
74
74
|
* shared widgets); keep this list in sync with `Firebase/lib/components/` and
|
|
75
75
|
* the home component catalog.
|
|
76
76
|
*/
|
|
77
|
+
const CORE_FILES = [
|
|
78
|
+
// Dev tools
|
|
79
|
+
'lib/core/web_device_preview/web_device_preview.dart',
|
|
80
|
+
'lib/core/dev_inspector/dev_inspector.dart',
|
|
81
|
+
'lib/core/dev_inspector/dev_inspector_highlight.dart',
|
|
82
|
+
'lib/core/dev_inspector/dev_inspector_info.dart',
|
|
83
|
+
'lib/core/dev_inspector/dev_inspector_panel.dart',
|
|
84
|
+
'lib/core/dev_inspector/dev_inspector_service.dart',
|
|
85
|
+
'lib/core/keyboard_fix/keyboard_flicker_fix.dart',
|
|
86
|
+
// Animations
|
|
87
|
+
'lib/core/animations/bottomfade_anim.dart',
|
|
88
|
+
'lib/core/animations/movefade_anim.dart',
|
|
89
|
+
'lib/core/animations/slideright_anim.dart',
|
|
90
|
+
// Core widgets (pure utilities, never user-modified)
|
|
91
|
+
'lib/core/widgets/debouncer.dart',
|
|
92
|
+
'lib/core/widgets/kasy_hover.dart',
|
|
93
|
+
'lib/core/widgets/kasy_pressable.dart',
|
|
94
|
+
'lib/core/widgets/kasy_scroll_behavior.dart',
|
|
95
|
+
'lib/core/widgets/keyboard_visibility.dart',
|
|
96
|
+
'lib/core/widgets/page_background.dart',
|
|
97
|
+
'lib/core/widgets/page_not_found.dart',
|
|
98
|
+
'lib/core/widgets/responsive_layout.dart',
|
|
99
|
+
'lib/core/widgets/update_bottom_sheet.dart',
|
|
100
|
+
// Page transitions (pure utilities)
|
|
101
|
+
'lib/core/navigation/kasy_fade_page_transitions_builder.dart',
|
|
102
|
+
'lib/core/navigation/kasy_material_page_route.dart',
|
|
103
|
+
'lib/core/navigation/kasy_page_transition.dart',
|
|
104
|
+
'lib/core/navigation/kasy_route_transition.dart',
|
|
105
|
+
'lib/core/navigation/kasy_transition_kind.dart',
|
|
106
|
+
// Utilities
|
|
107
|
+
'lib/core/toast/toast_service.dart',
|
|
108
|
+
'lib/core/ui/app_dialog.dart',
|
|
109
|
+
'lib/core/icons/kasy_icons.dart',
|
|
110
|
+
'lib/core/haptics/kasy_haptics.dart',
|
|
111
|
+
'lib/core/haptics/haptic_feedback_notifier.dart',
|
|
112
|
+
// Theme (colors.dart and shadows.dart are in BASE_COMPONENT_FILES)
|
|
113
|
+
'lib/core/theme/spacing.dart',
|
|
114
|
+
'lib/core/theme/texts.dart',
|
|
115
|
+
'lib/core/theme/radius.dart',
|
|
116
|
+
'lib/core/theme/theme.dart',
|
|
117
|
+
'lib/core/theme/theme_data/theme_data.dart',
|
|
118
|
+
'lib/core/theme/theme_data/theme_data_factory.dart',
|
|
119
|
+
'lib/core/theme/extensions/theme_extension.dart',
|
|
120
|
+
'lib/core/theme/providers/kasy_theme.dart',
|
|
121
|
+
'lib/core/theme/providers/theme_provider.dart',
|
|
122
|
+
'lib/core/theme/universal_theme.dart',
|
|
123
|
+
];
|
|
124
|
+
|
|
77
125
|
const BASE_COMPONENT_FILES = [
|
|
78
126
|
'lib/core/theme/colors.dart',
|
|
79
127
|
'lib/core/theme/shadows.dart',
|
|
@@ -170,6 +218,7 @@ module.exports = {
|
|
|
170
218
|
AVAILABLE_FEATURES,
|
|
171
219
|
DEFAULT_FEATURES,
|
|
172
220
|
BASE_COMPONENT_FILES,
|
|
221
|
+
CORE_FILES,
|
|
173
222
|
normalizeBackend,
|
|
174
223
|
normalizeFeature,
|
|
175
224
|
parseFeatureList,
|
package/lib/scaffold/features/widget/lib/core/home_widgets/home_widget_mywidget_service.dart
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import 'dart:io';
|
|
2
|
+
|
|
1
3
|
import 'package:home_widget/home_widget.dart';
|
|
2
|
-
import 'package:
|
|
4
|
+
import 'package:kasy_kit/core/data/models/user.dart';
|
|
3
5
|
import 'package:kasy_kit/core/home_widgets/home_widget_service.dart';
|
|
4
6
|
import 'package:kasy_kit/core/states/user_state_notifier.dart';
|
|
7
|
+
import 'package:logger/logger.dart';
|
|
5
8
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
6
9
|
|
|
7
10
|
part 'home_widget_mywidget_service.g.dart';
|
|
@@ -18,40 +21,78 @@ class MyWidgetHomeWidget extends _$MyWidgetHomeWidget
|
|
|
18
21
|
@override
|
|
19
22
|
Future<void> update() {
|
|
20
23
|
Logger().i('🔄 Updating MyWidget Home Widget');
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
final
|
|
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
|
+
};
|
|
24
39
|
|
|
25
|
-
return updateWidget({
|
|
40
|
+
return updateWidget({
|
|
41
|
+
'greeting': _greeting(),
|
|
42
|
+
'name': name,
|
|
43
|
+
'isPro': isPro.toString(),
|
|
44
|
+
});
|
|
26
45
|
}
|
|
27
46
|
|
|
28
|
-
/// Update widget data
|
|
29
|
-
///
|
|
30
|
-
/// This will save data that the widget can read
|
|
31
47
|
Future<void> updateWidget(Map<String, String> data) async {
|
|
32
|
-
await HomeWidget.saveWidgetData<String>(
|
|
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');
|
|
33
53
|
|
|
34
|
-
await HomeWidget.saveWidgetData<String>('counter', data['counter'] ?? '0');
|
|
35
|
-
|
|
36
|
-
// Trigger widget update
|
|
37
54
|
await HomeWidget.updateWidget(
|
|
38
55
|
name: _androidWidgetName,
|
|
39
56
|
iOSName: _iosWidgetName,
|
|
40
57
|
);
|
|
41
58
|
}
|
|
42
59
|
|
|
43
|
-
/// Get current widget data
|
|
44
60
|
Future<Map<String, dynamic>> getWidgetData() async {
|
|
45
61
|
return {
|
|
46
|
-
'
|
|
47
|
-
'
|
|
48
|
-
defaultValue: '
|
|
62
|
+
'greeting': await HomeWidget.getWidgetData<String>(
|
|
63
|
+
'greeting',
|
|
64
|
+
defaultValue: 'Good morning',
|
|
49
65
|
),
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
'
|
|
53
|
-
defaultValue: '0',
|
|
66
|
+
'name': await HomeWidget.getWidgetData<String>(
|
|
67
|
+
'name',
|
|
68
|
+
defaultValue: 'there',
|
|
54
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',
|
|
55
96
|
};
|
|
56
97
|
}
|
|
57
98
|
}
|
package/lib/utils/i18n.js
CHANGED
|
@@ -698,11 +698,14 @@ const MESSAGES = {
|
|
|
698
698
|
'update.warn.commitComponents': 'This will overwrite base component files. Make sure you have committed your changes first.',
|
|
699
699
|
'update.confirm': 'Overwrite feature "{module}" files with the latest version?',
|
|
700
700
|
'update.confirmComponents': 'Overwrite base component files with the latest version?',
|
|
701
|
+
'update.confirmCore': 'Overwrite core files (animations, widgets, theme, dev tools) with the latest version?',
|
|
701
702
|
'update.cancelled': 'Cancelled.',
|
|
702
703
|
'update.applying': 'Applying update for feature: {module}',
|
|
703
704
|
'update.applyingComponents': 'Applying update for base components...',
|
|
705
|
+
'update.applyingCore': 'Applying update for core files...',
|
|
704
706
|
'update.applied': 'Feature {module} updated',
|
|
705
707
|
'update.appliedComponents': '{count} base component files updated',
|
|
708
|
+
'update.appliedCore': '{count} core files updated',
|
|
706
709
|
'update.applyFailed': 'Failed to apply update for feature {module}',
|
|
707
710
|
'update.applyComponentsFailed': 'Failed to apply update for base components',
|
|
708
711
|
'update.noPatch': 'Feature "{module}" has no files to update (configuration-only feature).',
|
|
@@ -715,6 +718,7 @@ const MESSAGES = {
|
|
|
715
718
|
'update.buildRunnerFailed': 'build_runner failed — run it manually',
|
|
716
719
|
'update.success': 'Feature "{module}" updated successfully.',
|
|
717
720
|
'update.componentsSuccess': 'Base components updated successfully.',
|
|
721
|
+
'update.coreSuccess': 'Core files updated successfully.',
|
|
718
722
|
},
|
|
719
723
|
pt: {
|
|
720
724
|
'cli.tagline': 'Crie apps móveis sem dor de configuração',
|
|
@@ -1409,11 +1413,14 @@ const MESSAGES = {
|
|
|
1409
1413
|
'update.warn.commitComponents': 'Isso vai sobrescrever arquivos dos componentes base. Faca commit de tudo antes de continuar.',
|
|
1410
1414
|
'update.confirm': 'Sobrescrever arquivos da feature "{module}" com a versao mais recente?',
|
|
1411
1415
|
'update.confirmComponents': 'Sobrescrever arquivos dos componentes base com a versao mais recente?',
|
|
1416
|
+
'update.confirmCore': 'Sobrescrever arquivos do core (animacoes, widgets, tema, ferramentas de dev) com a versao mais recente?',
|
|
1412
1417
|
'update.cancelled': 'Cancelado.',
|
|
1413
1418
|
'update.applying': 'Aplicando atualizacao da feature: {module}',
|
|
1414
1419
|
'update.applyingComponents': 'Aplicando atualizacao dos componentes base...',
|
|
1420
|
+
'update.applyingCore': 'Aplicando atualizacao dos arquivos de core...',
|
|
1415
1421
|
'update.applied': 'Feature {module} atualizada',
|
|
1416
1422
|
'update.appliedComponents': '{count} arquivos de componentes base atualizados',
|
|
1423
|
+
'update.appliedCore': '{count} arquivos de core atualizados',
|
|
1417
1424
|
'update.applyFailed': 'Falha ao aplicar atualizacao da feature {module}',
|
|
1418
1425
|
'update.applyComponentsFailed': 'Falha ao aplicar atualizacao dos componentes base',
|
|
1419
1426
|
'update.noPatch': 'Feature "{module}" nao tem arquivos para atualizar (feature so de configuracao).',
|
|
@@ -1426,6 +1433,7 @@ const MESSAGES = {
|
|
|
1426
1433
|
'update.buildRunnerFailed': 'build_runner falhou — execute manualmente',
|
|
1427
1434
|
'update.success': 'Feature "{module}" atualizada com sucesso.',
|
|
1428
1435
|
'update.componentsSuccess': 'Componentes base atualizados com sucesso.',
|
|
1436
|
+
'update.coreSuccess': 'Arquivos de core atualizados com sucesso.',
|
|
1429
1437
|
},
|
|
1430
1438
|
es: {
|
|
1431
1439
|
'cli.tagline': 'Crea apps móviles sin dolor de configuración',
|
|
@@ -2120,11 +2128,14 @@ const MESSAGES = {
|
|
|
2120
2128
|
'update.warn.commitComponents': 'Esto sobreescribira archivos de los componentes base. Asegurate de haber hecho commit antes.',
|
|
2121
2129
|
'update.confirm': 'Sobreescribir archivos de la feature "{module}" con la version mas reciente?',
|
|
2122
2130
|
'update.confirmComponents': 'Sobreescribir archivos de los componentes base con la version mas reciente?',
|
|
2131
|
+
'update.confirmCore': 'Sobreescribir archivos de core (animaciones, widgets, tema, herramientas dev) con la version mas reciente?',
|
|
2123
2132
|
'update.cancelled': 'Cancelado.',
|
|
2124
2133
|
'update.applying': 'Aplicando actualizacion de la feature: {module}',
|
|
2125
2134
|
'update.applyingComponents': 'Aplicando actualizacion de componentes base...',
|
|
2135
|
+
'update.applyingCore': 'Aplicando actualizacion de archivos de core...',
|
|
2126
2136
|
'update.applied': 'Feature {module} actualizada',
|
|
2127
2137
|
'update.appliedComponents': '{count} archivos de componentes base actualizados',
|
|
2138
|
+
'update.appliedCore': '{count} archivos de core actualizados',
|
|
2128
2139
|
'update.applyFailed': 'Error al aplicar actualizacion de la feature {module}',
|
|
2129
2140
|
'update.applyComponentsFailed': 'Error al aplicar actualizacion de componentes base',
|
|
2130
2141
|
'update.noPatch': 'La feature "{module}" no tiene archivos para actualizar (feature solo de configuracion).',
|
|
@@ -2137,6 +2148,7 @@ const MESSAGES = {
|
|
|
2137
2148
|
'update.buildRunnerFailed': 'build_runner fallo — ejecutalo manualmente',
|
|
2138
2149
|
'update.success': 'Feature "{module}" actualizada exitosamente.',
|
|
2139
2150
|
'update.componentsSuccess': 'Componentes base actualizados exitosamente.',
|
|
2151
|
+
'update.coreSuccess': 'Archivos de core actualizados exitosamente.',
|
|
2140
2152
|
}
|
|
2141
2153
|
};
|
|
2142
2154
|
|
package/package.json
CHANGED
|
@@ -1,22 +1,29 @@
|
|
|
1
1
|
package com.aicrus.firebase.kit
|
|
2
2
|
|
|
3
|
+
import android.content.Context
|
|
3
4
|
import androidx.compose.runtime.Composable
|
|
4
5
|
import androidx.compose.ui.graphics.Color
|
|
5
6
|
import androidx.compose.ui.unit.dp
|
|
7
|
+
import androidx.compose.ui.unit.sp
|
|
6
8
|
import androidx.glance.GlanceId
|
|
7
9
|
import androidx.glance.GlanceModifier
|
|
8
10
|
import androidx.glance.appwidget.GlanceAppWidget
|
|
9
11
|
import androidx.glance.appwidget.provideContent
|
|
10
12
|
import androidx.glance.background
|
|
13
|
+
import androidx.glance.currentState
|
|
14
|
+
import androidx.glance.layout.Alignment
|
|
11
15
|
import androidx.glance.layout.Box
|
|
12
16
|
import androidx.glance.layout.Column
|
|
17
|
+
import androidx.glance.layout.Spacer
|
|
18
|
+
import androidx.glance.layout.fillMaxSize
|
|
13
19
|
import androidx.glance.layout.padding
|
|
14
20
|
import androidx.glance.state.GlanceStateDefinition
|
|
21
|
+
import androidx.glance.text.FontWeight
|
|
15
22
|
import androidx.glance.text.Text
|
|
23
|
+
import androidx.glance.text.TextStyle
|
|
24
|
+
import androidx.glance.unit.ColorProvider
|
|
16
25
|
import es.antonborri.home_widget.HomeWidgetGlanceState
|
|
17
26
|
import es.antonborri.home_widget.HomeWidgetGlanceStateDefinition
|
|
18
|
-
import android.content.Context
|
|
19
|
-
import androidx.glance.currentState
|
|
20
27
|
|
|
21
28
|
class MyWidgetWidget : GlanceAppWidget() {
|
|
22
29
|
|
|
@@ -32,18 +39,47 @@ class MyWidgetWidget : GlanceAppWidget() {
|
|
|
32
39
|
@Composable
|
|
33
40
|
private fun GlanceContent(context: Context, currentState: HomeWidgetGlanceState) {
|
|
34
41
|
val prefs = currentState.preferences
|
|
42
|
+
val greeting = prefs.getString("greeting", "Good morning") ?: "Good morning"
|
|
43
|
+
val name = prefs.getString("name", "there") ?: "there"
|
|
44
|
+
val isPro = prefs.getString("isPro", "false") == "true"
|
|
35
45
|
|
|
36
|
-
val
|
|
37
|
-
|
|
38
|
-
val
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
Column {
|
|
42
|
-
|
|
43
|
-
Text(text = title ?: "--", modifier = GlanceModifier.padding(bottom = 4.dp))
|
|
44
|
-
|
|
45
|
-
Text(text = counter ?: "--")
|
|
46
|
+
val bgColor = Color(red = 0.08f, green = 0.03f, blue = 0.16f)
|
|
47
|
+
val white = Color.White
|
|
48
|
+
val whiteSubtle = Color(red = 1f, green = 1f, blue = 1f, alpha = 0.55f)
|
|
49
|
+
val gold = Color(red = 1f, green = 0.84f, blue = 0f)
|
|
50
|
+
val whiteVerySubtle = Color(red = 1f, green = 1f, blue = 1f, alpha = 0.40f)
|
|
46
51
|
|
|
52
|
+
Box(
|
|
53
|
+
modifier = GlanceModifier.fillMaxSize().background(bgColor).padding(16.dp),
|
|
54
|
+
contentAlignment = Alignment.TopStart,
|
|
55
|
+
) {
|
|
56
|
+
Column(modifier = GlanceModifier.fillMaxSize()) {
|
|
57
|
+
Text(
|
|
58
|
+
text = greeting,
|
|
59
|
+
style = TextStyle(
|
|
60
|
+
color = ColorProvider(whiteSubtle),
|
|
61
|
+
fontSize = 11.sp,
|
|
62
|
+
fontWeight = FontWeight.Medium,
|
|
63
|
+
),
|
|
64
|
+
)
|
|
65
|
+
Text(
|
|
66
|
+
text = "Hi, $name!",
|
|
67
|
+
style = TextStyle(
|
|
68
|
+
color = ColorProvider(white),
|
|
69
|
+
fontSize = 22.sp,
|
|
70
|
+
fontWeight = FontWeight.Bold,
|
|
71
|
+
),
|
|
72
|
+
modifier = GlanceModifier.padding(top = 4.dp),
|
|
73
|
+
)
|
|
74
|
+
Spacer(modifier = GlanceModifier.defaultWeight())
|
|
75
|
+
Text(
|
|
76
|
+
text = if (isPro) "⭐ PRO" else "Free plan",
|
|
77
|
+
style = TextStyle(
|
|
78
|
+
color = ColorProvider(if (isPro) gold else whiteVerySubtle),
|
|
79
|
+
fontSize = 11.sp,
|
|
80
|
+
fontWeight = if (isPro) FontWeight.Bold else FontWeight.Medium,
|
|
81
|
+
),
|
|
82
|
+
)
|
|
47
83
|
}
|
|
48
84
|
}
|
|
49
85
|
}
|
|
@@ -4,13 +4,17 @@
|
|
|
4
4
|
"name": "MyWidget",
|
|
5
5
|
"description": "Sample home widget generated by kasy",
|
|
6
6
|
"metadata": {
|
|
7
|
-
"
|
|
7
|
+
"greeting": {
|
|
8
8
|
"type": "string",
|
|
9
|
-
"defaultValue": "
|
|
9
|
+
"defaultValue": "Good morning"
|
|
10
10
|
},
|
|
11
|
-
"
|
|
12
|
-
"type": "
|
|
13
|
-
"defaultValue": "
|
|
11
|
+
"name": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"defaultValue": "there"
|
|
14
|
+
},
|
|
15
|
+
"isPro": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"defaultValue": "false"
|
|
14
18
|
}
|
|
15
19
|
},
|
|
16
20
|
"isLockScreenWidget": false,
|
|
@@ -3,63 +3,78 @@ import SwiftUI
|
|
|
3
3
|
|
|
4
4
|
struct MyWidgetProvider: TimelineProvider {
|
|
5
5
|
func placeholder(in context: Context) -> MyWidgetEntry {
|
|
6
|
-
MyWidgetEntry(
|
|
7
|
-
date: Date(),
|
|
8
|
-
title: "Hello",
|
|
9
|
-
counter: "0"
|
|
10
|
-
)
|
|
6
|
+
MyWidgetEntry(date: Date(), greeting: "Good morning", name: "there", isPro: false)
|
|
11
7
|
}
|
|
12
8
|
|
|
13
|
-
func getSnapshot(in context: Context, completion: @escaping (MyWidgetEntry) ->
|
|
9
|
+
func getSnapshot(in context: Context, completion: @escaping (MyWidgetEntry) -> Void) {
|
|
14
10
|
let prefs = UserDefaults(suiteName: "group.com.aicrus.firebase.kit")
|
|
15
|
-
|
|
16
|
-
let title = prefs?.string(forKey: "title") ?? "Hello"
|
|
17
|
-
|
|
18
|
-
let counter = prefs?.string(forKey: "counter") ?? "0"
|
|
19
|
-
|
|
20
|
-
let entry = MyWidgetEntry(
|
|
11
|
+
completion(MyWidgetEntry(
|
|
21
12
|
date: Date(),
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
13
|
+
greeting: prefs?.string(forKey: "greeting") ?? "Good morning",
|
|
14
|
+
name: prefs?.string(forKey: "name") ?? "there",
|
|
15
|
+
isPro: prefs?.string(forKey: "isPro") == "true"
|
|
16
|
+
))
|
|
26
17
|
}
|
|
27
18
|
|
|
28
|
-
func getTimeline(in context: Context, completion: @escaping (Timeline<MyWidgetEntry>) ->
|
|
29
|
-
getSnapshot(in: context) {
|
|
30
|
-
|
|
31
|
-
completion(timeline)
|
|
19
|
+
func getTimeline(in context: Context, completion: @escaping (Timeline<MyWidgetEntry>) -> Void) {
|
|
20
|
+
getSnapshot(in: context) { entry in
|
|
21
|
+
completion(Timeline(entries: [entry], policy: .atEnd))
|
|
32
22
|
}
|
|
33
23
|
}
|
|
34
24
|
}
|
|
35
25
|
|
|
36
26
|
struct MyWidgetEntry: TimelineEntry {
|
|
37
27
|
let date: Date
|
|
38
|
-
|
|
39
|
-
let
|
|
40
|
-
|
|
41
|
-
let counter: String
|
|
42
|
-
|
|
28
|
+
let greeting: String
|
|
29
|
+
let name: String
|
|
30
|
+
let isPro: Bool
|
|
43
31
|
}
|
|
44
32
|
|
|
45
33
|
struct MyWidgetWidgetView: View {
|
|
46
34
|
var entry: MyWidgetProvider.Entry
|
|
35
|
+
@Environment(\.widgetFamily) var family
|
|
36
|
+
|
|
37
|
+
private var titleSize: CGFloat {
|
|
38
|
+
switch family {
|
|
39
|
+
case .systemSmall: return 24
|
|
40
|
+
case .systemMedium: return 28
|
|
41
|
+
default: return 34
|
|
42
|
+
}
|
|
43
|
+
}
|
|
47
44
|
|
|
48
45
|
var body: some View {
|
|
49
|
-
VStack(alignment: .leading, spacing:
|
|
50
|
-
Text(entry.
|
|
51
|
-
.font(.system(size:
|
|
52
|
-
.
|
|
53
|
-
.
|
|
54
|
-
.lineLimit(2)
|
|
46
|
+
VStack(alignment: .leading, spacing: 0) {
|
|
47
|
+
Text(entry.greeting)
|
|
48
|
+
.font(.system(size: 11, weight: .medium, design: .rounded))
|
|
49
|
+
.foregroundStyle(.white.opacity(0.55))
|
|
50
|
+
.lineLimit(1)
|
|
55
51
|
|
|
56
|
-
|
|
57
|
-
.font(.system(size: 24, weight: .bold, design: .rounded))
|
|
58
|
-
.foregroundColor(.primary)
|
|
52
|
+
Spacer().frame(height: 6)
|
|
59
53
|
|
|
54
|
+
Text("Hi, \(entry.name)!")
|
|
55
|
+
.font(.system(size: titleSize, weight: .bold, design: .rounded))
|
|
56
|
+
.foregroundStyle(.white)
|
|
57
|
+
.lineLimit(2)
|
|
58
|
+
.minimumScaleFactor(0.75)
|
|
59
|
+
|
|
60
|
+
Spacer()
|
|
61
|
+
|
|
62
|
+
if entry.isPro {
|
|
63
|
+
Label("PRO", systemImage: "star.fill")
|
|
64
|
+
.font(.system(size: 10, weight: .bold, design: .rounded))
|
|
65
|
+
.foregroundStyle(Color(red: 1.0, green: 0.84, blue: 0.0))
|
|
66
|
+
.padding(.horizontal, 8)
|
|
67
|
+
.padding(.vertical, 4)
|
|
68
|
+
.background(Color(red: 1.0, green: 0.84, blue: 0.0).opacity(0.18))
|
|
69
|
+
.clipShape(Capsule())
|
|
70
|
+
} else {
|
|
71
|
+
Text("Free plan")
|
|
72
|
+
.font(.system(size: 10, weight: .medium, design: .rounded))
|
|
73
|
+
.foregroundStyle(.white.opacity(0.4))
|
|
74
|
+
}
|
|
60
75
|
}
|
|
61
|
-
.padding()
|
|
62
76
|
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
|
|
77
|
+
.padding()
|
|
63
78
|
}
|
|
64
79
|
}
|
|
65
80
|
|
|
@@ -69,18 +84,31 @@ struct MyWidgetWidget: Widget {
|
|
|
69
84
|
var body: some WidgetConfiguration {
|
|
70
85
|
StaticConfiguration(kind: kind, provider: MyWidgetProvider()) { entry in
|
|
71
86
|
MyWidgetWidgetView(entry: entry)
|
|
72
|
-
.containerBackground(
|
|
87
|
+
.containerBackground(for: .widget) {
|
|
88
|
+
LinearGradient(
|
|
89
|
+
gradient: Gradient(colors: [
|
|
90
|
+
Color(red: 0.08, green: 0.03, blue: 0.16),
|
|
91
|
+
Color(red: 0.20, green: 0.09, blue: 0.42),
|
|
92
|
+
]),
|
|
93
|
+
startPoint: .topLeading,
|
|
94
|
+
endPoint: .bottomTrailing
|
|
95
|
+
)
|
|
96
|
+
}
|
|
73
97
|
}
|
|
74
98
|
.configurationDisplayName("MyWidget")
|
|
75
99
|
.description("Sample home widget generated by kasy")
|
|
76
|
-
.supportedFamilies([
|
|
77
|
-
.systemSmall,.systemMedium,.systemLarge, // test 2
|
|
78
|
-
])
|
|
100
|
+
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
|
|
79
101
|
}
|
|
80
102
|
}
|
|
81
103
|
|
|
82
|
-
#Preview("
|
|
104
|
+
#Preview("Small", as: .systemSmall) {
|
|
105
|
+
MyWidgetWidget()
|
|
106
|
+
} timeline: {
|
|
107
|
+
MyWidgetEntry(date: .now, greeting: "Good morning", name: "Paulo", isPro: true)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
#Preview("Medium", as: .systemMedium) {
|
|
83
111
|
MyWidgetWidget()
|
|
84
112
|
} timeline: {
|
|
85
|
-
MyWidgetEntry(date: .now,
|
|
113
|
+
MyWidgetEntry(date: .now, greeting: "Good afternoon", name: "Paulo", isPro: false)
|
|
86
114
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import 'dart:io';
|
|
2
|
+
|
|
1
3
|
import 'package:home_widget/home_widget.dart';
|
|
4
|
+
import 'package:kasy_kit/core/data/models/user.dart';
|
|
2
5
|
import 'package:kasy_kit/core/home_widgets/home_widget_service.dart';
|
|
3
6
|
import 'package:kasy_kit/core/states/user_state_notifier.dart';
|
|
4
7
|
import 'package:logger/logger.dart';
|
|
@@ -18,40 +21,78 @@ class MyWidgetHomeWidget extends _$MyWidgetHomeWidget
|
|
|
18
21
|
@override
|
|
19
22
|
Future<void> update() {
|
|
20
23
|
Logger().i('🔄 Updating MyWidget Home Widget');
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
final
|
|
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
|
+
};
|
|
24
32
|
|
|
25
|
-
|
|
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
|
+
});
|
|
26
45
|
}
|
|
27
46
|
|
|
28
|
-
/// Update widget data
|
|
29
|
-
///
|
|
30
|
-
/// This will save data that the widget can read
|
|
31
47
|
Future<void> updateWidget(Map<String, String> data) async {
|
|
32
|
-
await HomeWidget.saveWidgetData<String>(
|
|
33
|
-
|
|
34
|
-
await HomeWidget.saveWidgetData<String>('
|
|
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');
|
|
35
53
|
|
|
36
|
-
// Trigger widget update
|
|
37
54
|
await HomeWidget.updateWidget(
|
|
38
55
|
name: _androidWidgetName,
|
|
39
56
|
iOSName: _iosWidgetName,
|
|
40
57
|
);
|
|
41
58
|
}
|
|
42
59
|
|
|
43
|
-
/// Get current widget data
|
|
44
60
|
Future<Map<String, dynamic>> getWidgetData() async {
|
|
45
61
|
return {
|
|
46
|
-
'
|
|
47
|
-
'
|
|
48
|
-
defaultValue: '
|
|
62
|
+
'greeting': await HomeWidget.getWidgetData<String>(
|
|
63
|
+
'greeting',
|
|
64
|
+
defaultValue: 'Good morning',
|
|
49
65
|
),
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
'
|
|
53
|
-
|
|
66
|
+
'name': await HomeWidget.getWidgetData<String>(
|
|
67
|
+
'name',
|
|
68
|
+
defaultValue: 'there',
|
|
69
|
+
),
|
|
70
|
+
'isPro': await HomeWidget.getWidgetData<String>(
|
|
71
|
+
'isPro',
|
|
72
|
+
defaultValue: 'false',
|
|
54
73
|
),
|
|
55
74
|
};
|
|
56
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
|
+
}
|
|
57
98
|
}
|