kasy-cli 1.16.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 +16 -2
- package/lib/commands/add.js +52 -19
- package/lib/commands/configure.js +548 -0
- package/lib/commands/deploy.js +4 -4
- package/lib/commands/doctor.js +54 -6
- package/lib/commands/favicon.js +4 -4
- package/lib/commands/icon.js +5 -5
- package/lib/commands/new.js +404 -213
- package/lib/commands/remove.js +14 -3
- package/lib/commands/run.js +208 -6
- package/lib/commands/splash.js +5 -5
- package/lib/commands/update.js +9 -9
- package/lib/scaffold/CHANGELOG.json +23 -0
- package/lib/scaffold/backends/api/patch/README.md +3 -2
- 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/backends/supabase/patch/README.md +3 -2
- package/lib/scaffold/generate.js +24 -8
- package/lib/scaffold/shared/generator-utils.js +52 -8
- package/lib/scaffold/shared/post-build.js +113 -31
- package/lib/scaffold/shared/template-strings.js +6 -0
- package/lib/utils/brand.js +16 -12
- package/lib/utils/flutter-run.js +139 -11
- package/lib/utils/i18n/messages-en.js +85 -7
- package/lib/utils/i18n/messages-es.js +85 -7
- package/lib/utils/i18n/messages-pt.js +86 -8
- package/lib/utils/ui.js +79 -4
- package/package.json +1 -1
- package/templates/firebase/README.en.md +18 -8
- package/templates/firebase/README.es.md +18 -8
- package/templates/firebase/README.md +18 -8
- package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MyWidget.kt +68 -45
- package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MyWidgetReceiver.kt +37 -0
- package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/OpenAppAction.kt +26 -0
- 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/docs/revenuecat-setup.es.md +28 -8
- package/templates/firebase/docs/revenuecat-setup.pt.md +28 -8
- 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 +45 -53
- 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/lib/router.dart +15 -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
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
android:layout_width="wrap_content"
|
|
18
18
|
android:layout_height="wrap_content"
|
|
19
19
|
android:text="Boa noite"
|
|
20
|
-
android:textColor="
|
|
20
|
+
android:textColor="@color/widget_text_subtle"
|
|
21
21
|
android:textSize="11sp" />
|
|
22
22
|
|
|
23
23
|
<TextView
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
android:layout_height="wrap_content"
|
|
26
26
|
android:layout_marginTop="4dp"
|
|
27
27
|
android:text="Olá!"
|
|
28
|
-
android:textColor="
|
|
28
|
+
android:textColor="@color/widget_text_strong"
|
|
29
29
|
android:textSize="22sp"
|
|
30
30
|
android:textStyle="bold" />
|
|
31
31
|
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
android:paddingTop="5dp"
|
|
47
47
|
android:paddingBottom="5dp"
|
|
48
48
|
android:text="⭐ PRO"
|
|
49
|
-
android:textColor="
|
|
49
|
+
android:textColor="@color/widget_pro_gold"
|
|
50
50
|
android:textSize="11sp"
|
|
51
51
|
android:textStyle="bold" />
|
|
52
52
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<!--
|
|
3
|
+
Brand colors for the MyWidget home widget — single source of truth on Android.
|
|
4
|
+
|
|
5
|
+
To rebrand the widget, edit ONLY this file. Every Android XML asset under
|
|
6
|
+
res/drawable/widget_* and res/layout/widget_* references these names instead
|
|
7
|
+
of inlining hex values.
|
|
8
|
+
|
|
9
|
+
Keep in sync with iOS counterparts in:
|
|
10
|
+
Firebase/ios/HomeWidgetExtension/MyWidget.swift (enum WidgetBrand)
|
|
11
|
+
-->
|
|
12
|
+
<resources>
|
|
13
|
+
<!-- Background gradient used behind every widget surface. -->
|
|
14
|
+
<color name="widget_gradient_start">#FF140829</color>
|
|
15
|
+
<color name="widget_gradient_end">#FF33176B</color>
|
|
16
|
+
|
|
17
|
+
<!-- PRO plan pill (gold). _bg is the soft fill behind the gold text. -->
|
|
18
|
+
<color name="widget_pro_gold">#FFFFD700</color>
|
|
19
|
+
<color name="widget_pro_pill_bg">#2EFFD700</color>
|
|
20
|
+
|
|
21
|
+
<!-- Free plan pill — low-emphasis translucent white. -->
|
|
22
|
+
<color name="widget_free_pill_bg">#14FFFFFF</color>
|
|
23
|
+
|
|
24
|
+
<!-- "+" circular button on medium/large widgets. -->
|
|
25
|
+
<color name="widget_add_button_bg">#2EFFFFFF</color>
|
|
26
|
+
<color name="widget_add_button_stroke">#FFFFFFFF</color>
|
|
27
|
+
|
|
28
|
+
<!-- Text colors used in the preview layout (gallery only). The Glance
|
|
29
|
+
runtime applies these via Compose, not XML. -->
|
|
30
|
+
<color name="widget_text_subtle">#8CFFFFFF</color>
|
|
31
|
+
<color name="widget_text_strong">#FFFFFFFF</color>
|
|
32
|
+
</resources>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -9,12 +9,24 @@ Guía para activar suscripciones y compras en la app después de que la CLI gene
|
|
|
9
9
|
| Ya listo | Lo que falta |
|
|
10
10
|
|----------|--------------|
|
|
11
11
|
| Instaló `purchases_flutter` | Crear cuenta en RevenueCat |
|
|
12
|
-
| Configuró las claves en `
|
|
12
|
+
| Configuró las claves en `.env` (test/iOS prod/Android prod) | Crear Productos, Entitlements y Offerings en el panel RC |
|
|
13
13
|
| Generó el código del paywall y del repositorio de suscripciones | Registrar la URL del webhook en el panel RC |
|
|
14
14
|
| Firebase: desplegó la Cloud Function del webhook | — |
|
|
15
15
|
| Supabase: desplegó la Edge Function del webhook | — |
|
|
16
16
|
|
|
17
|
-
> Las claves quedan en `.vscode/launch.json`
|
|
17
|
+
> Las claves quedan en `.env` en la raíz (fuente de verdad) y reflejadas en `.vscode/launch.json` + `Makefile`. Todos en `.gitignore` — nunca van al repositorio.
|
|
18
|
+
|
|
19
|
+
### ¿Qué clave usar?
|
|
20
|
+
|
|
21
|
+
La CLI pregunta **tres claves opcionales** (al menos una es obligatoria):
|
|
22
|
+
|
|
23
|
+
| Variable | Prefijo | Uso |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| `RC_TEST_KEY` | `test_` | Test Store. **Una sola clave**, sirve para iOS+Android. Usada automáticamente en simulador/emulador. |
|
|
26
|
+
| `RC_IOS_PROD_KEY` | `appl_` | App Store (Sandbox + Producción). Usada automáticamente en iPhone físico. |
|
|
27
|
+
| `RC_ANDROID_PROD_KEY` | `goog_` | Google Play (Sandbox + Producción). Usada automáticamente en Android físico. |
|
|
28
|
+
|
|
29
|
+
`kasy run` elige la clave correcta según el dispositivo. Forzar manualmente: `kasy run --rc=test` o `kasy run --rc=prod`.
|
|
18
30
|
|
|
19
31
|
---
|
|
20
32
|
|
|
@@ -155,9 +167,13 @@ Después de completar todo y configurar el idioma del grupo, el estado sale de *
|
|
|
155
167
|
|
|
156
168
|
[app.revenuecat.com](https://app.revenuecat.com) → tu proyecto → **Apps** → `+ Add app` → **App Store** → copia la clave `appl_xxx`.
|
|
157
169
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
170
|
+
Pégala en el `.env` de la raíz:
|
|
171
|
+
|
|
172
|
+
```env
|
|
173
|
+
RC_IOS_PROD_KEY=appl_xxxxxxxxxxxxxxx
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
`kasy run` usa esta clave automáticamente en iPhone físico (el simulador sigue con `RC_TEST_KEY`).
|
|
161
177
|
|
|
162
178
|
**6. Crear Sandbox Tester — obligatorio para probar en iPhone físico**
|
|
163
179
|
|
|
@@ -253,9 +269,13 @@ d) Google Play Console → **Configuración** → **Usuarios y permisos** → **
|
|
|
253
269
|
|
|
254
270
|
[app.revenuecat.com](https://app.revenuecat.com) → tu proyecto → **Apps** → `+ Add app` → **Google Play** → sube el archivo JSON → copia la clave `goog_xxx`.
|
|
255
271
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
272
|
+
Pégala en el `.env` de la raíz:
|
|
273
|
+
|
|
274
|
+
```env
|
|
275
|
+
RC_ANDROID_PROD_KEY=goog_xxxxxxxxxxxxxxx
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
`kasy run` usa esta clave automáticamente en Android físico (el emulador sigue con `RC_TEST_KEY`).
|
|
259
279
|
|
|
260
280
|
**8. Agregar License Tester — obligatorio para probar en el dispositivo**
|
|
261
281
|
|
|
@@ -9,12 +9,24 @@ Guia para ativar assinaturas e compras no app depois que a CLI gerou o projeto.
|
|
|
9
9
|
| Já pronto | O que ainda falta |
|
|
10
10
|
|-----------|-------------------|
|
|
11
11
|
| Instalou `purchases_flutter` | Criar conta no RevenueCat |
|
|
12
|
-
| Configurou as chaves no `
|
|
12
|
+
| Configurou as chaves no `.env` (test/iOS prod/Android prod) | Criar Produtos, Entitlements e Offerings no painel RC |
|
|
13
13
|
| Gerou o código do paywall e do repositório de assinaturas | Registrar a URL do webhook no painel RC |
|
|
14
14
|
| Firebase: implantou a Cloud Function do webhook | — |
|
|
15
15
|
| Supabase: implantou a Edge Function do webhook | — |
|
|
16
16
|
|
|
17
|
-
> As chaves ficam em `.vscode/launch.json`
|
|
17
|
+
> As chaves ficam em `.env` na raiz (fonte da verdade) e refletidas no `.vscode/launch.json` + `Makefile`. Todos no `.gitignore` — nunca vão para o repositório.
|
|
18
|
+
|
|
19
|
+
### Qual chave usar?
|
|
20
|
+
|
|
21
|
+
A CLI pergunta **três chaves opcionais** (pelo menos uma é obrigatória):
|
|
22
|
+
|
|
23
|
+
| Variável | Prefixo | Uso |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| `RC_TEST_KEY` | `test_` | Test Store. **Uma chave única**, vale iOS+Android. Usada automaticamente em simulador/emulador. |
|
|
26
|
+
| `RC_IOS_PROD_KEY` | `appl_` | App Store (Sandbox + Produção). Usada automaticamente em iPhone físico. |
|
|
27
|
+
| `RC_ANDROID_PROD_KEY` | `goog_` | Google Play (Sandbox + Produção). Usada automaticamente em Android físico. |
|
|
28
|
+
|
|
29
|
+
O `kasy run` escolhe a chave certa baseado no device. Forçar manual: `kasy run --rc=test` ou `kasy run --rc=prod`.
|
|
18
30
|
|
|
19
31
|
---
|
|
20
32
|
|
|
@@ -155,9 +167,13 @@ Após preencher tudo e configurar o idioma do grupo, o status sai de **Missing M
|
|
|
155
167
|
|
|
156
168
|
[app.revenuecat.com](https://app.revenuecat.com) → seu projeto → **Apps** → `+ Add app` → **App Store** → copie a chave `appl_xxx`.
|
|
157
169
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
170
|
+
Cole no `.env` da raiz:
|
|
171
|
+
|
|
172
|
+
```env
|
|
173
|
+
RC_IOS_PROD_KEY=appl_xxxxxxxxxxxxxxx
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
O `kasy run` usa essa chave automaticamente em iPhone físico (simulador continua com `RC_TEST_KEY`).
|
|
161
177
|
|
|
162
178
|
**6. Criar Sandbox Tester — obrigatório para testar no iPhone físico**
|
|
163
179
|
|
|
@@ -253,9 +269,13 @@ d) Google Play Console → **Configurações** → **Usuários e permissões**
|
|
|
253
269
|
|
|
254
270
|
[app.revenuecat.com](https://app.revenuecat.com) → seu projeto → **Apps** → `+ Add app` → **Google Play** → faça upload do arquivo JSON → copie a chave `goog_xxx`.
|
|
255
271
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
272
|
+
Cole no `.env` da raiz:
|
|
273
|
+
|
|
274
|
+
```env
|
|
275
|
+
RC_ANDROID_PROD_KEY=goog_xxxxxxxxxxxxxxx
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
O `kasy run` usa essa chave automaticamente em Android físico (emulador continua com `RC_TEST_KEY`).
|
|
259
279
|
|
|
260
280
|
**8. Adicionar License Tester — obrigatório para testar no dispositivo**
|
|
261
281
|
|
|
@@ -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() {
|