kasy-cli 1.4.2 → 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.
@@ -191,9 +191,16 @@ async function promptOrganizationIfNeeded(tr, onCancel) {
191
191
 
192
192
  function printBanner(tr) {
193
193
  const bar = kleur.gray('─────────────────────────────────────────────────');
194
- const brand = gradient(['#a78bfa', '#60a5fa'])('kasy');
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(` ⚡ ${kleur.bold(brand)}`);
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
  }
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kasy-cli",
3
- "version": "1.4.2",
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
  }