kasy-cli 1.8.1 → 1.9.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 CHANGED
@@ -107,14 +107,55 @@ function createLocalizedHelpConfig(t) {
107
107
  }
108
108
  }
109
109
 
110
- const commandList = helper.visibleCommands(cmd).map((subcommand) => {
111
- return formatItem(
112
- localizeSubcommandTerm(helper.subcommandTerm(subcommand)),
113
- helper.subcommandDescription(subcommand)
114
- );
115
- });
116
- if (commandList.length > 0) {
117
- output = output.concat([`${t('cli.help.heading.commands')}:`, formatList(commandList), '']);
110
+ const visibleCommands = helper.visibleCommands(cmd);
111
+ if (visibleCommands.length > 0) {
112
+ const isRoot = cmd.parent === null || cmd.parent === undefined;
113
+ if (isRoot) {
114
+ // Group root commands by intent for easier scanning by non-devs.
115
+ const groups = [
116
+ { id: 'start', ids: ['new', 'doctor', 'features'] },
117
+ { id: 'work', ids: ['add', 'remove', 'update', 'run'] },
118
+ { id: 'publish', ids: ['deploy', 'check', 'ios', 'codemagic'] },
119
+ { id: 'maintenance', ids: ['upgrade', 'version', 'uninstall', 'docs', 'notifications'] },
120
+ { id: 'advanced', ids: ['setup', 'validate'] },
121
+ ];
122
+ const knownIds = new Set(groups.flatMap((g) => g.ids));
123
+ const byName = new Map(visibleCommands.map((c) => [c.name(), c]));
124
+ for (const group of groups) {
125
+ const items = group.ids
126
+ .map((id) => byName.get(id))
127
+ .filter(Boolean)
128
+ .map((sub) =>
129
+ formatItem(
130
+ localizeSubcommandTerm(helper.subcommandTerm(sub)),
131
+ helper.subcommandDescription(sub)
132
+ )
133
+ );
134
+ if (items.length === 0) continue;
135
+ output = output.concat([`${t(`cli.help.group.${group.id}`)}:`, formatList(items), '']);
136
+ }
137
+ const otherItems = visibleCommands
138
+ .filter((c) => !knownIds.has(c.name()))
139
+ .map((sub) =>
140
+ formatItem(
141
+ localizeSubcommandTerm(helper.subcommandTerm(sub)),
142
+ helper.subcommandDescription(sub)
143
+ )
144
+ );
145
+ if (otherItems.length > 0) {
146
+ output = output.concat([`${t('cli.help.group.other')}:`, formatList(otherItems), '']);
147
+ }
148
+ output = output.concat([helper.wrap(t('cli.help.tip'), helpWidth, 0), '']);
149
+ } else {
150
+ // Subcommand help — keep the flat list (no grouping).
151
+ const commandList = visibleCommands.map((subcommand) =>
152
+ formatItem(
153
+ localizeSubcommandTerm(helper.subcommandTerm(subcommand)),
154
+ helper.subcommandDescription(subcommand)
155
+ )
156
+ );
157
+ output = output.concat([`${t('cli.help.heading.commands')}:`, formatList(commandList), '']);
158
+ }
118
159
  }
119
160
 
120
161
  return output.join('\n');
@@ -1,4 +1,13 @@
1
1
  {
2
+ "1.8.0": {
3
+ "modules": {
4
+ "components": {
5
+ "pt": "Novo componente KasySwipeAction (arrastar para excluir/arquivar) + tela de notificações com excluir tudo e swipe-to-delete",
6
+ "en": "New KasySwipeAction component (swipe to delete/archive) + notifications screen with delete-all and swipe-to-delete",
7
+ "es": "Nuevo componente KasySwipeAction (deslizar para eliminar/archivar) + pantalla de notificaciones con eliminar todo y swipe-to-delete"
8
+ }
9
+ }
10
+ },
2
11
  "1.5.0": {
3
12
  "modules": {
4
13
  "widget": {
package/lib/utils/i18n.js CHANGED
@@ -22,8 +22,15 @@ const MESSAGES = {
22
22
  'cli.help.heading.options': 'Options',
23
23
  'cli.help.heading.globalOptions': 'Global Options',
24
24
  'cli.help.heading.commands': 'Commands',
25
- 'cli.command.setup.description': '📱 Set up a new Flutter app template',
26
- 'cli.command.new.description': ' Create a new app',
25
+ 'cli.help.group.start': '🚀 Get started',
26
+ 'cli.help.group.work': '🛠 Work on your app',
27
+ 'cli.help.group.publish': '📤 Publish & test',
28
+ 'cli.help.group.maintenance': '⚙️ Kasy CLI maintenance',
29
+ 'cli.help.group.advanced': '🔧 Advanced',
30
+ 'cli.help.group.other': 'Other',
31
+ 'cli.help.tip': 'Tip: run `kasy <command> --help` for details. Use --lang pt|en|es to switch language.',
32
+ 'cli.command.setup.description': '📱 (advanced) Set up an existing Flutter project',
33
+ 'cli.command.new.description': '✨ Create a new app (e.g.: kasy new my-app)',
27
34
  'cli.command.new.projectName': 'project name',
28
35
  'cli.command.new.projectNameArg': 'Project folder name',
29
36
  'prompt.projectName.enter': "What's your project name?",
@@ -36,13 +43,13 @@ const MESSAGES = {
36
43
  'cli.command.setup.backendOption': 'Backend adapter (firebase, supabase, api)',
37
44
  'cli.command.setup.featuresOption': 'Comma separated optional features (web,widget,llm_chat,revenuecat,ci)',
38
45
  'cli.command.help.paramName': 'command',
39
- 'cli.command.doctor.description': '🩺 Check environment and dependencies',
40
- 'cli.command.modules.description': '🧩 List available backend and features',
41
- 'cli.command.validate.description': '✅ Validate minimum backend/feature combinations',
46
+ 'cli.command.doctor.description': '🩺 Check if your computer is ready to run Kasy',
47
+ 'cli.command.modules.description': '🧩 Show what comes included and what you can add',
48
+ 'cli.command.validate.description': '✅ (advanced) Validate backend + feature combinations',
42
49
  'cli.command.validate.analyzeOnlyOption': 'Run flutter analyze only (skip builds)',
43
- 'cli.command.version.description': '🏷️ Print installed CLI version',
44
- 'cli.command.upgrade.description': '⬆️ Update the kasy CLI to the latest version',
45
- 'cli.command.uninstall.description': '🗑️ Uninstall the kasy CLI from this machine',
50
+ 'cli.command.version.description': '🏷️ Show the installed Kasy version',
51
+ 'cli.command.upgrade.description': '⬆️ Update the Kasy CLI to the latest version',
52
+ 'cli.command.uninstall.description': '🗑️ Uninstall Kasy from this computer',
46
53
  'cli.command.upgrade.running': 'Updating kasy CLI...',
47
54
  'cli.command.upgrade.done': 'kasy updated successfully!',
48
55
  'cli.command.uninstall.running': 'Uninstalling kasy CLI...',
@@ -264,20 +271,20 @@ const MESSAGES = {
264
271
 
265
272
  'new.firebase.success.deployStep': '• Deploy backend (from inside the project folder):',
266
273
 
267
- 'cli.command.deploy.description': 'Deploy backend (Firebase or Supabase) — auto-detects project and configures FCM',
268
- 'cli.command.check.description': '🔔 Check push notifications setup (use --fix to auto-configure)',
274
+ 'cli.command.deploy.description': '📤 Publish the server to Firebase or Supabase',
275
+ 'cli.command.check.description': '🔔 Check push notifications setup (use --fix to fix it)',
269
276
  'deploy.q.project': 'Firebase Project ID:',
270
277
  'deploy.q.serviceAccount': 'Path to service account JSON:',
271
278
  'deploy.detected.project': '✓ Firebase project detected:',
272
279
  'deploy.detected.serviceAccount': '✓ Service account detected:',
273
280
  'deploy.error.notProject': 'No firebase.json found in this directory. Run kasy deploy from inside the project folder.',
274
281
 
275
- 'cli.command.ios.description': 'iOS App Store release (Mac)',
282
+ 'cli.command.ios.description': '🍎 Publish the app to the App Store (Mac needed)',
276
283
  'cli.command.ios.configure.description': 'Configure Apple API credentials for local IPA upload',
277
284
  'cli.command.ios.release.description': 'Bump build, create IPA, and upload to App Store Connect',
278
285
  'cli.command.ios.build.description': 'Build IPA only (no upload)',
279
286
  'cli.command.ios.clean.description': 'Clean Flutter/Xcode caches before a failed iOS build',
280
- 'cli.command.codemagic.description': 'Trigger iOS builds on Codemagic (cloud)',
287
+ 'cli.command.codemagic.description': '☁️ Build the app in the cloud (no Mac needed)',
281
288
  'cli.command.codemagic.configure.description': 'Configure Codemagic API credentials',
282
289
  'cli.command.codemagic.release.description': 'Start a Codemagic iOS workflow build',
283
290
  'cli.command.codemagic.status.description': 'Show Codemagic build status by ID',
@@ -594,7 +601,7 @@ const MESSAGES = {
594
601
  'new.internet.warning': '📶 Make sure you have a stable internet connection — this step requires network access.',
595
602
 
596
603
  // run command
597
- 'cli.command.run.description': '▶ Run the Flutter app using .env (launch.json dart-defines fallback)',
604
+ 'cli.command.run.description': '▶ Run your app on phone, simulator, or browser',
598
605
  'run.launching': 'Launching Flutter app...',
599
606
  'run.updateHint.prefix': 'Project improvements available —',
600
607
  'run.updateHint.suffix': 'to see what\'s new',
@@ -612,13 +619,13 @@ const MESSAGES = {
612
619
  'doctor.project.noModules': 'No optional features active',
613
620
 
614
621
  // add command
615
- 'cli.command.add.description': '➕ Add a feature to an existing project',
622
+ 'cli.command.add.description': '➕ Add a new feature to your app (e.g.: kasy add ai_chat)',
616
623
 
617
624
  // docs command
618
- 'cli.command.docs.description': '📖 Show CLI documentation and available commands',
625
+ 'cli.command.docs.description': '📖 Open the full documentation',
619
626
 
620
627
  // notifications command
621
- 'cli.command.notifications.description': '🔔 Local notification copy (scheduled reminders + Home demo)',
628
+ 'cli.command.notifications.description': '🔔 Edit the text of local notifications and reminders',
622
629
  'cli.command.notifications.text.description': 'Set titles and bodies shown in local notifications',
623
630
  'notifications.error.notKasyProject': 'No kit_setup.json found. Run this command from inside a Kasy project.',
624
631
  'notifications.error.noI18n': 'lib/i18n/*.i18n.json not found in this project.',
@@ -682,7 +689,7 @@ const MESSAGES = {
682
689
  'add.llm_chat.nextSteps.supabase': '\n Next steps:\n 1. The LLM_CHAT_ENDPOINT in .vscode/launch.json has been pre-filled.\n 2. Run the app: kasy run\n',
683
690
  'add.llm_chat.nextSteps.supabase.deployFailed': '\n Next steps:\n 1. Deploy manually: supabase functions deploy llm-chat --no-verify-jwt\n 2. The LLM_CHAT_ENDPOINT in .vscode/launch.json has been pre-filled.\n 3. Run the app: kasy run\n',
684
691
  'add.llm_chat.nextSteps.api': '\n Next steps:\n 1. Create an endpoint on your server that accepts {message, history} and calls your LLM.\n 2. Update LLM_CHAT_ENDPOINT in .vscode/launch.json with your endpoint URL.\n 3. Run the app: kasy run\n',
685
- 'cli.command.remove.description': '🗑️ Remove a feature from an existing project',
692
+ 'cli.command.remove.description': '🗑️ Remove a feature you no longer use (e.g.: kasy remove sentry)',
686
693
  'remove.error.noModule': 'Provide a feature name. Usage: kasy remove <feature>',
687
694
  'remove.error.notKasyProject': 'No kit_setup.json found. Run this command from inside a Kasy project.',
688
695
  'remove.error.unknownModule': 'Unknown feature: {module}\nAvailable: {list}',
@@ -699,7 +706,7 @@ const MESSAGES = {
699
706
  'remove.success': 'Feature "{module}" removed successfully.',
700
707
  'remove.warn.ci': 'CI files removed. If you had custom workflow files, restore them from git.',
701
708
  'remove.warn.sentry.shared': 'sentry_flutter kept — still required by active features (revenuecat/facebook).',
702
- 'cli.command.update.description': '⬆️ Update features/components in an existing project',
709
+ 'cli.command.update.description': '⬆️ Update parts of your app to the latest (e.g.: kasy update components)',
703
710
  'cli.command.update.targetArg': 'Target to update (e.g. revenuecat, sentry, components)',
704
711
  'update.error.noProject': 'No kit_setup.json found. Run this command from inside a Kasy project.',
705
712
  'update.error.unknownModule': 'Unknown feature: {module}\nAvailable: {list}',
@@ -742,40 +749,47 @@ const MESSAGES = {
742
749
  'cli.tagline': 'Crie apps móveis sem dor de configuração',
743
750
  'cli.description': 'Crie apps Flutter sem dor de configuração.',
744
751
  'cli.usage': '<comando> [argumentos]',
745
- 'cli.option.version': 'Mostrar versao instalada',
752
+ 'cli.option.version': 'Mostrar versão instalada',
746
753
  'cli.option.lang': 'Idioma (en, pt, es)',
747
754
  'cli.help.afterError': '(execute com --help para detalhes de uso)',
748
- 'cli.help.more': 'Execute "{command}" para detalhes de um comando especifico.',
755
+ 'cli.help.more': 'Execute "{command}" para detalhes de um comando específico.',
749
756
  'cli.help.quickStart': 'Criar um novo app:',
750
757
  'cli.help.optionDescription': 'Mostrar ajuda do comando',
751
758
  'cli.help.commandDescription': 'Mostrar ajuda do comando',
752
- 'cli.help.inline.options': '[opcoes]',
759
+ 'cli.help.inline.options': '[opções]',
753
760
  'cli.help.heading.usage': 'Uso',
754
761
  'cli.help.heading.arguments': 'Argumentos',
755
- 'cli.help.heading.options': 'Opcoes',
756
- 'cli.help.heading.globalOptions': 'Opcoes Globais',
762
+ 'cli.help.heading.options': 'Opções',
763
+ 'cli.help.heading.globalOptions': 'Opções globais',
757
764
  'cli.help.heading.commands': 'Comandos',
758
- 'cli.command.setup.description': '📱 Configura um novo template Flutter',
759
- 'cli.command.new.description': ' Cria um novo app',
765
+ 'cli.help.group.start': '🚀 Para começar',
766
+ 'cli.help.group.work': '🛠 Trabalhar no app',
767
+ 'cli.help.group.publish': '📤 Publicar e testar',
768
+ 'cli.help.group.maintenance': '⚙️ Manutenção da Kasy CLI',
769
+ 'cli.help.group.advanced': '🔧 Avançado',
770
+ 'cli.help.group.other': 'Outros',
771
+ 'cli.help.tip': 'Dica: rode `kasy <comando> --help` para detalhes. Use --lang pt|en|es para mudar o idioma.',
772
+ 'cli.command.setup.description': '📱 (avançado) Configura um projeto Flutter existente',
773
+ 'cli.command.new.description': '✨ Cria um app novo (ex: kasy new meu-app)',
760
774
  'cli.command.new.projectName': 'nome do projeto',
761
775
  'cli.command.new.projectNameArg': 'Nome da pasta do projeto',
762
776
  'prompt.projectName.enter': 'Qual o nome do seu projeto?',
763
777
  'prompt.projectName.required': 'O nome do projeto é obrigatório.',
764
778
  'prompt.projectName.default': 'meu_app',
765
- 'cli.command.setup.directoryName': 'diretorio',
779
+ 'cli.command.setup.directoryName': 'diretório',
766
780
  'cli.command.setup.directoryArg': 'Diretório de destino (padrão: pasta atual .)',
767
781
  'cli.command.setup.langName': 'idioma',
768
782
  'cli.command.setup.langOption': 'Idioma dos prompts (en, pt, es)',
769
783
  'cli.command.setup.backendOption': 'Adapter de backend (firebase, supabase, api)',
770
784
  'cli.command.setup.featuresOption': 'Features opcionais separadas por virgula (web,widget,llm_chat,revenuecat,ci)',
771
785
  'cli.command.help.paramName': 'comando',
772
- 'cli.command.doctor.description': '🩺 Verifica ambiente e dependencias',
773
- 'cli.command.modules.description': '🧩 Lista backends e features disponiveis',
774
- 'cli.command.validate.description': '✅ Valida combinacoes minimas de backend/features',
786
+ 'cli.command.doctor.description': '🩺 Verifica se o seu computador está pronto para rodar a Kasy',
787
+ 'cli.command.modules.description': '🧩 Mostra o que já vem incluso e o que você pode adicionar',
788
+ 'cli.command.validate.description': '✅ (avançado) Valida combinações de backend e features',
775
789
  'cli.command.validate.analyzeOnlyOption': 'Executa apenas flutter analyze (sem build)',
776
- 'cli.command.version.description': '🏷️ Mostra a versao instalada da CLI',
777
- 'cli.command.upgrade.description': '⬆️ Atualiza a CLI kasy para a versao mais recente',
778
- 'cli.command.uninstall.description': '🗑️ Remove a CLI kasy desta maquina',
790
+ 'cli.command.version.description': '🏷️ Mostra a versão instalada da Kasy',
791
+ 'cli.command.upgrade.description': '⬆️ Atualiza a Kasy CLI para a versão mais recente',
792
+ 'cli.command.uninstall.description': '🗑️ Desinstala a Kasy deste computador',
779
793
  'cli.command.upgrade.running': 'Atualizando a CLI kasy...',
780
794
  'cli.command.upgrade.done': 'kasy atualizado com sucesso!',
781
795
  'cli.command.uninstall.running': 'Desinstalando a CLI kasy...',
@@ -997,20 +1011,20 @@ const MESSAGES = {
997
1011
 
998
1012
  'new.firebase.success.deployStep': '• Deploy do backend (de dentro da pasta do projeto):',
999
1013
 
1000
- 'cli.command.deploy.description': 'Deploy do backend (Firebase ou Supabase) — detecta projeto e configura FCM',
1001
- 'cli.command.check.description': '🔔 Verifica configuração de push notifications (use --fix para corrigir)',
1014
+ 'cli.command.deploy.description': '📤 Publica o servidor no Firebase ou Supabase',
1015
+ 'cli.command.check.description': '🔔 Confere se as notificações push estão configuradas (use --fix para corrigir)',
1002
1016
  'deploy.q.project': 'Firebase Project ID:',
1003
1017
  'deploy.q.serviceAccount': 'Caminho para o service account JSON:',
1004
1018
  'deploy.detected.project': '✓ Projeto Firebase detectado:',
1005
1019
  'deploy.detected.serviceAccount': '✓ Service account detectado:',
1006
1020
  'deploy.error.notProject': 'Nenhum firebase.json encontrado. Execute kasy deploy de dentro da pasta do projeto.',
1007
1021
 
1008
- 'cli.command.ios.description': 'Release iOS na App Store (Mac)',
1022
+ 'cli.command.ios.description': '🍎 Publica o app na App Store (precisa de Mac)',
1009
1023
  'cli.command.ios.configure.description': 'Configurar credenciais Apple para envio local do IPA',
1010
1024
  'cli.command.ios.release.description': 'Incrementa build, gera IPA e envia para App Store Connect',
1011
1025
  'cli.command.ios.build.description': 'Só gera o IPA (sem enviar)',
1012
1026
  'cli.command.ios.clean.description': 'Limpa caches Flutter/Xcode após falha no build iOS',
1013
- 'cli.command.codemagic.description': 'Disparar build iOS no Codemagic (nuvem)',
1027
+ 'cli.command.codemagic.description': '☁️ Compila o app na nuvem (sem precisar de Mac)',
1014
1028
  'cli.command.codemagic.configure.description': 'Configurar credenciais da API Codemagic',
1015
1029
  'cli.command.codemagic.release.description': 'Iniciar build do workflow iOS no Codemagic',
1016
1030
  'cli.command.codemagic.status.description': 'Status do build Codemagic por ID',
@@ -1327,7 +1341,7 @@ const MESSAGES = {
1327
1341
  'new.internet.warning': '📶 Verifique se voce esta com uma internet estavel — esta etapa precisa de conexao.',
1328
1342
 
1329
1343
  // run command
1330
- 'cli.command.run.description': '▶ Executa o app Flutter usando .env (fallback para dart-defines do launch.json)',
1344
+ 'cli.command.run.description': '▶ Roda o app no celular, simulador ou navegador',
1331
1345
  'run.launching': 'Iniciando app Flutter...',
1332
1346
  'run.updateHint.prefix': 'Melhorias disponíveis para o projeto —',
1333
1347
  'run.updateHint.suffix': 'para ver o que há de novo',
@@ -1345,13 +1359,13 @@ const MESSAGES = {
1345
1359
  'doctor.project.noModules': 'Nenhuma feature opcional ativa',
1346
1360
 
1347
1361
  // add command
1348
- 'cli.command.add.description': '➕ Adiciona uma feature a um projeto existente',
1362
+ 'cli.command.add.description': '➕ Adiciona algo novo ao app (ex: kasy add ai_chat)',
1349
1363
 
1350
1364
  // docs command
1351
- 'cli.command.docs.description': '📖 Exibe a documentacao e comandos disponiveis',
1365
+ 'cli.command.docs.description': '📖 Abre a documentação completa',
1352
1366
 
1353
1367
  // notifications command
1354
- 'cli.command.notifications.description': '🔔 Textos de notificacao local (lembretes + demo na Home)',
1368
+ 'cli.command.notifications.description': '🔔 Edita os textos das notificações locais e lembretes',
1355
1369
  'cli.command.notifications.text.description': 'Define titulos e mensagens das notificacoes locais',
1356
1370
  'notifications.error.notKasyProject': 'kit_setup.json nao encontrado. Execute dentro de um projeto Kasy.',
1357
1371
  'notifications.error.noI18n': 'lib/i18n/*.i18n.json nao encontrado neste projeto.',
@@ -1415,7 +1429,7 @@ const MESSAGES = {
1415
1429
  'add.llm_chat.nextSteps.supabase': '\n Proximos passos:\n 1. O LLM_CHAT_ENDPOINT no .vscode/launch.json ja foi preenchido.\n 2. Rode o app: kasy run\n',
1416
1430
  'add.llm_chat.nextSteps.supabase.deployFailed': '\n Proximos passos:\n 1. Deploy manual: supabase functions deploy llm-chat --no-verify-jwt\n 2. O LLM_CHAT_ENDPOINT no .vscode/launch.json ja foi preenchido.\n 3. Rode o app: kasy run\n',
1417
1431
  'add.llm_chat.nextSteps.api': '\n Proximos passos:\n 1. Crie um endpoint no seu servidor que aceite {message, history} e chame sua LLM.\n 2. Atualize LLM_CHAT_ENDPOINT no .vscode/launch.json com a URL do seu endpoint.\n 3. Rode o app: kasy run\n',
1418
- 'cli.command.remove.description': '🗑️ Remove uma feature de um projeto existente',
1432
+ 'cli.command.remove.description': '🗑️ Remove algo que você não usa mais (ex: kasy remove sentry)',
1419
1433
  'remove.error.noModule': 'Informe o nome da feature. Uso: kasy remove <feature>',
1420
1434
  'remove.error.notKasyProject': 'kit_setup.json nao encontrado. Execute este comando dentro de um projeto Kasy.',
1421
1435
  'remove.error.unknownModule': 'Feature desconhecida: {module}\nDisponiveis: {list}',
@@ -1432,7 +1446,7 @@ const MESSAGES = {
1432
1446
  'remove.success': 'Feature "{module}" removida com sucesso.',
1433
1447
  'remove.warn.ci': 'Arquivos de CI removidos. Se tinha workflows customizados, restaure-os pelo git.',
1434
1448
  'remove.warn.sentry.shared': 'sentry_flutter mantido — ainda necessario para features ativas (revenuecat/facebook).',
1435
- 'cli.command.update.description': '⬆️ Atualiza features/componentes em um projeto existente',
1449
+ 'cli.command.update.description': '⬆️ Atualiza partes do app para a última versão (ex: kasy update components)',
1436
1450
  'cli.command.update.targetArg': 'Alvo para atualizar (ex.: revenuecat, sentry, components)',
1437
1451
  'update.error.noProject': 'kit_setup.json nao encontrado. Execute dentro de um projeto Kasy.',
1438
1452
  'update.error.unknownModule': 'Modulo desconhecido: {module}\nDisponiveis: {list}',
@@ -1475,10 +1489,10 @@ const MESSAGES = {
1475
1489
  'cli.tagline': 'Crea apps móviles sin dolor de configuración',
1476
1490
  'cli.description': 'Crea apps Flutter sin dolor de configuración.',
1477
1491
  'cli.usage': '<comando> [argumentos]',
1478
- 'cli.option.version': 'Mostrar version instalada',
1492
+ 'cli.option.version': 'Mostrar versión instalada',
1479
1493
  'cli.option.lang': 'Idioma (en, pt, es)',
1480
1494
  'cli.help.afterError': '(ejecuta con --help para detalles de uso)',
1481
- 'cli.help.more': 'Ejecuta "{command}" para detalles de un comando especifico.',
1495
+ 'cli.help.more': 'Ejecuta "{command}" para detalles de un comando específico.',
1482
1496
  'cli.help.quickStart': 'Crear un nuevo app:',
1483
1497
  'cli.help.optionDescription': 'Mostrar ayuda del comando',
1484
1498
  'cli.help.commandDescription': 'Mostrar ayuda del comando',
@@ -1486,10 +1500,17 @@ const MESSAGES = {
1486
1500
  'cli.help.heading.usage': 'Uso',
1487
1501
  'cli.help.heading.arguments': 'Argumentos',
1488
1502
  'cli.help.heading.options': 'Opciones',
1489
- 'cli.help.heading.globalOptions': 'Opciones Globales',
1503
+ 'cli.help.heading.globalOptions': 'Opciones globales',
1490
1504
  'cli.help.heading.commands': 'Comandos',
1491
- 'cli.command.setup.description': '📱 Configura una nueva plantilla Flutter',
1492
- 'cli.command.new.description': ' Crea un nuevo app',
1505
+ 'cli.help.group.start': '🚀 Para empezar',
1506
+ 'cli.help.group.work': '🛠 Trabajar en tu app',
1507
+ 'cli.help.group.publish': '📤 Publicar y probar',
1508
+ 'cli.help.group.maintenance': '⚙️ Mantenimiento de Kasy CLI',
1509
+ 'cli.help.group.advanced': '🔧 Avanzado',
1510
+ 'cli.help.group.other': 'Otros',
1511
+ 'cli.help.tip': 'Tip: ejecuta `kasy <comando> --help` para más detalles. Usa --lang pt|en|es para cambiar idioma.',
1512
+ 'cli.command.setup.description': '📱 (avanzado) Configura un proyecto Flutter existente',
1513
+ 'cli.command.new.description': '✨ Crea una app nueva (ej: kasy new mi-app)',
1493
1514
  'cli.command.new.projectName': 'nombre del proyecto',
1494
1515
  'cli.command.new.projectNameArg': 'Nombre de la carpeta del proyecto',
1495
1516
  'prompt.projectName.enter': '¿Cuál es el nombre de tu proyecto?',
@@ -1502,13 +1523,13 @@ const MESSAGES = {
1502
1523
  'cli.command.setup.backendOption': 'Adapter de backend (firebase, supabase, api)',
1503
1524
  'cli.command.setup.featuresOption': 'Features opcionales separadas por coma (web,widget,llm_chat,revenuecat,ci)',
1504
1525
  'cli.command.help.paramName': 'comando',
1505
- 'cli.command.doctor.description': '🩺 Verifica entorno y dependencias',
1506
- 'cli.command.modules.description': '🧩 Lista backends y features disponibles',
1507
- 'cli.command.validate.description': '✅ Valida combinaciones minimas de backend/features',
1526
+ 'cli.command.doctor.description': '🩺 Verifica si tu computadora está lista para correr Kasy',
1527
+ 'cli.command.modules.description': '🧩 Muestra lo que viene incluido y lo que puedes añadir',
1528
+ 'cli.command.validate.description': '✅ (avanzado) Valida combinaciones de backend y features',
1508
1529
  'cli.command.validate.analyzeOnlyOption': 'Ejecuta solo flutter analyze (sin build)',
1509
- 'cli.command.version.description': '🏷️ Muestra la version instalada de la CLI',
1510
- 'cli.command.upgrade.description': '⬆️ Actualiza la CLI kasy a la ultima version',
1511
- 'cli.command.uninstall.description': '🗑️ Desinstala la CLI kasy de esta maquina',
1530
+ 'cli.command.version.description': '🏷️ Muestra la versión instalada de Kasy',
1531
+ 'cli.command.upgrade.description': '⬆️ Actualiza la Kasy CLI a la última versión',
1532
+ 'cli.command.uninstall.description': '🗑️ Desinstala Kasy de esta computadora',
1512
1533
  'cli.command.upgrade.running': 'Actualizando la CLI kasy...',
1513
1534
  'cli.command.upgrade.done': 'kasy actualizado correctamente!',
1514
1535
  'cli.command.uninstall.running': 'Desinstalando la CLI kasy...',
@@ -1732,20 +1753,20 @@ const MESSAGES = {
1732
1753
 
1733
1754
  'new.firebase.success.deployStep': '• Desplegar backend (desde dentro de la carpeta del proyecto):',
1734
1755
 
1735
- 'cli.command.deploy.description': 'Despliega el backend (Firebase o Supabase) — detecta proyecto y configura FCM',
1736
- 'cli.command.check.description': '🔔 Verifica configuración de push notifications (usa --fix para corregir)',
1756
+ 'cli.command.deploy.description': '📤 Publica el servidor en Firebase o Supabase',
1757
+ 'cli.command.check.description': '🔔 Verifica si las notificaciones push están configuradas (usa --fix para arreglar)',
1737
1758
  'deploy.q.project': 'Firebase Project ID:',
1738
1759
  'deploy.q.serviceAccount': 'Ruta al service account JSON:',
1739
1760
  'deploy.detected.project': '✓ Proyecto Firebase detectado:',
1740
1761
  'deploy.detected.serviceAccount': '✓ Service account detectado:',
1741
1762
  'deploy.error.notProject': 'No se encontro firebase.json. Ejecute kasy deploy desde dentro de la carpeta del proyecto.',
1742
1763
 
1743
- 'cli.command.ios.description': 'Release iOS en App Store (Mac)',
1764
+ 'cli.command.ios.description': '🍎 Publica la app en la App Store (necesita Mac)',
1744
1765
  'cli.command.ios.configure.description': 'Configurar credenciales Apple para subida local del IPA',
1745
1766
  'cli.command.ios.release.description': 'Incrementa build, genera IPA y sube a App Store Connect',
1746
1767
  'cli.command.ios.build.description': 'Solo genera el IPA (sin subir)',
1747
1768
  'cli.command.ios.clean.description': 'Limpia cachés Flutter/Xcode tras fallo en build iOS',
1748
- 'cli.command.codemagic.description': 'Disparar build iOS en Codemagic (nube)',
1769
+ 'cli.command.codemagic.description': '☁️ Compila la app en la nube (sin necesitar Mac)',
1749
1770
  'cli.command.codemagic.configure.description': 'Configurar credenciales API de Codemagic',
1750
1771
  'cli.command.codemagic.release.description': 'Iniciar build del workflow iOS en Codemagic',
1751
1772
  'cli.command.codemagic.status.description': 'Estado del build Codemagic por ID',
@@ -2060,7 +2081,7 @@ const MESSAGES = {
2060
2081
  'new.internet.warning': '📶 Asegurate de tener una conexion a internet estable — este paso requiere red.',
2061
2082
 
2062
2083
  // run command
2063
- 'cli.command.run.description': '▶ Ejecuta el app Flutter usando .env (fallback a dart-defines del launch.json)',
2084
+ 'cli.command.run.description': '▶ Corre la app en celular, simulador o navegador',
2064
2085
  'run.launching': 'Iniciando app Flutter...',
2065
2086
  'run.updateHint.prefix': 'Mejoras disponibles para el proyecto —',
2066
2087
  'run.updateHint.suffix': 'para ver las novedades',
@@ -2078,13 +2099,13 @@ const MESSAGES = {
2078
2099
  'doctor.project.noModules': 'Ninguna feature opcional activa',
2079
2100
 
2080
2101
  // add command
2081
- 'cli.command.add.description': '➕ Agrega una feature a un proyecto existente',
2102
+ 'cli.command.add.description': '➕ Añade algo nuevo a tu app (ej: kasy add ai_chat)',
2082
2103
 
2083
2104
  // docs command
2084
- 'cli.command.docs.description': '📖 Muestra la documentacion y comandos disponibles',
2105
+ 'cli.command.docs.description': '📖 Abre la documentación completa',
2085
2106
 
2086
2107
  // notifications command
2087
- 'cli.command.notifications.description': '🔔 Textos de notificacion local (recordatorios + demo en Home)',
2108
+ 'cli.command.notifications.description': '🔔 Edita los textos de las notificaciones locales y recordatorios',
2088
2109
  'cli.command.notifications.text.description': 'Define titulos y mensajes de notificaciones locales',
2089
2110
  'notifications.error.notKasyProject': 'No se encontro kit_setup.json. Ejecuta dentro de un proyecto Kasy.',
2090
2111
  'notifications.error.noI18n': 'lib/i18n/*.i18n.json no encontrado en este proyecto.',
@@ -2148,7 +2169,7 @@ const MESSAGES = {
2148
2169
  'add.llm_chat.nextSteps.supabase': '\n Proximos pasos:\n 1. El LLM_CHAT_ENDPOINT en .vscode/launch.json ya fue pre-llenado.\n 2. Corre el app: kasy run\n',
2149
2170
  'add.llm_chat.nextSteps.supabase.deployFailed': '\n Proximos pasos:\n 1. Deploy manual: supabase functions deploy llm-chat --no-verify-jwt\n 2. El LLM_CHAT_ENDPOINT en .vscode/launch.json ya fue pre-llenado.\n 3. Corre el app: kasy run\n',
2150
2171
  'add.llm_chat.nextSteps.api': '\n Proximos pasos:\n 1. Crea un endpoint en tu servidor que acepte {message, history} y llame tu LLM.\n 2. Actualiza LLM_CHAT_ENDPOINT en .vscode/launch.json con la URL de tu endpoint.\n 3. Corre el app: kasy run\n',
2151
- 'cli.command.remove.description': '🗑️ Elimina una feature de un proyecto existente',
2172
+ 'cli.command.remove.description': '🗑️ Elimina algo que ya no usas (ej: kasy remove sentry)',
2152
2173
  'remove.error.noModule': 'Ingresa el nombre de la feature. Uso: kasy remove <feature>',
2153
2174
  'remove.error.notKasyProject': 'kit_setup.json no encontrado. Ejecuta este comando dentro de un proyecto Kasy.',
2154
2175
  'remove.error.unknownModule': 'Modulo desconocido: {module}\nDisponibles: {list}',
@@ -2165,7 +2186,7 @@ const MESSAGES = {
2165
2186
  'remove.success': 'Feature "{module}" eliminada exitosamente.',
2166
2187
  'remove.warn.ci': 'Archivos de CI eliminados. Si tenias workflows personalizados, restauralos desde git.',
2167
2188
  'remove.warn.sentry.shared': 'sentry_flutter conservado — todavia requerido por features activas (revenuecat/facebook).',
2168
- 'cli.command.update.description': '⬆️ Actualiza features/componentes en un proyecto existente',
2189
+ 'cli.command.update.description': '⬆️ Actualiza partes de tu app a la última versión (ej: kasy update components)',
2169
2190
  'cli.command.update.targetArg': 'Objetivo a actualizar (ej.: revenuecat, sentry, components)',
2170
2191
  'update.error.noProject': 'kit_setup.json no encontrado. Ejecuta dentro de un proyecto Kasy.',
2171
2192
  'update.error.unknownModule': 'Modulo desconocido: {module}\nDisponibles: {list}',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kasy-cli",
3
- "version": "1.8.1",
3
+ "version": "1.9.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"
@@ -21,6 +21,7 @@ export 'kasy_chip.dart';
21
21
  export 'kasy_dialog.dart';
22
22
  export 'kasy_otp_verification_bottom_sheet.dart';
23
23
  export 'kasy_skeleton.dart';
24
+ export 'kasy_swipe_action.dart';
24
25
  export 'kasy_text_area.dart';
25
26
  export 'kasy_text_field.dart';
26
27
  export 'kasy_text_field_otp.dart';
@@ -0,0 +1,143 @@
1
+ import 'package:flutter/material.dart';
2
+ import 'package:kasy_kit/core/theme/theme.dart';
3
+
4
+ /// Visual tone for the action revealed behind a [KasySwipeAction].
5
+ enum KasySwipeActionTone {
6
+ /// Destructive action (delete, remove). Red background, white icon.
7
+ destructive,
8
+
9
+ /// Neutral action (archive, snooze). Surface tint, on-surface icon.
10
+ neutral,
11
+ }
12
+
13
+ /// Direction in which the user must swipe to reveal the action.
14
+ enum KasySwipeActionDirection {
15
+ /// Swipe from right to left (common for delete on mobile lists).
16
+ endToStart,
17
+
18
+ /// Swipe from left to right.
19
+ startToEnd,
20
+
21
+ /// Both directions allowed.
22
+ both,
23
+ }
24
+
25
+ /// Wraps any [child] so the user can swipe it horizontally to trigger an
26
+ /// action — most commonly a delete. Visual chrome and dismiss threshold come
27
+ /// from the design system; the parent only supplies the action callback.
28
+ ///
29
+ /// ```dart
30
+ /// KasySwipeAction(
31
+ /// key: ValueKey(item.id),
32
+ /// onDismissed: () => repository.delete(item),
33
+ /// child: NotificationTile(notification: item),
34
+ /// );
35
+ /// ```
36
+ ///
37
+ /// For a confirmation step (e.g. asking before deleting), supply
38
+ /// [confirmDismiss] returning `true` to proceed. Returning `false` (or
39
+ /// awaiting a dialog that returns `false`) snaps the card back into place.
40
+ class KasySwipeAction extends StatelessWidget {
41
+ final Widget child;
42
+ final VoidCallback onDismissed;
43
+ final KasySwipeActionTone tone;
44
+ final KasySwipeActionDirection direction;
45
+ final IconData icon;
46
+ final String? label;
47
+
48
+ /// Optional confirmation hook. Return `true` to dismiss, `false` to cancel.
49
+ final Future<bool> Function()? confirmDismiss;
50
+
51
+ const KasySwipeAction({
52
+ required Key super.key,
53
+ required this.child,
54
+ required this.onDismissed,
55
+ this.tone = KasySwipeActionTone.destructive,
56
+ this.direction = KasySwipeActionDirection.endToStart,
57
+ this.icon = KasyIcons.trash,
58
+ this.label,
59
+ }) : confirmDismiss = null;
60
+
61
+ const KasySwipeAction.withConfirm({
62
+ required Key super.key,
63
+ required this.child,
64
+ required this.onDismissed,
65
+ required Future<bool> Function() confirm,
66
+ this.tone = KasySwipeActionTone.destructive,
67
+ this.direction = KasySwipeActionDirection.endToStart,
68
+ this.icon = KasyIcons.trash,
69
+ this.label,
70
+ }) : confirmDismiss = confirm;
71
+
72
+ DismissDirection _flutterDirection() {
73
+ switch (direction) {
74
+ case KasySwipeActionDirection.endToStart:
75
+ return DismissDirection.endToStart;
76
+ case KasySwipeActionDirection.startToEnd:
77
+ return DismissDirection.startToEnd;
78
+ case KasySwipeActionDirection.both:
79
+ return DismissDirection.horizontal;
80
+ }
81
+ }
82
+
83
+ Color _backgroundColor(BuildContext context) {
84
+ switch (tone) {
85
+ case KasySwipeActionTone.destructive:
86
+ return context.colors.error;
87
+ case KasySwipeActionTone.neutral:
88
+ return context.colors.surfaceNeutralSoft;
89
+ }
90
+ }
91
+
92
+ Color _foregroundColor(BuildContext context) {
93
+ switch (tone) {
94
+ case KasySwipeActionTone.destructive:
95
+ return context.colors.onError;
96
+ case KasySwipeActionTone.neutral:
97
+ return context.colors.onSurface;
98
+ }
99
+ }
100
+
101
+ @override
102
+ Widget build(BuildContext context) {
103
+ final Color bg = _backgroundColor(context);
104
+ final Color fg = _foregroundColor(context);
105
+ final DismissDirection flutterDirection = _flutterDirection();
106
+
107
+ Widget buildBackground(Alignment alignment) {
108
+ final children = <Widget>[
109
+ Icon(icon, color: fg),
110
+ if (label != null) ...[
111
+ const SizedBox(width: KasySpacing.sm),
112
+ Text(
113
+ label!,
114
+ style: context.textTheme.labelLarge?.copyWith(color: fg),
115
+ ),
116
+ ],
117
+ ];
118
+ return Container(
119
+ alignment: alignment,
120
+ padding: const EdgeInsets.symmetric(horizontal: KasySpacing.lg),
121
+ color: bg,
122
+ child: Row(
123
+ mainAxisSize: MainAxisSize.min,
124
+ children: alignment == Alignment.centerRight
125
+ ? children
126
+ : children.reversed.toList(),
127
+ ),
128
+ );
129
+ }
130
+
131
+ return Dismissible(
132
+ key: key!,
133
+ direction: flutterDirection,
134
+ confirmDismiss: confirmDismiss == null
135
+ ? null
136
+ : (_) => confirmDismiss!.call(),
137
+ onDismissed: (_) => onDismissed(),
138
+ background: buildBackground(Alignment.centerLeft),
139
+ secondaryBackground: buildBackground(Alignment.centerRight),
140
+ child: child,
141
+ );
142
+ }
143
+ }
@@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart';
2
2
  import 'package:flutter/material.dart';
3
3
  import 'package:flutter_riverpod/flutter_riverpod.dart';
4
4
  import 'package:kasy_kit/core/config/features.dart';
5
+ import 'package:kasy_kit/core/data/models/user.dart';
5
6
  import 'package:kasy_kit/core/guards/guard.dart';
6
7
  import 'package:kasy_kit/core/states/user_state_notifier.dart';
7
8
  import 'package:kasy_kit/core/theme/theme.dart';
@@ -44,9 +45,14 @@ class UserInfosGuard extends ConsumerWidget {
44
45
  // Native: all users (including guests with no ID) go through onboarding.
45
46
  // Guests are never forced to /signin here; individual features that
46
47
  // require authentication redirect to /signin on their own.
48
+ // Authenticated users skip onboarding: they already committed to an account
49
+ // (e.g. returning user signing back in after the anonymous session was
50
+ // discarded by `credential-already-in-use`).
47
51
  return Guard(
48
52
  canActivate: Future.value(
49
- !withOnboarding || authState.user.isOnboarded,
53
+ !withOnboarding ||
54
+ authState.user.isOnboarded ||
55
+ authState.user is AuthenticatedUserData,
50
56
  ),
51
57
  fallbackRoute: fallbackRoute,
52
58
  child: child,
@@ -188,6 +188,7 @@ const Set<String> _kReadyComponents = <String>{
188
188
  'TextField',
189
189
  'Sidebar',
190
190
  'Skeleton',
191
+ 'SwipeAction',
191
192
  'TextFieldOTP',
192
193
  'Toast',
193
194
  };
@@ -208,6 +209,7 @@ const Set<String> _kWebReadyComponents = <String>{
208
209
  'Hover',
209
210
  'Sidebar',
210
211
  'Skeleton',
212
+ 'SwipeAction',
211
213
  'TextArea',
212
214
  'TextField',
213
215
  'TextFieldOTP',
@@ -238,6 +240,7 @@ const List<_CatalogRow> _kCatalog = [
238
240
  _CatalogRow('Hover'),
239
241
  _CatalogRow('Sidebar'),
240
242
  _CatalogRow('Skeleton'),
243
+ _CatalogRow('SwipeAction'),
241
244
  _CatalogRow('TextArea'),
242
245
  _CatalogRow('TextField'),
243
246
  _CatalogRow('TextFieldOTP'),
@@ -490,6 +490,24 @@ ComponentPreviewDefinition? getComponentPreviewDefinition(
490
490
  ),
491
491
  ],
492
492
  );
493
+ case 'SwipeAction':
494
+ return const ComponentPreviewDefinition(
495
+ title: 'SwipeAction',
496
+ variants: [
497
+ ComponentPreviewVariant(
498
+ label: 'Destructive (delete)',
499
+ builder: _buildSwipeActionDestructive,
500
+ ),
501
+ ComponentPreviewVariant(
502
+ label: 'Neutral (archive)',
503
+ builder: _buildSwipeActionNeutral,
504
+ ),
505
+ ComponentPreviewVariant(
506
+ label: 'With confirm',
507
+ builder: _buildSwipeActionWithConfirm,
508
+ ),
509
+ ],
510
+ );
493
511
  default:
494
512
  return null;
495
513
  }
@@ -7710,3 +7728,171 @@ class _SkeletonGridCell extends StatelessWidget {
7710
7728
  );
7711
7729
  }
7712
7730
  }
7731
+
7732
+ // ─────────────────────────────────────────────────────────────────────────────
7733
+ // SwipeAction — wrap any tile so the user can swipe it to reveal an action
7734
+ // ─────────────────────────────────────────────────────────────────────────────
7735
+
7736
+ Widget _buildSwipeActionDestructive(BuildContext context) =>
7737
+ const _SwipeActionPreview(tone: KasySwipeActionTone.destructive);
7738
+
7739
+ Widget _buildSwipeActionNeutral(BuildContext context) =>
7740
+ const _SwipeActionPreview(
7741
+ tone: KasySwipeActionTone.neutral,
7742
+ icon: Icons.archive_outlined,
7743
+ );
7744
+
7745
+ Widget _buildSwipeActionWithConfirm(BuildContext context) =>
7746
+ const _SwipeActionPreview(
7747
+ tone: KasySwipeActionTone.destructive,
7748
+ useConfirm: true,
7749
+ );
7750
+
7751
+ class _SwipeActionPreview extends StatefulWidget {
7752
+ final KasySwipeActionTone tone;
7753
+ final IconData? icon;
7754
+ final bool useConfirm;
7755
+
7756
+ const _SwipeActionPreview({
7757
+ required this.tone,
7758
+ this.icon,
7759
+ this.useConfirm = false,
7760
+ });
7761
+
7762
+ @override
7763
+ State<_SwipeActionPreview> createState() => _SwipeActionPreviewState();
7764
+ }
7765
+
7766
+ class _SwipeActionPreviewState extends State<_SwipeActionPreview> {
7767
+ late List<_SwipePreviewItem> _items = _initial();
7768
+
7769
+ List<_SwipePreviewItem> _initial() => List.generate(
7770
+ 4,
7771
+ (i) => _SwipePreviewItem(
7772
+ id: i,
7773
+ title: 'Item ${i + 1}',
7774
+ subtitle: 'Swipe left to ${widget.useConfirm ? 'try to ' : ''}remove',
7775
+ ),
7776
+ );
7777
+
7778
+ void _reset() => setState(() => _items = _initial());
7779
+
7780
+ Future<bool> _confirm() async {
7781
+ bool confirmed = false;
7782
+ await showKasyConfirmDialog(
7783
+ context,
7784
+ title: 'Remove item?',
7785
+ message: 'This is just a preview — nothing is actually deleted.',
7786
+ cancelLabel: 'Cancel',
7787
+ confirmLabel: 'Remove',
7788
+ destructive: true,
7789
+ onConfirm: () => confirmed = true,
7790
+ );
7791
+ return confirmed;
7792
+ }
7793
+
7794
+ @override
7795
+ Widget build(BuildContext context) {
7796
+ if (_items.isEmpty) {
7797
+ return Padding(
7798
+ padding: const EdgeInsets.all(KasySpacing.lg),
7799
+ child: Column(
7800
+ children: [
7801
+ Text(
7802
+ 'All items removed',
7803
+ style: context.textTheme.bodyMedium,
7804
+ textAlign: TextAlign.center,
7805
+ ),
7806
+ const SizedBox(height: KasySpacing.sm),
7807
+ KasyButton(
7808
+ label: 'Reset preview',
7809
+ variant: KasyButtonVariant.outline,
7810
+ onPressed: _reset,
7811
+ ),
7812
+ ],
7813
+ ),
7814
+ );
7815
+ }
7816
+ return Column(
7817
+ crossAxisAlignment: CrossAxisAlignment.stretch,
7818
+ children: [
7819
+ for (final item in _items)
7820
+ widget.useConfirm
7821
+ ? KasySwipeAction.withConfirm(
7822
+ key: ValueKey('swipe_confirm_${item.id}'),
7823
+ onDismissed: () =>
7824
+ setState(() => _items.removeWhere((i) => i.id == item.id)),
7825
+ confirm: _confirm,
7826
+ tone: widget.tone,
7827
+ icon: widget.icon ?? KasyIcons.trash,
7828
+ child: _SwipePreviewTile(item: item),
7829
+ )
7830
+ : KasySwipeAction(
7831
+ key: ValueKey('swipe_${item.id}'),
7832
+ onDismissed: () =>
7833
+ setState(() => _items.removeWhere((i) => i.id == item.id)),
7834
+ tone: widget.tone,
7835
+ icon: widget.icon ?? KasyIcons.trash,
7836
+ child: _SwipePreviewTile(item: item),
7837
+ ),
7838
+ ],
7839
+ );
7840
+ }
7841
+ }
7842
+
7843
+ class _SwipePreviewItem {
7844
+ final int id;
7845
+ final String title;
7846
+ final String subtitle;
7847
+ const _SwipePreviewItem({
7848
+ required this.id,
7849
+ required this.title,
7850
+ required this.subtitle,
7851
+ });
7852
+ }
7853
+
7854
+ class _SwipePreviewTile extends StatelessWidget {
7855
+ final _SwipePreviewItem item;
7856
+ const _SwipePreviewTile({required this.item});
7857
+
7858
+ @override
7859
+ Widget build(BuildContext context) {
7860
+ return Container(
7861
+ padding: const EdgeInsets.all(KasySpacing.md),
7862
+ decoration: BoxDecoration(
7863
+ color: context.colors.surface,
7864
+ border: Border(
7865
+ bottom: BorderSide(color: context.colors.outline, width: 0.5),
7866
+ ),
7867
+ ),
7868
+ child: Row(
7869
+ children: [
7870
+ Container(
7871
+ width: 40,
7872
+ height: 40,
7873
+ decoration: BoxDecoration(
7874
+ color: context.colors.surfaceNeutralSoft,
7875
+ borderRadius: BorderRadius.circular(KasyRadius.md),
7876
+ ),
7877
+ child: Icon(KasyIcons.assistant, color: context.colors.onSurface),
7878
+ ),
7879
+ const SizedBox(width: KasySpacing.md),
7880
+ Expanded(
7881
+ child: Column(
7882
+ crossAxisAlignment: CrossAxisAlignment.start,
7883
+ children: [
7884
+ Text(item.title, style: context.textTheme.bodyMedium),
7885
+ Text(
7886
+ item.subtitle,
7887
+ style: context.textTheme.bodySmall?.copyWith(
7888
+ color: context.colors.muted,
7889
+ ),
7890
+ ),
7891
+ ],
7892
+ ),
7893
+ ),
7894
+ ],
7895
+ ),
7896
+ );
7897
+ }
7898
+ }
@@ -1,6 +1,7 @@
1
1
  import 'package:cloud_firestore/cloud_firestore.dart';
2
2
  import 'package:cloud_functions/cloud_functions.dart';
3
3
  import 'package:firebase_messaging/firebase_messaging.dart';
4
+ import 'package:flutter/services.dart';
4
5
  import 'package:flutter_riverpod/flutter_riverpod.dart';
5
6
  import 'package:kasy_kit/core/data/api/base_api_exceptions.dart';
6
7
  import 'package:kasy_kit/features/notifications/api/entities/notifications_entity.dart';
@@ -224,6 +225,12 @@ class FirebaseNotificationsApi implements NotificationsApi {
224
225
  try {
225
226
  final agg = await query.count().get();
226
227
  yield agg.count ?? 0;
228
+ } on FirebaseException catch (e) {
229
+ if (e.code == 'permission-denied') return;
230
+ _logger.e('unreadNotifications error: $e');
231
+ } on PlatformException catch (e) {
232
+ if (e.code == 'permission-denied') return;
233
+ _logger.e('unreadNotifications error: $e');
227
234
  } catch (e, s) {
228
235
  _logger.e('unreadNotifications error: $e: $s');
229
236
  }
@@ -154,13 +154,18 @@ class _NotificationsPageState extends ConsumerState<NotificationsPage> {
154
154
  return switch (item) {
155
155
  _HeaderItem(:final label) => _GroupLabel(label: label),
156
156
  _TileItem(:final notification, :final animationIndex) =>
157
- _SwipeToDeleteTile(
158
- notification: notification,
159
- index: animationIndex,
160
- onTap: (n) => ref
161
- .read(notificationsProvider.notifier)
162
- .onTapNotification(n),
163
- onDismissed: (n) => _deleteOne(context, n),
157
+ KasySwipeAction(
158
+ key: ValueKey(
159
+ 'notification_${notification.id ?? animationIndex}',
160
+ ),
161
+ onDismissed: () => _deleteOne(context, notification),
162
+ child: NotificationTileComponent(
163
+ notification: notification,
164
+ index: animationIndex,
165
+ onTap: (n) => ref
166
+ .read(notificationsProvider.notifier)
167
+ .onTapNotification(n),
168
+ ),
164
169
  ),
165
170
  };
166
171
  },
@@ -262,38 +267,3 @@ class _GroupLabel extends StatelessWidget {
262
267
  }
263
268
  }
264
269
 
265
- /// Wraps [NotificationTileComponent] in a [Dismissible] so the user can
266
- /// swipe a notification to the left to delete it.
267
- class _SwipeToDeleteTile extends StatelessWidget {
268
- final app.Notification notification;
269
- final int index;
270
- final void Function(app.Notification) onTap;
271
- final void Function(app.Notification) onDismissed;
272
-
273
- const _SwipeToDeleteTile({
274
- required this.notification,
275
- required this.index,
276
- required this.onTap,
277
- required this.onDismissed,
278
- });
279
-
280
- @override
281
- Widget build(BuildContext context) {
282
- return Dismissible(
283
- key: ValueKey('notification_${notification.id ?? index}'),
284
- direction: DismissDirection.endToStart,
285
- background: Container(
286
- alignment: Alignment.centerRight,
287
- padding: const EdgeInsets.symmetric(horizontal: KasySpacing.lg),
288
- color: context.colors.error,
289
- child: Icon(KasyIcons.trash, color: context.colors.onError),
290
- ),
291
- onDismissed: (_) => onDismissed(notification),
292
- child: NotificationTileComponent(
293
- notification: notification,
294
- index: index,
295
- onTap: onTap,
296
- ),
297
- );
298
- }
299
- }