kasy-cli 1.17.0 → 1.18.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.
Files changed (90) hide show
  1. package/bin/kasy.js +15 -2
  2. package/lib/commands/add.js +7 -7
  3. package/lib/commands/configure.js +548 -0
  4. package/lib/commands/deploy.js +4 -4
  5. package/lib/commands/doctor.js +17 -0
  6. package/lib/commands/favicon.js +4 -4
  7. package/lib/commands/icon.js +5 -5
  8. package/lib/commands/new.js +403 -238
  9. package/lib/commands/run.js +1 -1
  10. package/lib/commands/splash.js +5 -5
  11. package/lib/commands/update.js +9 -9
  12. package/lib/scaffold/CHANGELOG.json +14 -0
  13. package/lib/scaffold/backends/firebase/enable-auth-via-cli.js +108 -0
  14. package/lib/scaffold/backends/firebase/setup-from-scratch.js +44 -5
  15. package/lib/scaffold/generate.js +24 -8
  16. package/lib/scaffold/shared/post-build.js +8 -0
  17. package/lib/utils/brand.js +16 -12
  18. package/lib/utils/flutter-run.js +139 -11
  19. package/lib/utils/i18n/messages-en.js +58 -5
  20. package/lib/utils/i18n/messages-es.js +58 -5
  21. package/lib/utils/i18n/messages-pt.js +59 -6
  22. package/lib/utils/ui.js +79 -4
  23. package/package.json +1 -1
  24. package/templates/firebase/README.en.md +1 -1
  25. package/templates/firebase/README.es.md +1 -1
  26. package/templates/firebase/README.md +1 -1
  27. package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MainActivity.kt +0 -15
  28. package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MyWidget.kt +65 -26
  29. package/templates/firebase/android/app/src/main/res/drawable/widget_add_button.xml +2 -2
  30. package/templates/firebase/android/app/src/main/res/drawable/widget_gradient_bg.xml +7 -2
  31. package/templates/firebase/android/app/src/main/res/drawable/widget_gradient_inner.xml +6 -6
  32. package/templates/firebase/android/app/src/main/res/drawable/widget_plan_pill_bg.xml +1 -1
  33. package/templates/firebase/android/app/src/main/res/drawable/widget_preview_image.xml +2 -2
  34. package/templates/firebase/android/app/src/main/res/drawable/widget_pro_pill_bg.xml +1 -1
  35. package/templates/firebase/android/app/src/main/res/drawable-hdpi/android12splash.png +0 -0
  36. package/templates/firebase/android/app/src/main/res/drawable-hdpi/splash.png +0 -0
  37. package/templates/firebase/android/app/src/main/res/drawable-mdpi/android12splash.png +0 -0
  38. package/templates/firebase/android/app/src/main/res/drawable-mdpi/splash.png +0 -0
  39. package/templates/firebase/android/app/src/main/res/drawable-night-hdpi/android12splash.png +0 -0
  40. package/templates/firebase/android/app/src/main/res/drawable-night-hdpi/splash.png +0 -0
  41. package/templates/firebase/android/app/src/main/res/drawable-night-mdpi/android12splash.png +0 -0
  42. package/templates/firebase/android/app/src/main/res/drawable-night-mdpi/splash.png +0 -0
  43. package/templates/firebase/android/app/src/main/res/drawable-night-xhdpi/android12splash.png +0 -0
  44. package/templates/firebase/android/app/src/main/res/drawable-night-xhdpi/splash.png +0 -0
  45. package/templates/firebase/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png +0 -0
  46. package/templates/firebase/android/app/src/main/res/drawable-night-xxhdpi/splash.png +0 -0
  47. package/templates/firebase/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png +0 -0
  48. package/templates/firebase/android/app/src/main/res/drawable-night-xxxhdpi/splash.png +0 -0
  49. package/templates/firebase/android/app/src/main/res/drawable-xhdpi/android12splash.png +0 -0
  50. package/templates/firebase/android/app/src/main/res/drawable-xhdpi/splash.png +0 -0
  51. package/templates/firebase/android/app/src/main/res/drawable-xxhdpi/android12splash.png +0 -0
  52. package/templates/firebase/android/app/src/main/res/drawable-xxhdpi/splash.png +0 -0
  53. package/templates/firebase/android/app/src/main/res/drawable-xxxhdpi/android12splash.png +0 -0
  54. package/templates/firebase/android/app/src/main/res/drawable-xxxhdpi/splash.png +0 -0
  55. package/templates/firebase/android/app/src/main/res/layout/widget_preview.xml +3 -3
  56. package/templates/firebase/android/app/src/main/res/values/colors.xml +32 -0
  57. package/templates/firebase/assets/images/splash_logo_dark.png +0 -0
  58. package/templates/firebase/assets/images/splash_logo_dark_android12.png +0 -0
  59. package/templates/firebase/assets/images/splash_logo_light.png +0 -0
  60. package/templates/firebase/assets/images/splash_logo_light_android12.png +0 -0
  61. package/templates/firebase/ios/HomeWidgetExtension/MyWidget.swift +75 -29
  62. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png +0 -0
  63. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png +0 -0
  64. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png +0 -0
  65. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark.png +0 -0
  66. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png +0 -0
  67. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@3x.png +0 -0
  68. package/templates/firebase/ios/Runner/Base.lproj/LaunchScreen.storyboard +1 -1
  69. package/templates/firebase/lib/components/components.dart +1 -0
  70. package/templates/firebase/lib/components/kasy_avatar.dart +65 -17
  71. package/templates/firebase/lib/components/kasy_avatar_presets.dart +121 -97
  72. package/templates/firebase/lib/components/kasy_button.dart +8 -8
  73. package/templates/firebase/lib/components/kasy_date_picker.dart +834 -0
  74. package/templates/firebase/lib/components/kasy_tabs.dart +145 -61
  75. package/templates/firebase/lib/core/home_widgets/home_widget_mywidget_service.dart +12 -18
  76. package/templates/firebase/lib/features/home/home_components_preview_registry.dart +565 -77
  77. package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +10 -5
  78. package/templates/firebase/lib/i18n/en.i18n.json +2 -1
  79. package/templates/firebase/lib/i18n/es.i18n.json +2 -1
  80. package/templates/firebase/lib/i18n/pt.i18n.json +2 -1
  81. package/templates/firebase/pubspec.yaml +1 -1
  82. package/templates/firebase/web/index.html +9 -0
  83. package/templates/firebase/web/splash/img/dark-1x.png +0 -0
  84. package/templates/firebase/web/splash/img/dark-2x.png +0 -0
  85. package/templates/firebase/web/splash/img/dark-3x.png +0 -0
  86. package/templates/firebase/web/splash/img/dark-4x.png +0 -0
  87. package/templates/firebase/web/splash/img/light-1x.png +0 -0
  88. package/templates/firebase/web/splash/img/light-2x.png +0 -0
  89. package/templates/firebase/web/splash/img/light-3x.png +0 -0
  90. package/templates/firebase/web/splash/img/light-4x.png +0 -0
