kasy-cli 1.4.1 → 1.5.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.
@@ -192,22 +192,17 @@ async function promptOrganizationIfNeeded(tr, onCancel) {
192
192
  function printBanner(tr) {
193
193
  const bar = kleur.gray('─────────────────────────────────────────────────');
194
194
  const logo = [
195
- ' ██╗ ██╗ █████╗ ███████╗██╗ ██╗',
196
- ' ██║ ██╔╝██╔══██╗██╔════╝╚██╗ ██╔╝',
197
- ' █████╔╝ ███████║███████╗ ╚████╔╝ ',
198
- ' ██╔═██╗ ██╔══██║╚════██║ ╚██╔╝ ',
199
- ' ██║ ██╗██║ ██║███████║ ██║ ',
200
- ' ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚═╝ ',
195
+ ' ╦╔═ ╔═╗ ╔═╗ ╦ ╦',
196
+ ' ╠╩╗ ╠═╣ ╚═╗ ╚╦╝',
197
+ ' ╚═╝ ╩ ',
201
198
  ]
202
199
  .map((line) => gradient(['#a78bfa', '#60a5fa'])(line))
203
200
  .join('\n');
204
- const title = kleur.bold().white(` ${tr('new.banner')}`);
205
- console.log(`\n${bar}`);
201
+ console.log(`\n${bar}\n`);
206
202
  console.log(logo);
207
203
  console.log('');
208
- console.log(title);
209
- console.log(kleur.dim(` ${tr('new.subtitle2')}`));
210
- console.log(`${bar}\n`);
204
+ console.log(` ${kleur.dim(tr('new.subtitle2'))}`);
205
+ console.log(`\n${bar}\n`);
211
206
  }
212
207
 
213
208
  function printPrerequisites(tr, backend, firebaseSetupMode = 'existing', checkResults = []) {
@@ -355,33 +355,46 @@ async function runUpdate(module, options = {}) {
355
355
  const fromLabel = projectVersion || '?';
356
356
  console.log(`\n${t('update.status', { from: fromLabel, to: currentVersion })}\n`);
357
357
 
358
- if (!projectVersion) {
359
- console.log(kleur.dim(` ${t('update.noVersion')}\n`));
360
- }
361
-
362
- // Changelog per module
363
- if (Object.keys(changes).length > 0) {
358
+ // Modules/components with actual changelog entries — show what improved + the command
359
+ const modulesWithChanges = Object.keys(changes);
360
+ if (modulesWithChanges.length > 0) {
364
361
  console.log(kleur.bold(t('update.changesTitle')));
365
- for (const [mod, entries] of Object.entries(changes)) {
366
- console.log(kleur.cyan(` ${mod}`));
367
- for (const { version, description } of entries) {
368
- console.log(kleur.dim(` v${version}: ${description}`));
362
+ for (const mod of modulesWithChanges) {
363
+ for (const { description } of changes[mod]) {
364
+ console.log(` ${kleur.cyan('✦')} ${kleur.bold(mod)} ${kleur.dim('→')} ${kleur.cyan(`kasy update ${mod}`)}`);
365
+ console.log(` ${kleur.dim(description)}`);
369
366
  }
370
367
  }
371
368
  console.log('');
372
369
  }
373
370
 
374
- // Instructions
375
- if (patchableModules.length > 0) {
376
- console.log(t('update.howToUpdate'));
377
- for (const m of patchableModules) {
378
- console.log(kleur.cyan(` kasy update ${m}`));
371
+ // Modules without changelog entries that can still be re-applied (advanced / recovery)
372
+ const modulesWithoutChanges = patchableModules.filter((m) => !modulesWithChanges.includes(m));
373
+ const hasComponentChanges = modulesWithChanges.includes(COMPONENTS_UPDATE_TARGET);
374
+
375
+ if (modulesWithChanges.length === 0 && !hasComponentChanges) {
376
+ // Nothing new — show everything available
377
+ if (patchableModules.length > 0) {
378
+ console.log(t('update.howToUpdate'));
379
+ for (const m of patchableModules) {
380
+ console.log(kleur.cyan(` kasy update ${m}`));
381
+ }
382
+ console.log('');
383
+ }
384
+ console.log(t('update.howToUpdateComponents'));
385
+ console.log(kleur.cyan(` kasy update ${COMPONENTS_UPDATE_TARGET}`));
386
+ console.log('');
387
+ } else if (modulesWithoutChanges.length > 0) {
388
+ // Some modules have no new changes — show as secondary info
389
+ console.log(kleur.dim(t('update.reapplyTitle')));
390
+ for (const m of modulesWithoutChanges) {
391
+ console.log(kleur.dim(` kasy update ${m}`));
392
+ }
393
+ if (!hasComponentChanges) {
394
+ console.log(kleur.dim(` kasy update ${COMPONENTS_UPDATE_TARGET}`));
379
395
  }
380
396
  console.log('');
381
397
  }
382
- console.log(t('update.howToUpdateComponents'));
383
- console.log(kleur.cyan(` kasy update ${COMPONENTS_UPDATE_TARGET}`));
384
- console.log('');
385
398
  }
386
399
 
387
400
  module.exports = { runUpdate };
@@ -1,10 +1,10 @@
1
1
  {
2
- "1.4.0": {
2
+ "1.5.0": {
3
3
  "modules": {
4
- "components": {
5
- "pt": "Melhorias no WebDevicePreview: minimizar/maximizar, orientação, locale e screenshot",
6
- "en": "WebDevicePreview improvements: minimize/maximize, orientation, locale and screenshot",
7
- "es": "Mejoras en WebDevicePreview: minimizar/maximizar, orientación, locale y captura de pantalla"
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
  }
@@ -1,7 +1,10 @@
1
+ import 'dart:io';
2
+
1
3
  import 'package:home_widget/home_widget.dart';
2
- import 'package:logger/logger.dart';
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
- // Example: fetch some data from the user state
22
- final userState = ref.read(userStateNotifierProvider);
23
- final userName = userState.user.idOrNull ?? 'Guest';
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({'title': 'Hello, $userName', 'counter': '0'});
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>('title', data['title'] ?? 'Hello');
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
- 'title': await HomeWidget.getWidgetData<String>(
47
- 'title',
48
- defaultValue: 'Hello',
62
+ 'greeting': await HomeWidget.getWidgetData<String>(
63
+ 'greeting',
64
+ defaultValue: 'Good morning',
49
65
  ),
50
-
51
- 'counter': await HomeWidget.getWidgetData<String>(
52
- 'counter',
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
@@ -690,7 +690,8 @@ const MESSAGES = {
690
690
  'update.alreadyUpToDate': 'Project is already up to date (v{version}).',
691
691
  'update.status': 'Project: v{from} → CLI: v{to}',
692
692
  'update.noVersion': 'Project was generated without version tracking. All features can be updated.',
693
- 'update.changesTitle': 'Changes available for your features:',
693
+ 'update.changesTitle': 'Updates available:',
694
+ 'update.reapplyTitle': 'No new changes — can re-apply if needed:',
694
695
  'update.howToUpdate': 'To update a feature:',
695
696
  'update.howToUpdateComponents': 'To update base components:',
696
697
  'update.warn.commit': 'This will overwrite files from feature "{module}". Make sure you have committed your changes first.',
@@ -1400,7 +1401,8 @@ const MESSAGES = {
1400
1401
  'update.alreadyUpToDate': 'Projeto ja esta atualizado (v{version}).',
1401
1402
  'update.status': 'Projeto: v{from} → CLI: v{to}',
1402
1403
  'update.noVersion': 'Projeto foi gerado sem rastreamento de versao. Todas as features podem ser atualizadas.',
1403
- 'update.changesTitle': 'Mudancas disponiveis para suas features:',
1404
+ 'update.changesTitle': 'Atualizacoes disponiveis:',
1405
+ 'update.reapplyTitle': 'Sem mudancas novas — pode reaplicar se precisar:',
1404
1406
  'update.howToUpdate': 'Para atualizar uma feature:',
1405
1407
  'update.howToUpdateComponents': 'Para atualizar componentes base:',
1406
1408
  'update.warn.commit': 'Isso vai sobrescrever os arquivos da feature "{module}". Faca commit de tudo antes de continuar.',
@@ -2110,7 +2112,8 @@ const MESSAGES = {
2110
2112
  'update.alreadyUpToDate': 'El proyecto ya esta actualizado (v{version}).',
2111
2113
  'update.status': 'Proyecto: v{from} → CLI: v{to}',
2112
2114
  'update.noVersion': 'El proyecto fue generado sin seguimiento de version. Todas las features pueden actualizarse.',
2113
- 'update.changesTitle': 'Cambios disponibles para tus features:',
2115
+ 'update.changesTitle': 'Actualizaciones disponibles:',
2116
+ 'update.reapplyTitle': 'Sin cambios nuevos — puedes reaplicar si es necesario:',
2114
2117
  'update.howToUpdate': 'Para actualizar una feature:',
2115
2118
  'update.howToUpdateComponents': 'Para actualizar componentes base:',
2116
2119
  'update.warn.commit': 'Esto sobreescribira los archivos de la feature "{module}". Asegurate de haber hecho commit antes.',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kasy-cli",
3
- "version": "1.4.1",
3
+ "version": "1.5.0",
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"
@@ -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 title = prefs.getString("title", "Hello")
37
-
38
- val counter = prefs.getString("counter", "0")
39
-
40
- Box(modifier = GlanceModifier.background(Color.White).padding(16.dp)) {
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
- "title": {
7
+ "greeting": {
8
8
  "type": "string",
9
- "defaultValue": "Hello"
9
+ "defaultValue": "Good morning"
10
10
  },
11
- "counter": {
12
- "type": "number",
13
- "defaultValue": "0"
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
- title: title,
23
- counter: counter
24
- )
25
- completion(entry)
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) { (entry) in
30
- let timeline = Timeline(entries: [entry], policy: .atEnd)
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 title: String
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: 8) {
50
- Text(entry.title)
51
- .font(.system(size: 16, weight: .semibold, design: .rounded))
52
- .foregroundColor(.primary)
53
- .multilineTextAlignment(.leading)
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
- Text(entry.counter)
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(Color(.systemBackground), for: .widget)
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("MyWidget Widget", as: .systemSmall) {
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, title: "Hello", counter: "0")
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
- // Example: fetch some data from the user state
22
- final userState = ref.read(userStateNotifierProvider);
23
- final userName = userState.user.idOrNull ?? 'Guest';
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
- return updateWidget({'title': 'Hello, $userName', 'counter': '0'});
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>('title', data['title'] ?? 'Hello');
33
-
34
- await HomeWidget.saveWidgetData<String>('counter', data['counter'] ?? '0');
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
- 'title': await HomeWidget.getWidgetData<String>(
47
- 'title',
48
- defaultValue: 'Hello',
62
+ 'greeting': await HomeWidget.getWidgetData<String>(
63
+ 'greeting',
64
+ defaultValue: 'Good morning',
49
65
  ),
50
-
51
- 'counter': await HomeWidget.getWidgetData<String>(
52
- 'counter',
53
- defaultValue: '0',
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
  }