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.
- package/bin/kasy.js +15 -2
- package/lib/commands/add.js +7 -7
- package/lib/commands/configure.js +548 -0
- package/lib/commands/deploy.js +4 -4
- package/lib/commands/doctor.js +17 -0
- package/lib/commands/favicon.js +4 -4
- package/lib/commands/icon.js +5 -5
- package/lib/commands/new.js +403 -238
- package/lib/commands/run.js +1 -1
- package/lib/commands/splash.js +5 -5
- package/lib/commands/update.js +9 -9
- package/lib/scaffold/CHANGELOG.json +14 -0
- package/lib/scaffold/backends/firebase/enable-auth-via-cli.js +108 -0
- package/lib/scaffold/backends/firebase/setup-from-scratch.js +44 -5
- package/lib/scaffold/generate.js +24 -8
- package/lib/scaffold/shared/post-build.js +8 -0
- package/lib/utils/brand.js +16 -12
- package/lib/utils/flutter-run.js +139 -11
- package/lib/utils/i18n/messages-en.js +58 -5
- package/lib/utils/i18n/messages-es.js +58 -5
- package/lib/utils/i18n/messages-pt.js +59 -6
- package/lib/utils/ui.js +79 -4
- package/package.json +1 -1
- package/templates/firebase/README.en.md +1 -1
- package/templates/firebase/README.es.md +1 -1
- package/templates/firebase/README.md +1 -1
- package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MainActivity.kt +0 -15
- package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MyWidget.kt +65 -26
- package/templates/firebase/android/app/src/main/res/drawable/widget_add_button.xml +2 -2
- package/templates/firebase/android/app/src/main/res/drawable/widget_gradient_bg.xml +7 -2
- package/templates/firebase/android/app/src/main/res/drawable/widget_gradient_inner.xml +6 -6
- package/templates/firebase/android/app/src/main/res/drawable/widget_plan_pill_bg.xml +1 -1
- package/templates/firebase/android/app/src/main/res/drawable/widget_preview_image.xml +2 -2
- package/templates/firebase/android/app/src/main/res/drawable/widget_pro_pill_bg.xml +1 -1
- package/templates/firebase/android/app/src/main/res/drawable-hdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-hdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-mdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-mdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-hdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-hdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-mdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-mdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xxhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xxxhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xxhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xxhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xxxhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xxxhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/layout/widget_preview.xml +3 -3
- package/templates/firebase/android/app/src/main/res/values/colors.xml +32 -0
- package/templates/firebase/assets/images/splash_logo_dark.png +0 -0
- package/templates/firebase/assets/images/splash_logo_dark_android12.png +0 -0
- package/templates/firebase/assets/images/splash_logo_light.png +0 -0
- package/templates/firebase/assets/images/splash_logo_light_android12.png +0 -0
- package/templates/firebase/ios/HomeWidgetExtension/MyWidget.swift +75 -29
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@3x.png +0 -0
- package/templates/firebase/ios/Runner/Base.lproj/LaunchScreen.storyboard +1 -1
- package/templates/firebase/lib/components/components.dart +1 -0
- package/templates/firebase/lib/components/kasy_avatar.dart +65 -17
- package/templates/firebase/lib/components/kasy_avatar_presets.dart +121 -97
- package/templates/firebase/lib/components/kasy_button.dart +8 -8
- package/templates/firebase/lib/components/kasy_date_picker.dart +834 -0
- package/templates/firebase/lib/components/kasy_tabs.dart +145 -61
- package/templates/firebase/lib/core/home_widgets/home_widget_mywidget_service.dart +12 -18
- package/templates/firebase/lib/features/home/home_components_preview_registry.dart +565 -77
- package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +10 -5
- package/templates/firebase/lib/i18n/en.i18n.json +2 -1
- package/templates/firebase/lib/i18n/es.i18n.json +2 -1
- package/templates/firebase/lib/i18n/pt.i18n.json +2 -1
- package/templates/firebase/pubspec.yaml +1 -1
- package/templates/firebase/web/index.html +9 -0
- package/templates/firebase/web/splash/img/dark-1x.png +0 -0
- package/templates/firebase/web/splash/img/dark-2x.png +0 -0
- package/templates/firebase/web/splash/img/dark-3x.png +0 -0
- package/templates/firebase/web/splash/img/dark-4x.png +0 -0
- package/templates/firebase/web/splash/img/light-1x.png +0 -0
- package/templates/firebase/web/splash/img/light-2x.png +0 -0
- package/templates/firebase/web/splash/img/light-3x.png +0 -0
- 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
|
-
///
|
|
52
|
-
///
|
|
53
|
-
///
|
|
54
|
-
///
|
|
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
|
|
84
|
+
let greeting: String
|
|
85
|
+
let hello: String
|
|
59
86
|
switch lang {
|
|
60
|
-
case "pt": (
|
|
61
|
-
case "es": (
|
|
62
|
-
default: (
|
|
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
|
-
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
|
|
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(
|
|
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(
|
|
182
|
+
.foregroundStyle(WidgetBrand.proGold)
|
|
137
183
|
.padding(.horizontal, 10)
|
|
138
184
|
.padding(.vertical, 5)
|
|
139
|
-
.background(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
180
|
-
|
|
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: "
|
|
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
|
}
|
|
Binary file
|
package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
CHANGED
|
Binary file
|
package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
CHANGED
|
Binary file
|
package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark.png
CHANGED
|
Binary file
|
package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png
CHANGED
|
Binary file
|
package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@3x.png
CHANGED
|
Binary file
|
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
-
///
|
|
4
|
-
/// [KasyAvatar.gradientFill].
|
|
3
|
+
/// Gradient data for [KasyAvatar] orb-style fills.
|
|
5
4
|
///
|
|
6
|
-
///
|
|
7
|
-
///
|
|
8
|
-
///
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
///
|
|
25
|
-
static const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
///
|
|
33
|
-
static const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
|
41
|
-
static const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
///
|
|
49
|
-
static const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
///
|
|
57
|
-
static const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
///
|
|
65
|
-
static const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
///
|
|
73
|
-
static const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
///
|
|
81
|
-
static const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
///
|
|
89
|
-
static const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
///
|
|
97
|
-
static const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
///
|
|
105
|
-
static const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
///
|
|
113
|
-
static const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
///
|
|
121
|
-
static const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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<
|
|
130
|
-
blue, sky,
|
|
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
|
];
|