kasy-cli 1.17.0 → 1.19.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 +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 +483 -324
- package/lib/commands/run.js +17 -4
- 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 +123 -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 +62 -5
- package/lib/utils/i18n/messages-es.js +62 -5
- package/lib/utils/i18n/messages-pt.js +63 -6
- package/lib/utils/ui.js +79 -4
- package/package.json +1 -2
- 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 +2173 -0
- package/templates/firebase/lib/components/kasy_tabs.dart +214 -91
- package/templates/firebase/lib/components/kasy_text_area.dart +9 -4
- package/templates/firebase/lib/components/kasy_text_field.dart +96 -36
- package/templates/firebase/lib/components/kasy_text_field_otp.dart +1 -2
- package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +88 -35
- package/templates/firebase/lib/core/bottom_menu/bottom_router.dart +7 -43
- package/templates/firebase/lib/core/dev_inspector/dev_inspector.dart +118 -16
- package/templates/firebase/lib/core/dev_inspector/dev_inspector_service.dart +14 -20
- package/templates/firebase/lib/core/home_widgets/home_widget_mywidget_service.dart +12 -18
- package/templates/firebase/lib/core/security/secured_storage.dart +56 -15
- package/templates/firebase/lib/core/theme/providers/theme_provider.dart +3 -0
- package/templates/firebase/lib/core/theme/web_background_sync.dart +3 -0
- package/templates/firebase/lib/core/theme/web_background_sync_web.dart +18 -0
- package/templates/firebase/lib/core/web_device_preview/web_device_preview.dart +3 -6
- package/templates/firebase/lib/features/authentication/api/authentication_api.dart +6 -0
- package/templates/firebase/lib/features/home/home_components_page.dart +3 -2
- package/templates/firebase/lib/features/home/home_components_preview_registry.dart +949 -77
- package/templates/firebase/lib/features/home/home_page.dart +17 -40
- package/templates/firebase/lib/features/notifications/ui/notifications_page.dart +1 -16
- package/templates/firebase/lib/features/notifications/ui/widgets/empty_notifications.dart +0 -4
- 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/main.dart +34 -34
- package/templates/firebase/pubspec.yaml +2 -1
- package/templates/firebase/storage.cors.json +8 -0
- package/templates/firebase/web/index.html +24 -2
- 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
- package/templates/firebase/lib/core/bottom_menu/kasy_bart_navigation.dart +0 -22
|
@@ -79,6 +79,9 @@ module.exports = {
|
|
|
79
79
|
'doctor.title': 'Kasy Doctor',
|
|
80
80
|
'doctor.baseEnvironment': 'Ambiente base',
|
|
81
81
|
'doctor.optionalBackend': 'Ferramentas opcionais de backend',
|
|
82
|
+
'doctor.gcpBilling.title': 'Google Cloud Billing (Firebase Blaze)',
|
|
83
|
+
'doctor.gcpBilling.found': '{count} conta(s) de faturamento ativa(s):',
|
|
84
|
+
'doctor.gcpBilling.missing': 'Nenhuma conta de faturamento encontrada. Crie uma antes de rodar `kasy new` com Firebase:',
|
|
82
85
|
'doctor.requiredMissing': 'Dependencias obrigatórias ausentes. Corrija os erros acima e execute o doctor novamente.',
|
|
83
86
|
'doctor.requiredPassed': '✓ Verificações obrigatórias de ambiente aprovadas.',
|
|
84
87
|
'modules.backends': 'Backends disponíveis:',
|
|
@@ -196,6 +199,10 @@ module.exports = {
|
|
|
196
199
|
'new.firebase.create.success': 'Projeto Firebase criado com sucesso.',
|
|
197
200
|
'new.firebase.create.failed': 'Não foi possível criar o projeto',
|
|
198
201
|
'new.firebase.create.gcloudRequired': 'gcloud CLI é obrigatório para "criar do zero". Sem ele, o fluxo completo do Firebase não pode rodar.',
|
|
202
|
+
'new.firebase.billing.required': 'Você ainda não tem uma conta de faturamento no Google Cloud. O Firebase precisa do plano Blaze para usar Storage e Cloud Functions.',
|
|
203
|
+
'new.firebase.billing.create.steps': 'Vou abrir a página de criação de conta de faturamento. Crie a conta (cartão de crédito, sem cobrança até bater a cota gratuita) e volte aqui:',
|
|
204
|
+
'new.firebase.billing.created.ready': 'Já criei a conta de faturamento, pode seguir?',
|
|
205
|
+
'new.firebase.billing.stillMissing': 'Ainda não encontrei nenhuma conta de faturamento ativa. Finalize a criação no Console e rode `kasy new` novamente.',
|
|
199
206
|
'new.firebase.create.installTitle': 'Para instalar o gcloud CLI, execute:',
|
|
200
207
|
'new.firebase.create.installCommand': 'Comando',
|
|
201
208
|
'new.firebase.create.installAfter': 'Depois faca login',
|
|
@@ -212,6 +219,8 @@ module.exports = {
|
|
|
212
219
|
'new.firebase.q.organization': 'Em qual organização GCP criar o projeto?',
|
|
213
220
|
'new.firebase.q.organization.none': 'Sem organização (conta pessoal / projeto avulso)',
|
|
214
221
|
'new.firebase.q.organization.hint': 'Organizações vinculadas à sua conta gcloud',
|
|
222
|
+
'new.firebase.create.billingWait': 'Aguardando propagação do faturamento…',
|
|
223
|
+
'new.firebase.create.billingQuotaError': 'Não foi possível vincular o faturamento (cota da conta atingida ou propagação ainda não concluída).',
|
|
215
224
|
'new.firebase.create.billingRetry.title': 'Vincule o faturamento manualmente (cota excedida ou erro de billing):',
|
|
216
225
|
'new.firebase.create.billingRetry.hint': 'Dica: O limite é 3 projetos por conta de faturamento. Remova projetos não usados no link acima. Se acabou de remover um, espere 2–5 min para a alteração propagar e tente novamente.',
|
|
217
226
|
'new.firebase.create.billingRetry.ready': 'Vinculei o faturamento. Tentar novamente?',
|
|
@@ -233,6 +242,7 @@ module.exports = {
|
|
|
233
242
|
'new.supabase.loginHint': 'Execute: supabase login. Depois informe a URL e chave do seu projeto existente.',
|
|
234
243
|
'new.supabase.setup': 'Vinculando projeto e publicando…',
|
|
235
244
|
'new.supabase.setupManual': 'Execute manualmente: supabase link, supabase db push, supabase functions deploy',
|
|
245
|
+
'new.supabase.passwordSaved': 'Senha do banco gerada automaticamente e salva em .kasy/supabase.json (gitignorado).',
|
|
236
246
|
'new.supabase.q.useExisting.orgSelect': 'Em qual organização está o projeto?',
|
|
237
247
|
'new.supabase.q.useExisting.projectSelect': 'Qual projeto Supabase existente deseja usar?',
|
|
238
248
|
'new.supabase.projectsRequired': 'Nenhum projeto encontrado nesta organização.',
|
|
@@ -273,9 +283,9 @@ module.exports = {
|
|
|
273
283
|
'new.q.backend.supabase.desc': 'Banco SQL (PostgreSQL) com mais controle',
|
|
274
284
|
'new.q.backend.api.desc': 'Você já tem seu próprio servidor',
|
|
275
285
|
|
|
276
|
-
'new.q.mode': 'Como quer
|
|
277
|
-
'new.q.mode.quick': '⚡
|
|
278
|
-
'new.q.mode.advanced': '🛠
|
|
286
|
+
'new.q.mode': 'Como quer criar o app?',
|
|
287
|
+
'new.q.mode.quick': '⚡ Rápido (recomendado): tudo pronto, zero configuração',
|
|
288
|
+
'new.q.mode.advanced': '🛠 Passo a passo: escolher cada detalhe',
|
|
279
289
|
|
|
280
290
|
'new.q.preset': 'Quais features incluir?',
|
|
281
291
|
'new.q.preset.starter': '⚡ Starter — analytics + erros + onboarding',
|
|
@@ -288,6 +298,40 @@ module.exports = {
|
|
|
288
298
|
'new.firebase.success.deployStep': '• Deploy do backend (de dentro da pasta do projeto):',
|
|
289
299
|
|
|
290
300
|
'cli.command.deploy.description': 'Publica o servidor no Firebase ou Supabase',
|
|
301
|
+
'cli.command.configure.description': 'Configura chaves opcionais (RevenueCat, Sentry, Mixpanel...) — pode pular, lembra do que falta',
|
|
302
|
+
'configure.title': 'Configuração de chaves do app',
|
|
303
|
+
'configure.notKasyProject': 'Esta pasta não parece um projeto Kasy (não encontrei pubspec.yaml). Entre na pasta do projeto e rode novamente.',
|
|
304
|
+
'configure.alreadyFilled': 'Já configurado ({count}):',
|
|
305
|
+
'configure.toFill': 'Falta configurar ({count}):',
|
|
306
|
+
'configure.skipHint': 'Pressione Enter sem digitar nada pra pular qualquer chave. Rode `kasy configure` de novo quando tiver a credencial.',
|
|
307
|
+
'configure.skipPlaceholder': '(deixe vazio pra pular)',
|
|
308
|
+
'configure.saved': 'Salvo em {path} ({count} chave(s) preenchida(s))',
|
|
309
|
+
'configure.stillPending': 'Restam {count} chave(s) pendente(s).',
|
|
310
|
+
'configure.runAgainHint': 'Rode `kasy configure` novamente quando tiver as chaves.',
|
|
311
|
+
'configure.allDone': 'Tudo configurado! Você pode rodar o app sem pendências de credenciais.',
|
|
312
|
+
'configure.allSkipped': 'Nenhuma chave preenchida agora. Rode novamente quando estiver pronto.',
|
|
313
|
+
'configure.outroSaved': '{count} chave(s) atualizada(s) no .env',
|
|
314
|
+
'configure.outroNoChange': 'Nenhuma alteração no .env',
|
|
315
|
+
'configure.aborted': 'Cancelado. Nada foi gravado.',
|
|
316
|
+
'configure.section.appStore': 'App Store',
|
|
317
|
+
'configure.section.sentry': 'Sentry (Crash Reports)',
|
|
318
|
+
'configure.section.mixpanel': 'Mixpanel (Analytics)',
|
|
319
|
+
'configure.section.revenuecat': 'RevenueCat (Assinaturas)',
|
|
320
|
+
'configure.section.facebook': 'Facebook (Login + Ads)',
|
|
321
|
+
'configure.section.llmChat': 'AI Chat (LLM)',
|
|
322
|
+
'configure.savedFacebook': 'Credenciais do Facebook gravadas em Info.plist e strings.xml.',
|
|
323
|
+
'configure.facebookPlistMissing': 'ios/Runner/Info.plist não encontrado — Facebook iOS não foi atualizado.',
|
|
324
|
+
'configure.facebookStringsMissing': 'android/.../strings.xml não encontrado — Facebook Android não foi atualizado.',
|
|
325
|
+
'configure.facebookNeedsAppId': 'Pra salvar credenciais do Facebook, precisa do App ID. Rode `kasy configure` novamente e preencha o App ID.',
|
|
326
|
+
'configure.noOptionalFeatures': 'Esse projeto não tem features opcionais que precisem de credenciais.',
|
|
327
|
+
'configure.statusSummary': '{filled} já configurada(s), {pending} pendente(s)',
|
|
328
|
+
'configure.alreadyDone': 'tudo certo',
|
|
329
|
+
'configure.alreadyFilledShort': 'já configurada',
|
|
330
|
+
'configure.savedEnv': '{count} chave(s) salva(s) no .env',
|
|
331
|
+
'configure.savedFnEnv': '{count} chave(s) salva(s) em functions/.env',
|
|
332
|
+
'configure.settingSecrets': 'Gravando {count} Firebase Secret(s)…',
|
|
333
|
+
'configure.savedSecrets': '{count} Firebase Secret(s) gravado(s)',
|
|
334
|
+
'configure.secretFailed': 'Não consegui gravar o secret {key}. Rode manualmente: firebase functions:secrets:set {key}',
|
|
291
335
|
'cli.command.check.description': 'Confere se as notificações push estão configuradas (use --fix para corrigir)',
|
|
292
336
|
'deploy.q.project': 'Firebase Project ID:',
|
|
293
337
|
'deploy.q.serviceAccount': 'Caminho para o service account JSON:',
|
|
@@ -531,6 +575,10 @@ module.exports = {
|
|
|
531
575
|
'new.firebase.confirm.modules': 'Features',
|
|
532
576
|
'new.firebase.confirm.none': 'nenhum',
|
|
533
577
|
'new.firebase.confirm.proceed': 'Criar o projeto agora?',
|
|
578
|
+
'new.advanced.section.config': 'Configuração do app',
|
|
579
|
+
'new.advanced.section.features': 'Funcionalidades',
|
|
580
|
+
'new.advanced.section.creds': 'Credenciais',
|
|
581
|
+
'new.advanced.q.configureCredsNow': 'Configurar credenciais agora? (pode deixar pra depois com `kasy configure`)',
|
|
534
582
|
|
|
535
583
|
'new.firebase.step.copying': 'Criando seu projeto...',
|
|
536
584
|
'new.firebase.step.pubGet': 'Instalando pacotes Flutter...',
|
|
@@ -629,13 +677,20 @@ module.exports = {
|
|
|
629
677
|
'new.outdated.upgradeNow': 'Atualizar antes de criar? (requer assinatura ativa)',
|
|
630
678
|
'new.outdated.upgraded': 'kasy atualizado! Rode kasy new novamente.',
|
|
631
679
|
'new.success.title': 'Projeto criado com sucesso!',
|
|
680
|
+
'new.success.featuresInstalled': 'Recursos ativados:',
|
|
632
681
|
'new.success.nextSteps': 'Proximos passos:',
|
|
633
682
|
'new.success.step.cd': 'Entre na pasta do projeto:',
|
|
634
|
-
'new.success.step.deploy': '
|
|
635
|
-
'new.success.step.
|
|
683
|
+
'new.success.step.deploy': 'Suba o servidor pro Firebase (banco + funções):',
|
|
684
|
+
'new.success.step.configure': 'Configure chaves opcionais quando tiver (RevenueCat, Sentry, etc.):',
|
|
685
|
+
'new.success.step.run': 'Rode o app:',
|
|
636
686
|
'new.success.step.run.vscode': '(ou F5 no VS Code)',
|
|
637
687
|
'new.success.step.console': 'Abra o console do backend:',
|
|
688
|
+
'new.success.step.docs': 'Documentação:',
|
|
638
689
|
'new.fcm.generating': 'Gerando chave de Service Account FCM…',
|
|
690
|
+
'new.google.enabling': 'Ativando login com Google…',
|
|
691
|
+
'new.google.refreshConfigs': 'Atualizando google-services.json e GoogleService-Info.plist com Client IDs do Google…',
|
|
692
|
+
'new.google.manualHint': 'Login com Google: ative manualmente no Console (provedor Google):',
|
|
693
|
+
'new.google.manualHint.noEmail': 'Login com Google: não consegui detectar um e-mail de suporte (gcloud sem conta). Ative manualmente no Console:',
|
|
639
694
|
'new.sha1.registering': 'Registrando SHA-1 para Google Sign-In (Android)…',
|
|
640
695
|
'new.sha1.failed': 'SHA-1 não adicionado automaticamente: {error}',
|
|
641
696
|
'new.sha1.manual': 'Adicione manualmente para o Google Sign-In funcionar no Android:',
|
|
@@ -654,7 +709,8 @@ module.exports = {
|
|
|
654
709
|
'new.apns.warning': '⚠ Push iOS: configure a APNs Key no Firebase Console',
|
|
655
710
|
'new.apns.step1': '1. Apple Developer Portal → Keys → criar APNs Key (.p8)',
|
|
656
711
|
'new.apns.step2': '2. Firebase Console → Cloud Messaging → app iOS → fazer upload da APNs Key',
|
|
657
|
-
'new.
|
|
712
|
+
'new.apns.hint': 'Push pra iOS (APNs Key) só funciona depois que você publicar pra iOS. Passo a passo: https://kasy.dev/docs/apns',
|
|
713
|
+
'new.firebase.create.estimatedTime': '(3-5 min — precisa de internet estável, não feche o terminal)',
|
|
658
714
|
'new.internet.warning': '📶 Verifique se você está com uma internet estável — esta etapa precisa de conexão.',
|
|
659
715
|
|
|
660
716
|
// run command
|
|
@@ -715,6 +771,7 @@ module.exports = {
|
|
|
715
771
|
'run.rc.forcedTest': 'RevenueCat: --rc=test forçado',
|
|
716
772
|
'run.rc.forcedProd': 'RevenueCat: --rc=prod forçado',
|
|
717
773
|
'run.rc.forcedProdMissing': 'RevenueCat: --rc=prod pedido mas RC_IOS_PROD_KEY/RC_ANDROID_PROD_KEY não configuradas no .env',
|
|
774
|
+
'run.log.savedTo': 'Saída completa em {path} (útil para colar em IA/assistente)',
|
|
718
775
|
|
|
719
776
|
// doctor project checks
|
|
720
777
|
'doctor.project.title': 'Projeto',
|
package/lib/utils/ui.js
CHANGED
|
@@ -91,8 +91,20 @@ function cancel(message) { clack.cancel(message); }
|
|
|
91
91
|
* Spinner integrated with Clack's vertical line (│).
|
|
92
92
|
* Returns: { start(msg), message(msg), stop(msg, code?) }
|
|
93
93
|
* code: 0 = success (✦), 1 = cancel (■), 2 = error (▲)
|
|
94
|
+
*
|
|
95
|
+
* Optional `color` paints every text passed through (start/message/stop) —
|
|
96
|
+
* pass `paintLime` (from utils/brand) for the Kasy brand color.
|
|
94
97
|
*/
|
|
95
|
-
function spinner(
|
|
98
|
+
function spinner({ color } = {}) {
|
|
99
|
+
const s = clack.spinner();
|
|
100
|
+
if (typeof color !== 'function') return s;
|
|
101
|
+
return {
|
|
102
|
+
start(msg) { s.start(msg != null ? color(msg) : msg); },
|
|
103
|
+
message(msg) { s.message(msg != null ? color(msg) : msg); },
|
|
104
|
+
stop(msg, code) { s.stop(msg != null ? color(msg) : msg, code); },
|
|
105
|
+
error(msg) { s.error(msg != null ? color(msg) : msg); },
|
|
106
|
+
};
|
|
107
|
+
}
|
|
96
108
|
|
|
97
109
|
/**
|
|
98
110
|
* Spinner with an automatic elapsed-time suffix that ticks every second.
|
|
@@ -104,16 +116,18 @@ function spinner() { return clack.spinner(); }
|
|
|
104
116
|
* // ... 73 seconds later
|
|
105
117
|
* s.stop('Deploy done'); // "Deploy done [1m 13s]"
|
|
106
118
|
*/
|
|
107
|
-
function timedSpinner() {
|
|
119
|
+
function timedSpinner({ color } = {}) {
|
|
108
120
|
const s = clack.spinner();
|
|
121
|
+
const paint = typeof color === 'function' ? color : (t) => t;
|
|
109
122
|
let startTime = null;
|
|
110
123
|
let currentMessage = '';
|
|
111
124
|
let tick = null;
|
|
112
125
|
|
|
113
126
|
const render = (msg) => {
|
|
114
127
|
if (!msg) return '';
|
|
115
|
-
|
|
116
|
-
|
|
128
|
+
const painted = paint(msg);
|
|
129
|
+
if (!startTime) return painted;
|
|
130
|
+
return `${painted} ${kleur.dim(`[${formatElapsedSeconds(startTime)}]`)}`;
|
|
117
131
|
};
|
|
118
132
|
|
|
119
133
|
const stopTick = () => {
|
|
@@ -197,6 +211,66 @@ function makeTimedStepper() {
|
|
|
197
211
|
};
|
|
198
212
|
}
|
|
199
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Single-line stepper for Quick mode. Same API as makeTimedStepper, but every
|
|
216
|
+
* `.next(text)` mutates the message of a single underlying spinner instead of
|
|
217
|
+
* closing it and opening a new one — so the user sees one line that keeps
|
|
218
|
+
* updating ("Creating Firebase project…" → "Waiting for propagation…" → …)
|
|
219
|
+
* with a running total timer instead of a tall stack of step lines.
|
|
220
|
+
*
|
|
221
|
+
* Optional `color` paints the running text (lime in Kasy Quick mode). The
|
|
222
|
+
* final success/fail message keeps the caller-supplied formatting untouched.
|
|
223
|
+
*/
|
|
224
|
+
function makeQuickStepper({ color } = {}) {
|
|
225
|
+
const paint = typeof color === 'function' ? color : (t) => t;
|
|
226
|
+
const s = timedSpinner();
|
|
227
|
+
let started = false;
|
|
228
|
+
let currentMsg = '';
|
|
229
|
+
return {
|
|
230
|
+
next(text) {
|
|
231
|
+
currentMsg = text;
|
|
232
|
+
if (!started) {
|
|
233
|
+
s.start(paint(text));
|
|
234
|
+
started = true;
|
|
235
|
+
} else {
|
|
236
|
+
s.message(paint(text));
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
update(text) {
|
|
240
|
+
currentMsg = text;
|
|
241
|
+
if (started) s.message(paint(text));
|
|
242
|
+
},
|
|
243
|
+
succeed(text) {
|
|
244
|
+
if (started) {
|
|
245
|
+
s.stop(paint(text || currentMsg));
|
|
246
|
+
started = false;
|
|
247
|
+
currentMsg = '';
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
fail(text) {
|
|
251
|
+
if (started) {
|
|
252
|
+
s.error(text || currentMsg);
|
|
253
|
+
started = false;
|
|
254
|
+
currentMsg = '';
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
warn(text) {
|
|
258
|
+
if (started) {
|
|
259
|
+
s.stop(`⚠ ${text || currentMsg}`);
|
|
260
|
+
started = false;
|
|
261
|
+
currentMsg = '';
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
stop() {
|
|
265
|
+
if (started) {
|
|
266
|
+
s.stop(paint(currentMsg));
|
|
267
|
+
started = false;
|
|
268
|
+
currentMsg = '';
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
200
274
|
/**
|
|
201
275
|
* Multi-step spinner: each .next(text) succeeds the previous step
|
|
202
276
|
* with the previous message, then starts a new step with `text`.
|
|
@@ -281,6 +355,7 @@ module.exports = {
|
|
|
281
355
|
timedSpinner,
|
|
282
356
|
makeStepper,
|
|
283
357
|
makeTimedStepper,
|
|
358
|
+
makeQuickStepper,
|
|
284
359
|
taskLog,
|
|
285
360
|
progress,
|
|
286
361
|
log,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kasy-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.19.0",
|
|
4
4
|
"description": "CLI for scaffolding production-ready Flutter SaaS apps with Firebase, Supabase, or API REST backends.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"kasy": "./bin/kasy.js"
|
|
@@ -51,7 +51,6 @@
|
|
|
51
51
|
"boxen": "^8.0.1",
|
|
52
52
|
"commander": "^12.0.0",
|
|
53
53
|
"fs-extra": "^11.2.0",
|
|
54
|
-
"gradient-string": "^1.2.0",
|
|
55
54
|
"kleur": "^4.1.5",
|
|
56
55
|
"pngjs": "^7.0.0",
|
|
57
56
|
"yaml": "^2.4.2"
|
|
@@ -185,6 +185,6 @@ No Mac: [docs/codemagic-release.md](docs/codemagic-release.md)
|
|
|
185
185
|
|
|
186
186
|
## Security
|
|
187
187
|
|
|
188
|
-
`.gitignore` already excludes: `firebase_key.json`, `.env`, `.env.*`, `*.pem`, `*.keystore`, `.kasy/apple.env`, `.kasy/codemagic.env`.
|
|
188
|
+
`.gitignore` already excludes: `firebase_key.json`, `.env`, `.env.*`, `*.pem`, `*.keystore`, `.kasy/apple.env`, `.kasy/codemagic.env`, `.kasy/*.log`.
|
|
189
189
|
|
|
190
190
|
Never commit credentials to the repository.
|
|
@@ -185,6 +185,6 @@ Sin Mac: [docs/codemagic-release.md](docs/codemagic-release.md)
|
|
|
185
185
|
|
|
186
186
|
## Seguridad
|
|
187
187
|
|
|
188
|
-
El `.gitignore` ya excluye: `firebase_key.json`, `.env`, `.env.*`, `*.pem`, `*.keystore`, `.kasy/apple.env`, `.kasy/codemagic.env`.
|
|
188
|
+
El `.gitignore` ya excluye: `firebase_key.json`, `.env`, `.env.*`, `*.pem`, `*.keystore`, `.kasy/apple.env`, `.kasy/codemagic.env`, `.kasy/*.log`.
|
|
189
189
|
|
|
190
190
|
Nunca subas credenciales al repositorio.
|
|
@@ -185,6 +185,6 @@ Sem Mac: [docs/codemagic-release.md](docs/codemagic-release.md)
|
|
|
185
185
|
|
|
186
186
|
## Segurança
|
|
187
187
|
|
|
188
|
-
O `.gitignore` já exclui: `firebase_key.json`, `.env`, `.env.*`, `*.pem`, `*.keystore`, `.kasy/apple.env`, `.kasy/codemagic.env`.
|
|
188
|
+
O `.gitignore` já exclui: `firebase_key.json`, `.env`, `.env.*`, `*.pem`, `*.keystore`, `.kasy/apple.env`, `.kasy/codemagic.env`, `.kasy/*.log`.
|
|
189
189
|
|
|
190
190
|
Nunca comite credenciais no repositório.
|
package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MainActivity.kt
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
package com.aicrus.firebase.kit
|
|
2
2
|
|
|
3
3
|
import android.content.Context
|
|
4
|
-
import android.content.Intent
|
|
5
4
|
import android.os.Bundle
|
|
6
|
-
import android.util.Log
|
|
7
5
|
import androidx.appcompat.app.AppCompatDelegate
|
|
8
6
|
import com.google.android.gms.ads.identifier.AdvertisingIdClient
|
|
9
7
|
import io.flutter.embedding.android.FlutterActivity
|
|
@@ -22,19 +20,6 @@ class MainActivity : FlutterActivity() {
|
|
|
22
20
|
super.onCreate(savedInstanceState)
|
|
23
21
|
}
|
|
24
22
|
|
|
25
|
-
// Logs the incoming intent so we can diagnose warm-starts from the home
|
|
26
|
-
// widget that land on go_router's 404. With this, the Dart side's onException
|
|
27
|
-
// logs and these adb logs together show whether the intent itself carries a
|
|
28
|
-
// bad URI (data Uri / extras) or whether the 404 comes from elsewhere.
|
|
29
|
-
override fun onNewIntent(intent: Intent) {
|
|
30
|
-
Log.d(
|
|
31
|
-
"KasyWidgetTap",
|
|
32
|
-
"onNewIntent action=${intent.action} data=${intent.data} " +
|
|
33
|
-
"flags=0x${Integer.toHexString(intent.flags)} extras=${intent.extras}",
|
|
34
|
-
)
|
|
35
|
-
super.onNewIntent(intent)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
23
|
// Forces the night mode to match the user's saved theme preference (read
|
|
39
24
|
// from `shared_preferences`) so the native splash drawable selection
|
|
40
25
|
// (drawable-night vs drawable) follows the in-app choice, not just the OS.
|
|
@@ -5,6 +5,7 @@ import androidx.compose.runtime.Composable
|
|
|
5
5
|
import androidx.compose.ui.graphics.Color
|
|
6
6
|
import androidx.compose.ui.unit.dp
|
|
7
7
|
import androidx.compose.ui.unit.sp
|
|
8
|
+
import androidx.core.content.ContextCompat
|
|
8
9
|
import androidx.glance.GlanceId
|
|
9
10
|
import androidx.glance.GlanceModifier
|
|
10
11
|
import androidx.glance.Image
|
|
@@ -35,7 +36,6 @@ import androidx.glance.text.TextStyle
|
|
|
35
36
|
import androidx.glance.unit.ColorProvider
|
|
36
37
|
import es.antonborri.home_widget.HomeWidgetGlanceState
|
|
37
38
|
import es.antonborri.home_widget.HomeWidgetGlanceStateDefinition
|
|
38
|
-
import java.util.Calendar
|
|
39
39
|
import java.util.Locale
|
|
40
40
|
|
|
41
41
|
class MyWidgetWidget : GlanceAppWidget() {
|
|
@@ -61,24 +61,50 @@ class MyWidgetWidget : GlanceAppWidget() {
|
|
|
61
61
|
val planText = prefs.getString("planText", "") ?: ""
|
|
62
62
|
val isPro = prefs.getString("isPro", "false") == "true"
|
|
63
63
|
val quote = prefs.getString("quote", "") ?: ""
|
|
64
|
+
val quoteAuthor = prefs.getString("quoteAuthor", "") ?: ""
|
|
64
65
|
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
//
|
|
66
|
+
// Fallback used when Flutter has not pushed data yet — first install
|
|
67
|
+
// before the app opens. Keeps the widget from rendering blank in the
|
|
68
|
+
// gallery preview.
|
|
68
69
|
val defaults = defaultStrings()
|
|
69
70
|
val greeting = storedGreeting.ifEmpty { defaults.greeting }
|
|
70
71
|
val title = storedTitle.ifEmpty { defaults.hello }
|
|
71
72
|
|
|
72
73
|
val size = LocalSize.current
|
|
73
|
-
|
|
74
|
-
//
|
|
75
|
-
val
|
|
74
|
+
// "Narrow" covers both the true small widget and the tall-but-narrow
|
|
75
|
+
// single-column shape — both get the tightest sentence count.
|
|
76
|
+
val isSmall = size.width < 240.dp
|
|
77
|
+
// We show N COMPLETE sentences from the quote (split on "\n" — which is
|
|
78
|
+
// how the i18n JSON separates the four sentences). Truncating per
|
|
79
|
+
// sentence avoids the ugly "…" mid-word that lineLimit alone produces.
|
|
80
|
+
// height < 180dp → no quote.
|
|
81
|
+
// narrow column → 1 sentence (just the first one).
|
|
82
|
+
// wide + short → 2 sentences.
|
|
83
|
+
// wide + medium height → 3 sentences.
|
|
84
|
+
// wide + tall → 4 sentences + bold author.
|
|
85
|
+
val showQuote = size.height >= 180.dp
|
|
86
|
+
val numSentences = when {
|
|
87
|
+
isSmall -> 1
|
|
88
|
+
size.height < 240.dp -> 2
|
|
89
|
+
size.height < 300.dp -> 3
|
|
90
|
+
else -> 4
|
|
91
|
+
}
|
|
92
|
+
val showQuoteAuthor = !isSmall && size.height >= 300.dp
|
|
93
|
+
val sentences = quote.split("\n").filter { it.isNotBlank() }
|
|
94
|
+
val displayQuote = sentences.take(numSentences).joinToString("\n")
|
|
95
|
+
// Each sentence is short enough to wrap to 2 lines on narrow widgets,
|
|
96
|
+
// so a 2x cap covers the worst case without slicing the last sentence.
|
|
97
|
+
val quoteMaxLines = numSentences * 2
|
|
76
98
|
|
|
99
|
+
// Brand colors. The pure-Compose colors (whiteSubtle, whiteQuote,
|
|
100
|
+
// whiteVerySubtle) are foreground tints used by the live widget only,
|
|
101
|
+
// so they stay inline. The gold pulls from res/values/colors.xml to keep
|
|
102
|
+
// a single source of truth shared with the XML drawables.
|
|
77
103
|
val white = Color.White
|
|
78
104
|
val whiteSubtle = Color(red = 1f, green = 1f, blue = 1f, alpha = 0.55f)
|
|
79
105
|
val whiteQuote = Color(red = 1f, green = 1f, blue = 1f, alpha = 0.65f)
|
|
80
|
-
val gold = Color(red = 1f, green = 0.84f, blue = 0f)
|
|
81
106
|
val whiteVerySubtle = Color(red = 1f, green = 1f, blue = 1f, alpha = 0.45f)
|
|
107
|
+
val gold = Color(ContextCompat.getColor(context, R.color.widget_pro_gold))
|
|
82
108
|
|
|
83
109
|
// The gradient lives in its own Image at the bottom of the stack rather
|
|
84
110
|
// than as `.background(ImageProvider(...))`, because in some Glance
|
|
@@ -116,18 +142,37 @@ class MyWidgetWidget : GlanceAppWidget() {
|
|
|
116
142
|
),
|
|
117
143
|
modifier = GlanceModifier.padding(top = 4.dp),
|
|
118
144
|
)
|
|
119
|
-
if (
|
|
145
|
+
if (showQuote && displayQuote.isNotEmpty()) {
|
|
120
146
|
Text(
|
|
121
|
-
text =
|
|
147
|
+
text = displayQuote,
|
|
122
148
|
style = TextStyle(
|
|
123
149
|
color = ColorProvider(whiteQuote),
|
|
124
150
|
fontSize = 15.sp,
|
|
125
151
|
fontWeight = FontWeight.Normal,
|
|
126
152
|
fontStyle = FontStyle.Italic,
|
|
127
153
|
),
|
|
128
|
-
maxLines =
|
|
154
|
+
maxLines = quoteMaxLines,
|
|
129
155
|
modifier = GlanceModifier.padding(top = 12.dp),
|
|
130
156
|
)
|
|
157
|
+
if (showQuoteAuthor && quoteAuthor.isNotEmpty()) {
|
|
158
|
+
// Author goes on its own line, right-aligned and bold, so the
|
|
159
|
+
// quote reads as a quote and the attribution is visually distinct.
|
|
160
|
+
Row(
|
|
161
|
+
modifier = GlanceModifier
|
|
162
|
+
.fillMaxWidth()
|
|
163
|
+
.padding(top = 4.dp),
|
|
164
|
+
horizontalAlignment = Alignment.End,
|
|
165
|
+
) {
|
|
166
|
+
Text(
|
|
167
|
+
text = quoteAuthor,
|
|
168
|
+
style = TextStyle(
|
|
169
|
+
color = ColorProvider(whiteQuote),
|
|
170
|
+
fontSize = 13.sp,
|
|
171
|
+
fontWeight = FontWeight.Bold,
|
|
172
|
+
),
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
131
176
|
}
|
|
132
177
|
Spacer(modifier = GlanceModifier.defaultWeight())
|
|
133
178
|
Row(
|
|
@@ -200,22 +245,16 @@ class MyWidgetWidget : GlanceAppWidget() {
|
|
|
200
245
|
}
|
|
201
246
|
}
|
|
202
247
|
|
|
248
|
+
// Fallback used ONLY in the brief window between the widget being placed and
|
|
249
|
+
// the Flutter app pushing real values. Keep this dead simple — the
|
|
250
|
+
// time-aware greeting in three languages lives on the Dart side
|
|
251
|
+
// (home_widget_mywidget_service.dart::_greeting). Duplicating that logic
|
|
252
|
+
// here was a maintenance trap when adding new locales.
|
|
203
253
|
private data class DefaultStrings(val greeting: String, val hello: String)
|
|
204
254
|
|
|
205
|
-
private fun defaultStrings(): DefaultStrings {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
when (lang) {
|
|
210
|
-
"pt" -> { morning = "Bom dia"; afternoon = "Boa tarde"; evening = "Boa noite"; hello = "Olá!" }
|
|
211
|
-
"es" -> { morning = "Buenos días"; afternoon = "Buenas tardes"; evening = "Buenas noches"; hello = "¡Hola!" }
|
|
212
|
-
else -> { morning = "Good morning"; afternoon = "Good afternoon"; evening = "Good evening"; hello = "Hi there!" }
|
|
213
|
-
}
|
|
214
|
-
val greeting = when {
|
|
215
|
-
hour < 12 -> morning
|
|
216
|
-
hour < 18 -> afternoon
|
|
217
|
-
else -> evening
|
|
218
|
-
}
|
|
219
|
-
return DefaultStrings(greeting, hello)
|
|
255
|
+
private fun defaultStrings(): DefaultStrings = when (Locale.getDefault().language) {
|
|
256
|
+
"pt" -> DefaultStrings(greeting = "Olá", hello = "Bem-vindo!")
|
|
257
|
+
"es" -> DefaultStrings(greeting = "Hola", hello = "¡Bienvenido!")
|
|
258
|
+
else -> DefaultStrings(greeting = "Hello", hello = "Welcome!")
|
|
220
259
|
}
|
|
221
260
|
}
|
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
android:viewportHeight="34">
|
|
7
7
|
<path
|
|
8
8
|
android:pathData="M17,0 A17,17 0 1,0 17,34 A17,17 0 1,0 17,0 Z"
|
|
9
|
-
android:fillColor="
|
|
9
|
+
android:fillColor="@color/widget_add_button_bg"/>
|
|
10
10
|
<path
|
|
11
11
|
android:pathData="M17,9 L17,25 M9,17 L25,17"
|
|
12
|
-
android:strokeColor="
|
|
12
|
+
android:strokeColor="@color/widget_add_button_stroke"
|
|
13
13
|
android:strokeWidth="2.5"
|
|
14
14
|
android:strokeLineCap="round"/>
|
|
15
15
|
</vector>
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<!-- Background used by the static preview / loading layouts (RemoteViews
|
|
3
|
+
XML rendered by the launcher BEFORE Glance composes the real widget).
|
|
4
|
+
Includes the corner radius because the launcher doesn't clip these
|
|
5
|
+
layouts the same way it clips the live Glance widget.
|
|
6
|
+
To restyle the brand gradient, edit res/values/colors.xml. -->
|
|
2
7
|
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
|
3
8
|
<gradient
|
|
4
9
|
android:angle="315"
|
|
5
|
-
android:startColor="
|
|
6
|
-
android:endColor="
|
|
10
|
+
android:startColor="@color/widget_gradient_start"
|
|
11
|
+
android:endColor="@color/widget_gradient_end"
|
|
7
12
|
android:type="linear" />
|
|
8
13
|
<corners android:radius="24dp" />
|
|
9
14
|
</shape>
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
-
<!--
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
<!-- Background of the live Glance widget. No corners here — the OS already
|
|
3
|
+
clips the widget with its corner radius, so adding rounded corners
|
|
4
|
+
would produce a visible double-radius edge. To restyle the brand
|
|
5
|
+
gradient, edit res/values/colors.xml. -->
|
|
6
6
|
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
|
7
7
|
<gradient
|
|
8
8
|
android:angle="315"
|
|
9
|
-
android:startColor="
|
|
10
|
-
android:endColor="
|
|
9
|
+
android:startColor="@color/widget_gradient_start"
|
|
10
|
+
android:endColor="@color/widget_gradient_end"
|
|
11
11
|
android:type="linear" />
|
|
12
12
|
</shape>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<?xml version="1.0" encoding="utf-8"?>
|
|
2
2
|
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
|
3
3
|
<corners android:radius="999dp"/>
|
|
4
|
-
<solid android:color="
|
|
4
|
+
<solid android:color="@color/widget_free_pill_bg"/>
|
|
5
5
|
</shape>
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
<shape android:shape="rectangle">
|
|
9
9
|
<gradient
|
|
10
10
|
android:angle="315"
|
|
11
|
-
android:startColor="
|
|
12
|
-
android:endColor="
|
|
11
|
+
android:startColor="@color/widget_gradient_start"
|
|
12
|
+
android:endColor="@color/widget_gradient_end"
|
|
13
13
|
android:type="linear" />
|
|
14
14
|
<corners android:radius="24dp" />
|
|
15
15
|
</shape>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<?xml version="1.0" encoding="utf-8"?>
|
|
2
2
|
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
|
3
3
|
<corners android:radius="999dp"/>
|
|
4
|
-
<solid android:color="
|
|
4
|
+
<solid android:color="@color/widget_pro_pill_bg"/>
|
|
5
5
|
</shape>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/templates/firebase/android/app/src/main/res/drawable-night-xhdpi/android12splash.png
CHANGED
|
Binary file
|
|
Binary file
|
package/templates/firebase/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png
CHANGED
|
Binary file
|
|
Binary file
|
package/templates/firebase/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|