@@ -1,6 +1,29 @@
1
1
  import WidgetKit
2
2
  import SwiftUI
3
3
 
4
+ // MARK: - Brand
5
+ //
6
+ // Single source of truth for widget colors on iOS. To rebrand the widget,
7
+ // edit ONLY this enum.
8
+ //
9
+ // IMPORTANT: keep these values in sync with the Android counterparts in
10
+ // Firebase/android/app/src/main/res/values/colors.xml.
11
+ enum WidgetBrand {
12
+ // Background gradient.
13
+ static let gradientStart = Color(red: 0.08, green: 0.03, blue: 0.16) // #FF140829
14
+ static let gradientEnd = Color(red: 0.20, green: 0.09, blue: 0.42) // #FF33176B
15
+
16
+ // PRO plan pill (gold) + its soft background.
17
+ static let proGold = Color(red: 1.00, green: 0.84, blue: 0.00) // #FFFFD700
18
+ static let proPillBg = Color(red: 1.00, green: 0.84, blue: 0.00).opacity(0.18)
19
+
20
+ // Free plan pill — low-emphasis translucent white.
21
+ static let freePillBg = Color.white.opacity(0.08)
22
+
23
+ // "+" circular button on medium/large widgets.
24
+ static let addButtonBg = Color.white.opacity(0.18)
25
+ }
26
+
4
27
  struct MyWidgetProvider: TimelineProvider {
5
28
  func placeholder(in context: Context) -> MyWidgetEntry {
6
29
  MyWidgetEntry.defaults()
@@ -24,6 +47,7 @@ struct MyWidgetEntry: TimelineEntry {
24
47
  let planText: String
25
48
  let isPro: Bool
26
49
  let quote: String
50
+ let quoteAuthor: String
27
51
 
28
52
  /// Reads the latest data from the shared app group. If a string was never
29
53
  /// written (first install before the Flutter app pushed data), falls back
@@ -36,6 +60,7 @@ struct MyWidgetEntry: TimelineEntry {
36
60
  let storedPlan = prefs?.string(forKey: "planText") ?? ""
37
61
  let storedIsPro = prefs?.string(forKey: "isPro") == "true"
38
62
  let storedQuote = prefs?.string(forKey: "quote") ?? ""
63
+ let storedQuoteAuthor = prefs?.string(forKey: "quoteAuthor") ?? ""
39
64
 
40
65
  let defaults = MyWidgetEntry.defaults()
41
66
  return MyWidgetEntry(
@@ -44,34 +69,33 @@ struct MyWidgetEntry: TimelineEntry {
44
69
  title: storedTitle.isEmpty ? defaults.title : storedTitle,
45
70
  planText: storedPlan,
46
71
  isPro: storedIsPro,
47
- quote: storedQuote
72
+ quote: storedQuote,
73
+ quoteAuthor: storedQuoteAuthor
48
74
  )
49
75
  }
50
76
 
51
- /// Time-aware fallback used when the Flutter side has not yet written
52
- /// data. Picks the language from the device locale so the first render
53
- /// is at least in the user's language; once the app runs, the real
54
- /// values (in the app locale) overwrite this.
77
+ /// Fallback used ONLY in the brief window between the widget being placed
78
+ /// and the Flutter app pushing real values. Kept dead simple — the
79
+ /// time-aware greeting in three languages lives on the Dart side
80
+ /// (home_widget_mywidget_service.dart::_greeting). Duplicating that logic
81
+ /// here was a maintenance trap when adding new locales.
55
82
  static func defaults() -> MyWidgetEntry {
56
- let hour = Calendar.current.component(.hour, from: Date())
57
83
  let lang = Locale.current.language.languageCode?.identifier ?? "en"
58
- let (morning, afternoon, evening, hello): (String, String, String, String)
84
+ let greeting: String
85
+ let hello: String
59
86
  switch lang {
60
- case "pt": (morning, afternoon, evening, hello) = ("Bom dia", "Boa tarde", "Boa noite", "Olá!")
61
- case "es": (morning, afternoon, evening, hello) = ("Buenos días", "Buenas tardes", "Buenas noches", "¡Hola!")
62
- default: (morning, afternoon, evening, hello) = ("Good morning", "Good afternoon", "Good evening", "Hi there!")
87
+ case "pt": (greeting, hello) = ("Olá", "Bem-vindo!")
88
+ case "es": (greeting, hello) = ("Hola", "¡Bienvenido!")
89
+ default: (greeting, hello) = ("Hello", "Welcome!")
63
90
  }
64
- let greeting: String
65
- if hour < 12 { greeting = morning }
66
- else if hour < 18 { greeting = afternoon }
67
- else { greeting = evening }
68
91
  return MyWidgetEntry(
69
92
  date: Date(),
70
93
  greeting: greeting,
71
94
  title: hello,
72
95
  planText: "",
73
96
  isPro: false,
74
- quote: ""
97
+ quote: "",
98
+ quoteAuthor: ""
75
99
  )
76
100
  }
77
101
  }
@@ -107,19 +131,41 @@ struct MyWidgetWidgetView: View {
107
131
  .frame(maxWidth: .infinity, alignment: .leading)
108
132
  .padding(.trailing, 8)
109
133
 
110
- // Motivational quote only on the large widget small/medium
111
- // don't have the vertical room. Thin, low-emphasis typography
112
- // so the title stays the hero element.
113
- if family == .systemLarge && !entry.quote.isEmpty {
134
+ // Quote line counts per iOS widget family (fixed sizes, so we
135
+ // can hand-tune instead of measuring):
136
+ // small → 1 line (just the first sentence, no attribution)
137
+ // medium 2 lines (horizontal, short fits two short lines)
138
+ // large → 4 lines + bold attribution (the whole quote)
139
+ if !entry.quote.isEmpty {
140
+ let lineLimit: Int = {
141
+ switch family {
142
+ case .systemSmall: return 1
143
+ case .systemMedium: return 2
144
+ default: return 4
145
+ }
146
+ }()
147
+
114
148
  Spacer().frame(height: 12)
115
149
  Text(entry.quote)
116
150
  .font(.system(size: 15, weight: .light, design: .rounded))
117
151
  .italic()
118
152
  .foregroundStyle(.white.opacity(0.7))
119
- .lineLimit(4)
153
+ .lineLimit(lineLimit)
120
154
  .lineSpacing(2)
121
155
  .frame(maxWidth: .infinity, alignment: .leading)
122
156
  .padding(.trailing, 8)
157
+
158
+ if family == .systemLarge && !entry.quoteAuthor.isEmpty {
159
+ // Attribution on its own line, right-aligned and bold so
160
+ // it reads as the source of the quote rather than part of
161
+ // the quote itself.
162
+ Text(entry.quoteAuthor)
163
+ .font(.system(size: 13, weight: .bold, design: .rounded))
164
+ .foregroundStyle(.white.opacity(0.7))
165
+ .frame(maxWidth: .infinity, alignment: .trailing)
166
+ .padding(.trailing, 8)
167
+ .padding(.top, 4)
168
+ }
123
169
  }
124
170
 
125
171
  Spacer()
@@ -133,10 +179,10 @@ struct MyWidgetWidgetView: View {
133
179
  if entry.isPro {
134
180
  Label(entry.planText, systemImage: "star.fill")
135
181
  .font(.system(size: 11, weight: .bold, design: .rounded))
136
- .foregroundStyle(Color(red: 1.0, green: 0.84, blue: 0.0))
182
+ .foregroundStyle(WidgetBrand.proGold)
137
183
  .padding(.horizontal, 10)
138
184
  .padding(.vertical, 5)
139
- .background(Color(red: 1.0, green: 0.84, blue: 0.0).opacity(0.18))
185
+ .background(WidgetBrand.proPillBg)
140
186
  .clipShape(Capsule())
141
187
  } else {
142
188
  Text(entry.planText)
@@ -144,7 +190,7 @@ struct MyWidgetWidgetView: View {
144
190
  .foregroundStyle(.white.opacity(0.45))
145
191
  .padding(.horizontal, 10)
146
192
  .padding(.vertical, 5)
147
- .background(Color.white.opacity(0.08))
193
+ .background(WidgetBrand.freePillBg)
148
194
  .clipShape(Capsule())
149
195
  }
150
196
  }
@@ -153,7 +199,7 @@ struct MyWidgetWidgetView: View {
153
199
  Spacer(minLength: 12)
154
200
  ZStack {
155
201
  Circle()
156
- .fill(Color.white.opacity(0.18))
202
+ .fill(WidgetBrand.addButtonBg)
157
203
  .frame(width: 34, height: 34)
158
204
  Image(systemName: "plus")
159
205
  .font(.system(size: 16, weight: .bold))
@@ -176,8 +222,8 @@ struct MyWidgetWidget: Widget {
176
222
  .containerBackground(for: .widget) {
177
223
  LinearGradient(
178
224
  gradient: Gradient(colors: [
179
- Color(red: 0.08, green: 0.03, blue: 0.16),
180
- Color(red: 0.20, green: 0.09, blue: 0.42),
225
+ WidgetBrand.gradientStart,
226
+ WidgetBrand.gradientEnd,
181
227
  ]),
182
228
  startPoint: .topLeading,
183
229
  endPoint: .bottomTrailing
@@ -193,17 +239,17 @@ struct MyWidgetWidget: Widget {
193
239
  #Preview("Small", as: .systemSmall) {
194
240
  MyWidgetWidget()
195
241
  } timeline: {
196
- MyWidgetEntry(date: .now, greeting: "Bom dia", title: "Olá, Paulo!", planText: "PRO", isPro: true, quote: "")
242
+ MyWidgetEntry(date: .now, greeting: "Bom dia", title: "Olá, Paulo!", planText: "PRO", isPro: true, quote: "Seu tempo é limitado.", quoteAuthor: "")
197
243
  }
198
244
 
199
245
  #Preview("Medium", as: .systemMedium) {
200
246
  MyWidgetWidget()
201
247
  } timeline: {
202
- MyWidgetEntry(date: .now, greeting: "Boa tarde", title: "Olá, Paulo!", planText: "Plano grátis", isPro: false, quote: "")
248
+ MyWidgetEntry(date: .now, greeting: "Boa tarde", title: "Olá, Paulo!", planText: "Plano grátis", isPro: false, quote: "Seu tempo é limitado.\nNão viva a vida de outra pessoa.", quoteAuthor: "")
203
249
  }
204
250
 
205
251
  #Preview("Large", as: .systemLarge) {
206
252
  MyWidgetWidget()
207
253
  } timeline: {
208
- MyWidgetEntry(date: .now, greeting: "Boa noite", title: "Olá, Paulo!", planText: "PRO", isPro: true, quote: "Sempre parece impossível, até que seja feito.")
254
+ MyWidgetEntry(date: .now, greeting: "Boa noite", title: "Olá, Paulo!", planText: "PRO", isPro: true, quote: "Seu tempo é limitado.\nNão viva a vida de outra pessoa.\nTenha coragem de seguir sua intuição.\nTodo o resto é secundário.", quoteAuthor: "Steve Jobs")
209
255
  }
@@ -38,7 +38,7 @@
38
38
  </scene>
39
39
  </scenes>
40
40
  <resources>
41
- <image name="LaunchImage" width="512" height="512"/>
41
+ <image name="LaunchImage" width="1254" height="1254"/>
42
42
  <image name="LaunchBackground" width="1" height="1"/>
43
43
  </resources>
44
44
  </document>
@@ -18,6 +18,7 @@ export 'kasy_button.dart';
18
18
  export 'kasy_card.dart';
19
19
  export 'kasy_checkbox.dart';
20
20
  export 'kasy_chip.dart';
21
+ export 'kasy_date_picker.dart';
21
22
  export 'kasy_dialog.dart';
22
23
  export 'kasy_otp_verification_bottom_sheet.dart';
23
24
  export 'kasy_skeleton.dart';
@@ -1,7 +1,8 @@
1
- import 'dart:ui' show lerpDouble;
1
+ import 'dart:ui' as ui show lerpDouble;
2
2
 
3
3
  import 'package:flutter/foundation.dart' show kIsWeb;
4
4
  import 'package:flutter/material.dart';
5
+ import 'package:kasy_kit/components/kasy_avatar_presets.dart';
5
6
  import 'package:kasy_kit/core/theme/theme.dart';
6
7
 
7
8
  /// Logical sizes for [KasyAvatar] (diameter in logical pixels).
@@ -45,7 +46,7 @@ class KasyAvatar extends StatelessWidget {
45
46
  final bool showStoryRing;
46
47
  final Gradient? storyRingGradient;
47
48
  final KasyAvatarStatus? status;
48
- final Gradient? backgroundGradient;
49
+ final KasyAvatarGradientData? backgroundGradient;
49
50
  final VoidCallback? onTap;
50
51
  final String? semanticLabel;
51
52
 
@@ -73,7 +74,7 @@ class KasyAvatar extends StatelessWidget {
73
74
  factory KasyAvatar.gradientFill({
74
75
  Key? key,
75
76
  required KasyAvatarSize size,
76
- required Gradient gradient,
77
+ required KasyAvatarGradientData gradient,
77
78
  double? diameter,
78
79
  bool showShadow = true,
79
80
  VoidCallback? onTap,
@@ -183,17 +184,7 @@ class KasyAvatar extends StatelessWidget {
183
184
  return Stack(
184
185
  fit: StackFit.expand,
185
186
  children: [
186
- DecoratedBox(
187
- decoration: BoxDecoration(
188
- shape: shape == KasyAvatarShape.circle
189
- ? BoxShape.circle
190
- : BoxShape.rectangle,
191
- borderRadius: shape == KasyAvatarShape.roundedSquare
192
- ? BorderRadius.circular(_d * 0.32)
193
- : null,
194
- gradient: backgroundGradient,
195
- ),
196
- ),
187
+ CustomPaint(painter: _AvatarOrbPainter(backgroundGradient!)),
197
188
  if (initialsRaw != null && initialsRaw.isNotEmpty)
198
189
  Center(
199
190
  child: Text(
@@ -497,6 +488,63 @@ _KasyAvatarColors _colorsForTone(
497
488
  return _KasyAvatarColors(background: bg, foreground: a);
498
489
  }
499
490
 
491
+ // ─────────────────────────────────────────────────────────────────────────────
492
+ // Orb painter
493
+ // ─────────────────────────────────────────────────────────────────────────────
494
+
495
+ /// Renders the HeroUI-style orb effect: a soft linear-gradient background
496
+ /// plus a blurred sphere in the lower-center, using geometry extracted from
497
+ /// the Figma SVGs (all 14 presets share identical shape, only colors differ).
498
+ class _AvatarOrbPainter extends CustomPainter {
499
+ final KasyAvatarGradientData data;
500
+
501
+ const _AvatarOrbPainter(this.data);
502
+
503
+ @override
504
+ void paint(Canvas canvas, Size size) {
505
+ // Background: linear gradient top → bottom.
506
+ final Rect rect = Offset.zero & size;
507
+ final Paint bgPaint = Paint()
508
+ ..shader = LinearGradient(
509
+ begin: Alignment.topCenter,
510
+ end: Alignment.bottomCenter,
511
+ colors: [data.bgTop, data.bgBottom],
512
+ ).createShader(rect);
513
+ canvas.drawRect(rect, bgPaint);
514
+
515
+ // Orb: blurred circle with diagonal linear gradient.
516
+ // Geometry normalised from 80x80 px Figma SVG:
517
+ // center = (45.625, 52.5) → (0.5703 w, 0.6563 h)
518
+ // radius = 27.5 → 0.344 w
519
+ // blur = stdDeviation 7.8125 → 0.0977 w
520
+ final Offset center = Offset(
521
+ size.width * 0.5703,
522
+ size.height * 0.6563,
523
+ );
524
+ final double radius = size.width * 0.344;
525
+ final double blurSigma = size.width * 0.0977;
526
+
527
+ // Gradient aligned to the orb bounding box.
528
+ final Rect orbRect = Rect.fromCircle(center: center, radius: radius);
529
+ final Paint orbPaint = Paint()
530
+ ..shader = LinearGradient(
531
+ begin: const Alignment(-0.24, 0.0),
532
+ end: const Alignment(0.75, 1.21),
533
+ colors: [data.orbTop, data.orbBottom],
534
+ ).createShader(orbRect)
535
+ ..maskFilter = MaskFilter.blur(BlurStyle.normal, blurSigma);
536
+
537
+ canvas.drawCircle(center, radius, orbPaint);
538
+ }
539
+
540
+ @override
541
+ bool shouldRepaint(_AvatarOrbPainter old) => data != old.data;
542
+ }
543
+
544
+ // ─────────────────────────────────────────────────────────────────────────────
545
+ // Pressable wrapper
546
+ // ─────────────────────────────────────────────────────────────────────────────
547
+
500
548
  class _KasyAvatarPressable extends StatefulWidget {
501
549
  final Widget child;
502
550
  final VoidCallback onPressed;
@@ -538,12 +586,12 @@ class _KasyAvatarPressableState extends State<_KasyAvatarPressable>
538
586
  double get _s {
539
587
  final double t = _c.value.clamp(0.0, 1.0);
540
588
  if (t <= 0.28) {
541
- return lerpDouble(1.0, _pressIn, t / 0.28)!;
589
+ return ui.lerpDouble(1.0, _pressIn, t / 0.28)!;
542
590
  }
543
591
  if (t <= 0.55) {
544
- return lerpDouble(_pressIn, _releasePeak, (t - 0.28) / 0.27)!;
592
+ return ui.lerpDouble(_pressIn, _releasePeak, (t - 0.28) / 0.27)!;
545
593
  }
546
- return lerpDouble(_releasePeak, 1.0, (t - 0.55) / 0.45)!;
594
+ return ui.lerpDouble(_releasePeak, 1.0, (t - 0.55) / 0.45)!;
547
595
  }
548
596
 
549
597
  void _tap() {
@@ -1,133 +1,157 @@
1
1
  import 'package:flutter/material.dart';
2
2
 
3
- /// Linear gradient presets for [KasyAvatar] via `backgroundGradient` or
4
- /// [KasyAvatar.gradientFill].
3
+ /// Gradient data for [KasyAvatar] orb-style fills.
5
4
  ///
6
- /// Matches the HeroUI Figma Kit V3 avatar palette exactly.
7
- /// All gradients use 141° (upper-left lower-right), flat from 0%→27%,
8
- /// then transition to the second color at 100% — same as Figma specification.
5
+ /// Holds the four colors needed to render the background wash and the
6
+ /// soft-focus sphere: [bgTop]/[bgBottom] for the canvas gradient, and
7
+ /// [orbTop]/[orbBottom] for the blurred orb overlay.
8
+ ///
9
+ /// Matches the HeroUI Figma Kit V3 "Image Use" avatar palette exactly.
10
+ @immutable
11
+ class KasyAvatarGradientData {
12
+ final Color bgTop;
13
+ final Color bgBottom;
14
+ final Color orbTop;
15
+ final Color orbBottom;
16
+
17
+ const KasyAvatarGradientData({
18
+ required this.bgTop,
19
+ required this.bgBottom,
20
+ required this.orbTop,
21
+ required this.orbBottom,
22
+ });
23
+
24
+ @override
25
+ bool operator ==(Object other) {
26
+ if (identical(this, other)) return true;
27
+ return other is KasyAvatarGradientData &&
28
+ other.bgTop == bgTop &&
29
+ other.bgBottom == bgBottom &&
30
+ other.orbTop == orbTop &&
31
+ other.orbBottom == orbBottom;
32
+ }
33
+
34
+ @override
35
+ int get hashCode => Object.hash(bgTop, bgBottom, orbTop, orbBottom);
36
+ }
37
+
38
+ /// All 14 orb-style avatar gradient presets (matches Figma "Image Use" section).
9
39
  abstract final class KasyAvatarGradients {
10
- // 141° CSS angle → Flutter Alignment
11
- // begin = upper-left region (-sin141°, cos141°) ≈ (-0.63, -0.78)
12
- // end = lower-right region (+sin141°, -cos141°) ≈ (+0.63, +0.78)
13
- static const Alignment _b = Alignment(-0.63, -0.78);
14
- static const Alignment _e = Alignment(0.63, 0.78);
15
-
16
- /// Cornflower blue → deep blue
17
- static const LinearGradient blue = LinearGradient(
18
- begin: _b,
19
- end: _e,
20
- colors: [Color(0xFF5D9BE7), Color(0xFF5D9BE7), Color(0xFF0026FF)],
21
- stops: [0.0, 0.27, 1.0],
40
+ /// Cornflower-blue canvas, deep-blue orb.
41
+ static const KasyAvatarGradientData blueDark = KasyAvatarGradientData(
42
+ bgTop: Color(0xFFE9E9FF),
43
+ bgBottom: Color(0xFFCCCDF1),
44
+ orbTop: Color(0xFF5D9BE7),
45
+ orbBottom: Color(0xFF0026FF),
22
46
  );
23
47
 
24
- /// Cyan-blue violet
25
- static const LinearGradient sky = LinearGradient(
26
- begin: _b,
27
- end: _e,
28
- colors: [Color(0xFF5DD0E7), Color(0xFF5DD0E7), Color(0xFF7300FF)],
29
- stops: [0.0, 0.27, 1.0],
48
+ /// Ice-blue canvas, cyan-to-violet orb.
49
+ static const KasyAvatarGradientData blue = KasyAvatarGradientData(
50
+ bgTop: Color(0xFFE9E9FF),
51
+ bgBottom: Color(0xFFCCE5F1),
52
+ orbTop: Color(0xFF5DD0E7),
53
+ orbBottom: Color(0xFF7300FF),
30
54
  );
31
55
 
32
- /// Aqua deep blue
33
- static const LinearGradient teal = LinearGradient(
34
- begin: _b,
35
- end: _e,
36
- colors: [Color(0xFF5DE7E7), Color(0xFF5DE7E7), Color(0xFF001AFF)],
37
- stops: [0.0, 0.27, 1.0],
56
+ /// Pale-aqua canvas, aqua-to-deep-blue orb.
57
+ static const KasyAvatarGradientData sky = KasyAvatarGradientData(
58
+ bgTop: Color(0xFFE9FAFF),
59
+ bgBottom: Color(0xFFCCECF1),
60
+ orbTop: Color(0xFF5DE7E7),
61
+ orbBottom: Color(0xFF001AFF),
38
62
  );
39
63
 
40
- /// Mint green royal blue
41
- static const LinearGradient emerald = LinearGradient(
42
- begin: _b,
43
- end: _e,
44
- colors: [Color(0xFF5DE79D), Color(0xFF5DE79D), Color(0xFF0033FF)],
45
- stops: [0.0, 0.27, 1.0],
64
+ /// Mint canvas, seafoam-to-forest orb.
65
+ static const KasyAvatarGradientData emerald = KasyAvatarGradientData(
66
+ bgTop: Color(0xFFE9FFF0),
67
+ bgBottom: Color(0xFFCCF1D6),
68
+ orbTop: Color(0xFF52DEB0),
69
+ orbBottom: Color(0xFF0D5C45),
46
70
  );
47
71
 
48
- /// Seafoam dark forest green
49
- static const LinearGradient green = LinearGradient(
50
- begin: _b,
51
- end: _e,
52
- colors: [Color(0xFF52DEB0), Color(0xFF52DEB0), Color(0xFF0D5C45)],
53
- stops: [0.0, 0.27, 1.0],
72
+ /// Pale-green canvas, mint-to-royal-blue orb.
73
+ static const KasyAvatarGradientData green = KasyAvatarGradientData(
74
+ bgTop: Color(0xFFE9FFEB),
75
+ bgBottom: Color(0xFFD3F1CC),
76
+ orbTop: Color(0xFF5DE79D),
77
+ orbBottom: Color(0xFF0033FF),
54
78
  );
55
79
 
56
- /// Turquoise deep forest green
57
- static const LinearGradient forest = LinearGradient(
58
- begin: _b,
59
- end: _e,
60
- colors: [Color(0xFF5DD9AF), Color(0xFF5DD9AF), Color(0xFF094E39)],
61
- stops: [0.0, 0.27, 1.0],
80
+ /// Sage canvas, turquoise-to-deep-forest orb.
81
+ static const KasyAvatarGradientData forest = KasyAvatarGradientData(
82
+ bgTop: Color(0xFFCDE8D5),
83
+ bgBottom: Color(0xFF9CD0AA),
84
+ orbTop: Color(0xFF5DD9AF),
85
+ orbBottom: Color(0xFF094E39),
62
86
  );
63
87
 
64
- /// Golden yellow deep red-orange
65
- static const LinearGradient orange = LinearGradient(
66
- begin: _b,
67
- end: _e,
68
- colors: [Color(0xFFE7BD5D), Color(0xFFE7BD5D), Color(0xFFFF1E00)],
69
- stops: [0.0, 0.27, 1.0],
88
+ /// Cream canvas, gold-to-deep-red orb.
89
+ static const KasyAvatarGradientData orange = KasyAvatarGradientData(
90
+ bgTop: Color(0xFFFFF8E9),
91
+ bgBottom: Color(0xFFF1DFCC),
92
+ orbTop: Color(0xFFE7BD5D),
93
+ orbBottom: Color(0xFFFF1E00),
70
94
  );
71
95
 
72
- /// Salmon scarlet
73
- static const LinearGradient red = LinearGradient(
74
- begin: _b,
75
- end: _e,
76
- colors: [Color(0xFFE7885D), Color(0xFFE7885D), Color(0xFFFF0004)],
77
- stops: [0.0, 0.27, 1.0],
96
+ /// Blush canvas, salmon-to-scarlet orb.
97
+ static const KasyAvatarGradientData red = KasyAvatarGradientData(
98
+ bgTop: Color(0xFFFFE9E9),
99
+ bgBottom: Color(0xFFF1CCCC),
100
+ orbTop: Color(0xFFE7885D),
101
+ orbBottom: Color(0xFFFF0004),
78
102
  );
79
103
 
80
- /// Soft purplecobalt blue
81
- static const LinearGradient indigo = LinearGradient(
82
- begin: _b,
83
- end: _e,
84
- colors: [Color(0xFFB45DE7), Color(0xFFB45DE7), Color(0xFF0055FF)],
85
- stops: [0.0, 0.27, 1.0],
104
+ /// Lavender canvas, soft-purple-to-cobalt orb.
105
+ static const KasyAvatarGradientData indigo = KasyAvatarGradientData(
106
+ bgTop: Color(0xFFEDCEFF),
107
+ bgBottom: Color(0xFFF1CCEE),
108
+ orbTop: Color(0xFFB45DE7),
109
+ orbBottom: Color(0xFF0055FF),
86
110
  );
87
111
 
88
- /// Medium purple → crimson
89
- static const LinearGradient purple = LinearGradient(
90
- begin: _b,
91
- end: _e,
92
- colors: [Color(0xFF8D5DE7), Color(0xFF8D5DE7), Color(0xFFFF0009)],
93
- stops: [0.0, 0.27, 1.0],
112
+ /// Lilac canvas, medium-purple-to-crimson orb.
113
+ static const KasyAvatarGradientData purple = KasyAvatarGradientData(
114
+ bgTop: Color(0xFFEFE9FF),
115
+ bgBottom: Color(0xFFDBCCF1),
116
+ orbTop: Color(0xFF8D5DE7),
117
+ orbBottom: Color(0xFFFF0009),
94
118
  );
95
119
 
96
- /// Orchid pink deep magenta-red
97
- static const LinearGradient rose = LinearGradient(
98
- begin: _b,
99
- end: _e,
100
- colors: [Color(0xFFE75DCB), Color(0xFFE75DCB), Color(0xFFFF000D)],
101
- stops: [0.0, 0.27, 1.0],
120
+ /// Pink-white canvas, orchid-to-magenta orb.
121
+ static const KasyAvatarGradientData rose = KasyAvatarGradientData(
122
+ bgTop: Color(0xFFFFE9FB),
123
+ bgBottom: Color(0xFFF0CCF1),
124
+ orbTop: Color(0xFFE75DCB),
125
+ orbBottom: Color(0xFFFF000D),
102
126
  );
103
127
 
104
- /// Hot pinkcoral red
105
- static const LinearGradient hotPink = LinearGradient(
106
- begin: _b,
107
- end: _e,
108
- colors: [Color(0xFFE43673), Color(0xFFE43673), Color(0xFFFB5059)],
109
- stops: [0.0, 0.27, 1.0],
128
+ /// Blush canvas, hot-pink-to-coral orb.
129
+ static const KasyAvatarGradientData hotPink = KasyAvatarGradientData(
130
+ bgTop: Color(0xFFFFE9E9),
131
+ bgBottom: Color(0xFFF1CCCD),
132
+ orbTop: Color(0xFFE43673),
133
+ orbBottom: Color(0xFFFB5059),
110
134
  );
111
135
 
112
- /// Silver near-black
113
- static const LinearGradient silver = LinearGradient(
114
- begin: _b,
115
- end: _e,
116
- colors: [Color(0xFF949494), Color(0xFF949494), Color(0xFF080808)],
117
- stops: [0.0, 0.27, 1.0],
136
+ /// Light-grey canvas, silver-to-near-black orb.
137
+ static const KasyAvatarGradientData silver = KasyAvatarGradientData(
138
+ bgTop: Color(0xFFE3E3E3),
139
+ bgBottom: Color(0xFFC6C6C6),
140
+ orbTop: Color(0xFF949494),
141
+ orbBottom: Color(0xFF080808),
118
142
  );
119
143
 
120
- /// Mid-grey very dark grey
121
- static const LinearGradient black = LinearGradient(
122
- begin: _b,
123
- end: _e,
124
- colors: [Color(0xFF7F7F7F), Color(0xFF7F7F7F), Color(0xFF1F1F1F)],
125
- stops: [0.0, 0.27, 1.0],
144
+ /// Dark-grey canvas, mid-grey-to-very-dark orb.
145
+ static const KasyAvatarGradientData black = KasyAvatarGradientData(
146
+ bgTop: Color(0xFF252525),
147
+ bgBottom: Color(0xFF444444),
148
+ orbTop: Color(0xFF7F7F7F),
149
+ orbBottom: Color(0xFF1F1F1F),
126
150
  );
127
151
 
128
152
  /// All 14 presets in display order (matches Figma "Image Use" section).
129
- static const List<LinearGradient> all = [
130
- blue, sky, teal, emerald, green, forest,
153
+ static const List<KasyAvatarGradientData> all = [
154
+ blueDark, blue, sky, emerald, green, forest,
131
155
  orange, red, indigo, purple, rose, hotPink,
132
156
  silver, black,
133
157
  ];