kasy-cli 1.6.0 → 1.7.1
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 +2 -2
- package/docs/cli-reference.md +4 -6
- package/lib/commands/add.js +28 -3
- package/lib/commands/features.js +22 -3
- package/lib/scaffold/catalog.js +45 -11
- package/lib/scaffold/features/README.md +1 -2
- package/lib/scaffold/shared/generator-utils.js +1 -1
- package/lib/utils/i18n.js +102 -78
- package/lib/utils/prompts.js +0 -88
- package/package.json +1 -1
- package/templates/firebase/lib/features/home/home_page.dart +0 -19
- package/templates/firebase/lib/features/notifications/ui/notifications_page.dart +36 -8
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_routes.dart +0 -1
- package/templates/firebase/lib/router.dart +0 -9
- package/templates/firebase/test/features/notifications/ui/notifications_page_test.dart +65 -39
- package/templates/firebase/lib/features/dev/keyboard_test_page.dart +0 -93
package/bin/kasy.js
CHANGED
|
@@ -277,7 +277,7 @@ function buildProgram(language) {
|
|
|
277
277
|
applyLocalizedHelp(
|
|
278
278
|
program
|
|
279
279
|
.command('add')
|
|
280
|
-
.argument('[module]', 'Module to add (e.g. sentry, analytics, revenuecat, onboarding,
|
|
280
|
+
.argument('[module]', 'Module to add (e.g. sentry, analytics, revenuecat, onboarding, llm_chat, ci...)')
|
|
281
281
|
.option('--list', 'List all available modules and their current status')
|
|
282
282
|
.option('--yes', 'Skip interactive prompts (use placeholder values)')
|
|
283
283
|
.option('-d, --directory <path>', 'Project folder (default: current directory)', '.')
|
|
@@ -291,7 +291,7 @@ function buildProgram(language) {
|
|
|
291
291
|
applyLocalizedHelp(
|
|
292
292
|
program
|
|
293
293
|
.command('remove')
|
|
294
|
-
.argument('[module]', 'Module to remove (e.g. sentry, analytics,
|
|
294
|
+
.argument('[module]', 'Module to remove (e.g. sentry, analytics, llm_chat, ci...)')
|
|
295
295
|
.option('--yes', 'Skip confirmation prompt')
|
|
296
296
|
.option('-d, --directory <path>', 'Project folder (default: current directory)', '.')
|
|
297
297
|
.description(t('cli.command.remove.description'))
|
package/docs/cli-reference.md
CHANGED
|
@@ -17,7 +17,7 @@ kasy new # interativo — pergunta tudo
|
|
|
17
17
|
kasy new meu-app # cria na pasta meu-app
|
|
18
18
|
kasy new --yes # modo rápido, preset Starter
|
|
19
19
|
kasy new --backend supabase # define backend sem perguntar
|
|
20
|
-
kasy new --with
|
|
20
|
+
kasy new --with llm_chat,sentry # pré-seleciona features
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
**Opções:**
|
|
@@ -36,12 +36,12 @@ kasy new --with camera,sentry # pré-seleciona features
|
|
|
36
36
|
Adiciona uma feature a um projeto Kasy existente.
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
|
-
kasy add
|
|
39
|
+
kasy add llm_chat
|
|
40
40
|
kasy add sentry
|
|
41
41
|
kasy add revenuecat
|
|
42
42
|
kasy add --list # lista features disponíveis e status
|
|
43
|
-
kasy add --yes
|
|
44
|
-
kasy add -d ./outro-app
|
|
43
|
+
kasy add --yes llm_chat # sem perguntas interativas
|
|
44
|
+
kasy add -d ./outro-app llm_chat
|
|
45
45
|
```
|
|
46
46
|
|
|
47
47
|
**Features disponíveis:**
|
|
@@ -54,7 +54,6 @@ kasy add -d ./outro-app camera
|
|
|
54
54
|
| `onboarding` | Fluxo de onboarding customizável |
|
|
55
55
|
| `web` | Suporte a Flutter Web |
|
|
56
56
|
| `widget` | Home widgets para iOS e Android |
|
|
57
|
-
| `camera` | Câmera com CameraAwesome |
|
|
58
57
|
| `llm_chat` | Chat com IA via Cloud/Edge Functions |
|
|
59
58
|
| `feedback` | Sistema de feedback e feature requests |
|
|
60
59
|
| `ci` | CI/CD com GitHub Actions / Codemagic |
|
|
@@ -202,7 +201,6 @@ lib/
|
|
|
202
201
|
home/ # tela principal
|
|
203
202
|
notifications/ # notificações locais e push
|
|
204
203
|
settings/ # configurações do app
|
|
205
|
-
camera/ # [feature] câmera
|
|
206
204
|
llm_chat/ # [feature] chat com IA
|
|
207
205
|
feedbacks/ # [feature] feedback e feature requests
|
|
208
206
|
onboarding/ # [feature] onboarding
|
package/lib/commands/add.js
CHANGED
|
@@ -13,8 +13,15 @@ const {
|
|
|
13
13
|
FEATURES_PATCH_DIR,
|
|
14
14
|
normalizeFeature,
|
|
15
15
|
getVisibleFeatures,
|
|
16
|
+
getBaseFeatures,
|
|
17
|
+
BASE_FEATURES,
|
|
16
18
|
} = require('../scaffold/catalog');
|
|
17
19
|
|
|
20
|
+
function findBaseDisplayName(id) {
|
|
21
|
+
const base = BASE_FEATURES.find((f) => f.id === id);
|
|
22
|
+
return base ? base.displayName : id;
|
|
23
|
+
}
|
|
24
|
+
|
|
18
25
|
const KASY_AUDIENCE = process.env.KASY_INTERNAL === '1' ? 'internal' : 'public';
|
|
19
26
|
const { applyPatch } = require('../scaffold/engine');
|
|
20
27
|
const { writeRouter, writeNoOpAnalyticsApi, writeNoOpTrackingApi, writeNoOpAdminHomeWidgets, writeNoOpFeatureRequestRepository, writeMainDart, addPubspecDep, localizeReleaseDocs } = require('../scaffold/shared/generator-utils');
|
|
@@ -489,12 +496,30 @@ async function listModules(projectDir, t) {
|
|
|
489
496
|
}
|
|
490
497
|
|
|
491
498
|
const lines = [];
|
|
499
|
+
|
|
500
|
+
lines.push(kleur.bold(t('modules.featuresBase')));
|
|
501
|
+
for (const feature of getBaseFeatures()) {
|
|
502
|
+
lines.push(`${kleur.green('✓')} ${kleur.green(feature.displayName)}`);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Subscriptions is base, but inactive until RevenueCat is added.
|
|
506
|
+
if (!activeModules.includes('revenuecat')) {
|
|
507
|
+
lines.push('');
|
|
508
|
+
lines.push(kleur.dim(t('modules.hint.subscriptionNoRc')));
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
lines.push('');
|
|
512
|
+
lines.push(kleur.bold(t('modules.features')));
|
|
492
513
|
for (const feature of getVisibleFeatures({ audience: KASY_AUDIENCE })) {
|
|
493
514
|
const active = activeModules.includes(feature.id);
|
|
494
|
-
const
|
|
515
|
+
const betaBadge = feature.status === 'internal' ? kleur.yellow(' [beta]') : '';
|
|
516
|
+
const tagBadge = feature.tag ? kleur.yellow(` [${t(`modules.tag.${feature.tag}`)}]`) : '';
|
|
517
|
+
const enhancesBadge = feature.enhances
|
|
518
|
+
? kleur.magenta(` [${t('modules.tag.enhances', { target: findBaseDisplayName(feature.enhances) })}]`)
|
|
519
|
+
: '';
|
|
495
520
|
const icon = active ? kleur.green('✓') : kleur.dim('○');
|
|
496
|
-
const label = active ? kleur.green(feature.
|
|
497
|
-
lines.push(`${icon} ${label}${
|
|
521
|
+
const label = active ? kleur.green(feature.displayName) : kleur.white(feature.displayName);
|
|
522
|
+
lines.push(`${icon} ${label}${tagBadge}${enhancesBadge}${betaBadge}`);
|
|
498
523
|
}
|
|
499
524
|
ui.note(lines.join('\n'), t('add.list.title'));
|
|
500
525
|
}
|
package/lib/commands/features.js
CHANGED
|
@@ -4,9 +4,16 @@ const { printCompactHeader } = require('../utils/brand');
|
|
|
4
4
|
const { createTranslator, detectDefaultLanguage } = require('../utils/i18n');
|
|
5
5
|
const {
|
|
6
6
|
AVAILABLE_BACKENDS,
|
|
7
|
-
getVisibleFeatures
|
|
7
|
+
getVisibleFeatures,
|
|
8
|
+
getBaseFeatures,
|
|
9
|
+
BASE_FEATURES
|
|
8
10
|
} = require('../scaffold/catalog');
|
|
9
11
|
|
|
12
|
+
function findBaseDisplayName(id) {
|
|
13
|
+
const base = BASE_FEATURES.find((f) => f.id === id);
|
|
14
|
+
return base ? base.displayName : id;
|
|
15
|
+
}
|
|
16
|
+
|
|
10
17
|
const KASY_AUDIENCE = process.env.KASY_INTERNAL === '1' ? 'internal' : 'public';
|
|
11
18
|
|
|
12
19
|
function runFeatures(options = {}) {
|
|
@@ -21,10 +28,22 @@ function runFeatures(options = {}) {
|
|
|
21
28
|
);
|
|
22
29
|
ui.log.message(backendLines.join('\n'));
|
|
23
30
|
|
|
31
|
+
ui.log.step(kleur.bold(t('modules.featuresBase')));
|
|
32
|
+
const baseLines = getBaseFeatures().map((feature) =>
|
|
33
|
+
`${kleur.green('✓')} ${kleur.white(feature.displayName)} - ${kleur.gray(
|
|
34
|
+
t(`modules.feature.base.${feature.id}.description`)
|
|
35
|
+
)}`
|
|
36
|
+
);
|
|
37
|
+
ui.log.message(baseLines.join('\n'));
|
|
38
|
+
|
|
24
39
|
ui.log.step(kleur.bold(t('modules.features')));
|
|
25
40
|
const featureLines = getVisibleFeatures({ audience: KASY_AUDIENCE }).map((feature) => {
|
|
26
|
-
const
|
|
27
|
-
|
|
41
|
+
const betaBadge = feature.status === 'internal' ? kleur.yellow(' [beta]') : '';
|
|
42
|
+
const tagBadge = feature.tag ? kleur.yellow(` [${t(`modules.tag.${feature.tag}`)}]`) : '';
|
|
43
|
+
const enhancesBadge = feature.enhances
|
|
44
|
+
? kleur.magenta(` [${t('modules.tag.enhances', { target: findBaseDisplayName(feature.enhances) })}]`)
|
|
45
|
+
: '';
|
|
46
|
+
return `${kleur.cyan('○')} ${kleur.white(feature.displayName)}${tagBadge}${enhancesBadge}${betaBadge} - ${kleur.gray(
|
|
28
47
|
t(`modules.feature.${feature.id}.description`)
|
|
29
48
|
)}`;
|
|
30
49
|
});
|
package/lib/scaffold/catalog.js
CHANGED
|
@@ -25,26 +25,58 @@ const AVAILABLE_BACKENDS = [
|
|
|
25
25
|
*
|
|
26
26
|
* availableIn: backends that support this feature
|
|
27
27
|
* defaultInPresets: which named presets include this feature by default
|
|
28
|
+
* displayName: user-facing label shown in CLI listings (Title Case, English)
|
|
29
|
+
* tag: optional restriction tag — `firebaseOnly` or `requiresDb` —
|
|
30
|
+
* rendered as a colored badge in CLI listings
|
|
31
|
+
* enhances: id of a base feature this optional feature activates real
|
|
32
|
+
* functionality for. Example: `revenuecat` enhances
|
|
33
|
+
* `subscription` — the Subscriptions screen exists in the base
|
|
34
|
+
* project but only sells real plans once RevenueCat is added.
|
|
35
|
+
*
|
|
36
|
+
* Note on id vs folder name (intentional, not a bug):
|
|
37
|
+
* - id `local_notifications` → folder `lib/features/local_reminder/`
|
|
38
|
+
* - id `feedback` → folder `lib/features/feedbacks/`
|
|
39
|
+
* The id describes the user-facing capability; the folder describes the
|
|
40
|
+
* Dart component. The generator (see generator-utils.js writeRouter and
|
|
41
|
+
* the cleanup step around feature dirs) removes these folders when the
|
|
42
|
+
* feature is not selected.
|
|
28
43
|
*/
|
|
29
44
|
const FEATURE_CATALOG = [
|
|
30
45
|
// integrations
|
|
31
|
-
{ id: 'sentry', status: 'public', availableIn: ['firebase', 'supabase', 'api'], defaultInPresets: ['starter', 'saas', 'content', 'full'] },
|
|
32
|
-
{ id: 'analytics', status: 'public', availableIn: ['firebase', 'supabase', 'api'], defaultInPresets: ['starter', 'saas', 'content', 'full'] },
|
|
33
|
-
{ id: 'facebook', status: 'public', availableIn: ['firebase', 'supabase', 'api'], defaultInPresets: ['full'] },
|
|
46
|
+
{ id: 'sentry', displayName: 'Crash Reports (Sentry)', status: 'public', availableIn: ['firebase', 'supabase', 'api'], defaultInPresets: ['starter', 'saas', 'content', 'full'] },
|
|
47
|
+
{ id: 'analytics', displayName: 'Analytics', status: 'public', availableIn: ['firebase', 'supabase', 'api'], defaultInPresets: ['starter', 'saas', 'content', 'full'] },
|
|
48
|
+
{ id: 'facebook', displayName: 'Facebook (Login + Ads)', status: 'public', availableIn: ['firebase', 'supabase', 'api'], defaultInPresets: ['full'] },
|
|
34
49
|
// monetization
|
|
35
|
-
{ id: 'revenuecat', status: 'public', availableIn: ['firebase', 'supabase', 'api'], defaultInPresets: ['saas', 'full'] },
|
|
50
|
+
{ id: 'revenuecat', displayName: 'RevenueCat', status: 'public', availableIn: ['firebase', 'supabase', 'api'], defaultInPresets: ['saas', 'full'], enhances: 'subscription' },
|
|
36
51
|
// features
|
|
37
|
-
{ id: 'onboarding', status: 'public', availableIn: ['firebase', 'supabase', 'api'], defaultInPresets: ['starter', 'saas', 'content', 'full'] },
|
|
38
|
-
{ id: 'web', status: 'public', availableIn: ['firebase'], defaultInPresets: [] },
|
|
39
|
-
{ id: 'widget', status: 'public', availableIn: ['firebase', 'supabase', 'api'], defaultInPresets: ['full'] },
|
|
40
|
-
{ id: 'llm_chat', status: 'public', availableIn: ['firebase', 'supabase', 'api'], defaultInPresets: ['content', 'full'] },
|
|
41
|
-
{ id: 'local_notifications', status: 'public', availableIn: ['firebase', 'supabase', 'api'], defaultInPresets: [] },
|
|
52
|
+
{ id: 'onboarding', displayName: 'Onboarding', status: 'public', availableIn: ['firebase', 'supabase', 'api'], defaultInPresets: ['starter', 'saas', 'content', 'full'] },
|
|
53
|
+
{ id: 'web', displayName: 'Web Support (PWA)', status: 'public', availableIn: ['firebase'], defaultInPresets: [], tag: 'firebaseOnly' },
|
|
54
|
+
{ id: 'widget', displayName: 'Home Widget', status: 'public', availableIn: ['firebase', 'supabase', 'api'], defaultInPresets: ['full'] },
|
|
55
|
+
{ id: 'llm_chat', displayName: 'AI Chat', status: 'public', availableIn: ['firebase', 'supabase', 'api'], defaultInPresets: ['content', 'full'] },
|
|
56
|
+
{ id: 'local_notifications', displayName: 'Local Reminders', status: 'public', availableIn: ['firebase', 'supabase', 'api'], defaultInPresets: [] },
|
|
42
57
|
// feedback (Firebase/Supabase only)
|
|
43
|
-
{ id: 'feedback', status: 'public', availableIn: ['firebase', 'supabase'], defaultInPresets: ['saas', 'full'] },
|
|
58
|
+
{ id: 'feedback', displayName: 'Feature Requests', status: 'public', availableIn: ['firebase', 'supabase'], defaultInPresets: ['saas', 'full'], tag: 'requiresDb' },
|
|
44
59
|
// ci/cd
|
|
45
|
-
{ id: 'ci', status: 'public', availableIn: ['firebase', 'supabase', 'api'], defaultInPresets: ['full'] },
|
|
60
|
+
{ id: 'ci', displayName: 'CI/CD', status: 'public', availableIn: ['firebase', 'supabase', 'api'], defaultInPresets: ['full'] },
|
|
46
61
|
];
|
|
47
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Base features — always present in every generated project.
|
|
65
|
+
* These cannot be added or removed via `kasy add` / `kasy remove`.
|
|
66
|
+
* Listed in the CLI so users understand what ships with every project.
|
|
67
|
+
*/
|
|
68
|
+
const BASE_FEATURES = [
|
|
69
|
+
{ id: 'authentication', displayName: 'Auth' },
|
|
70
|
+
{ id: 'home', displayName: 'Home Screen' },
|
|
71
|
+
{ id: 'settings', displayName: 'Settings Screen' },
|
|
72
|
+
{ id: 'notifications', displayName: 'Push Notifications' },
|
|
73
|
+
{ id: 'subscription', displayName: 'Subscriptions' },
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
function getBaseFeatures() {
|
|
77
|
+
return BASE_FEATURES;
|
|
78
|
+
}
|
|
79
|
+
|
|
48
80
|
/**
|
|
49
81
|
* Returns features visible to the given audience, optionally filtered by backend.
|
|
50
82
|
*
|
|
@@ -215,6 +247,7 @@ module.exports = {
|
|
|
215
247
|
FEATURES_PATCH_DIR,
|
|
216
248
|
AVAILABLE_BACKENDS,
|
|
217
249
|
FEATURE_CATALOG,
|
|
250
|
+
BASE_FEATURES,
|
|
218
251
|
AVAILABLE_FEATURES,
|
|
219
252
|
DEFAULT_FEATURES,
|
|
220
253
|
BASE_COMPONENT_FILES,
|
|
@@ -223,4 +256,5 @@ module.exports = {
|
|
|
223
256
|
normalizeFeature,
|
|
224
257
|
parseFeatureList,
|
|
225
258
|
getVisibleFeatures,
|
|
259
|
+
getBaseFeatures,
|
|
226
260
|
};
|
|
@@ -21,7 +21,7 @@ features/
|
|
|
21
21
|
...
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
Cada subdiretório tem o **mesmo nome** que o valor do módulo em `new.js` (`sentry`, `analytics`, `facebook`, `revenuecat`, `onboarding`, `web`, `widget`, `
|
|
24
|
+
Cada subdiretório tem o **mesmo nome** que o valor do módulo em `new.js` (`sentry`, `analytics`, `facebook`, `revenuecat`, `onboarding`, `web`, `widget`, `llm_chat`, `local_notifications`, `feedback`, `ci`).
|
|
25
25
|
|
|
26
26
|
Ao gerar um projeto, o engine copia recursivamente o conteúdo de `features/{modulo}/` para a raiz do projeto, sobrescrevendo ou acrescentando arquivos.
|
|
27
27
|
|
|
@@ -32,7 +32,6 @@ Ao gerar um projeto, o engine copia recursivamente o conteúdo de `features/{mod
|
|
|
32
32
|
| `ci` | ✅ Sim | Adiciona `.github/`, `.gitlab-ci.yml`, etc. |
|
|
33
33
|
| `web` | ✅ Sim | Adiciona pasta `web/` e configurações de plataforma |
|
|
34
34
|
| `widget` | ✅ Sim | Adiciona configurações Android para home widgets |
|
|
35
|
-
| `camera` | Opcional | Permissões extras no AndroidManifest / Info.plist |
|
|
36
35
|
| `llm_chat` | Não | Apenas `LLM_CHAT_ENDPOINT` via dart-define. A chave da API LLM fica no servidor (Firebase Secret / Supabase Secret) — nunca no app. |
|
|
37
36
|
| `sentry` | Não | Apenas dart-define (`SENTRY_DSN`) |
|
|
38
37
|
| `analytics` | Não | Apenas dart-define |
|
|
@@ -985,7 +985,7 @@ async function writeMainDart(projectDir, backend, modules, packageName, options
|
|
|
985
985
|
* directories for modules the user did not select to avoid dead code.
|
|
986
986
|
*
|
|
987
987
|
* @param {string} projectDir
|
|
988
|
-
* @param {string[]} modules - Selected modules (e.g. ['
|
|
988
|
+
* @param {string[]} modules - Selected modules (e.g. ['analytics', 'sentry'])
|
|
989
989
|
*/
|
|
990
990
|
async function removeModuleDirs(projectDir, modules) {
|
|
991
991
|
const removable = [
|
package/lib/utils/i18n.js
CHANGED
|
@@ -34,7 +34,7 @@ const MESSAGES = {
|
|
|
34
34
|
'cli.command.setup.langName': 'lang',
|
|
35
35
|
'cli.command.setup.langOption': 'Prompt language (en, pt, es)',
|
|
36
36
|
'cli.command.setup.backendOption': 'Backend adapter (firebase, supabase, api)',
|
|
37
|
-
'cli.command.setup.featuresOption': 'Comma separated optional features (web,widget,
|
|
37
|
+
'cli.command.setup.featuresOption': 'Comma separated optional features (web,widget,llm_chat,revenuecat,ci)',
|
|
38
38
|
'cli.command.help.paramName': 'command',
|
|
39
39
|
'cli.command.doctor.description': '🩺 Check environment and dependencies',
|
|
40
40
|
'cli.command.modules.description': '🧩 List available backend and features',
|
|
@@ -73,22 +73,31 @@ const MESSAGES = {
|
|
|
73
73
|
'doctor.requiredMissing': 'Missing required dependencies. Fix the errors above and rerun doctor.',
|
|
74
74
|
'doctor.requiredPassed': '✓ Required environment checks passed.',
|
|
75
75
|
'modules.backends': 'Available backends:',
|
|
76
|
-
'modules.
|
|
76
|
+
'modules.featuresBase': 'Always included:',
|
|
77
|
+
'modules.features': 'Optional features:',
|
|
78
|
+
'modules.tag.firebaseOnly': 'Firebase only',
|
|
79
|
+
'modules.tag.requiresDb': 'requires Firebase or Supabase',
|
|
80
|
+
'modules.tag.enhances': 'enables {target}',
|
|
81
|
+
'modules.hint.subscriptionNoRc': 'Tip: Subscriptions screen is included but inactive. Run `kasy add revenuecat` to enable real payments.',
|
|
82
|
+
'modules.feature.base.authentication.description': 'Sign up, login and account management',
|
|
83
|
+
'modules.feature.base.home.description': 'Main app screen after login',
|
|
84
|
+
'modules.feature.base.settings.description': 'Theme, language, account and preferences',
|
|
85
|
+
'modules.feature.base.notifications.description': 'Push notifications via Firebase Cloud Messaging (works with any backend)',
|
|
86
|
+
'modules.feature.base.subscription.description': 'Premium subscription screen and data model (add RevenueCat to enable real payments)',
|
|
77
87
|
'modules.backend.firebase.description': 'Firebase backend adapter',
|
|
78
88
|
'modules.backend.supabase.description': 'Supabase backend adapter',
|
|
79
89
|
'modules.backend.api.description': 'REST/GraphQL API backend adapter',
|
|
80
90
|
'modules.feature.sentry.description': 'Sentry error tracking and crash reporting',
|
|
81
91
|
'modules.feature.analytics.description': 'Analytics event tracking (Firebase Analytics)',
|
|
82
|
-
'modules.feature.facebook.description': 'Facebook Login
|
|
83
|
-
'modules.feature.revenuecat.description': '
|
|
84
|
-
'modules.feature.onboarding.description': '
|
|
85
|
-
'modules.feature.web.description': 'Web
|
|
86
|
-
'modules.feature.widget.description': 'iOS/Android home widget integration',
|
|
87
|
-
'modules.feature.
|
|
88
|
-
'modules.feature.
|
|
89
|
-
'modules.feature.
|
|
90
|
-
'modules.feature.
|
|
91
|
-
'modules.feature.ci.description': 'CI: GitHub/GitLab (tests + build) + Codemagic (publish to stores)',
|
|
92
|
+
'modules.feature.facebook.description': 'Facebook Login and Meta Ads event tracking (always bundled together)',
|
|
93
|
+
'modules.feature.revenuecat.description': 'Enables real in-app payments on the Subscriptions screen',
|
|
94
|
+
'modules.feature.onboarding.description': 'Welcome flow shown on first launch',
|
|
95
|
+
'modules.feature.web.description': 'Web/PWA support',
|
|
96
|
+
'modules.feature.widget.description': 'iOS/Android home screen widget integration',
|
|
97
|
+
'modules.feature.llm_chat.description': 'AI chat screen with OpenAI or Gemini',
|
|
98
|
+
'modules.feature.feedback.description': 'In-app feature requests and voting',
|
|
99
|
+
'modules.feature.local_notifications.description': 'Local reminders scheduled by the user (no server required)',
|
|
100
|
+
'modules.feature.ci.description': 'CI/CD: GitHub/GitLab (tests + build) + Codemagic (publish to stores)',
|
|
92
101
|
'checks.checking': 'Checking {name}...',
|
|
93
102
|
'checks.found': '{name} found',
|
|
94
103
|
'checks.foundWithVersion': '{name} ready ({version})',
|
|
@@ -248,7 +257,7 @@ const MESSAGES = {
|
|
|
248
257
|
'new.q.preset': 'Which features to include?',
|
|
249
258
|
'new.q.preset.starter': '⚡ Starter — analytics + crash reports + onboarding',
|
|
250
259
|
'new.q.preset.saas': '💰 SaaS — subscriptions + analytics + onboarding + feedback',
|
|
251
|
-
'new.q.preset.content': '📱 Content —
|
|
260
|
+
'new.q.preset.content': '📱 Content — crash reports + analytics + onboarding + AI chat',
|
|
252
261
|
'new.q.preset.full': '🚀 Full — all features',
|
|
253
262
|
'new.q.preset.custom': '⚙️ Custom — choose features one by one',
|
|
254
263
|
'new.q.preset.none': '○ None — just the core',
|
|
@@ -411,18 +420,17 @@ const MESSAGES = {
|
|
|
411
420
|
'new.modules.header.feedback': '── Feedback (Firebase + Supabase) ──',
|
|
412
421
|
'new.modules.header.ci': '── CI/CD ──',
|
|
413
422
|
'new.modules.header.monetization': '── Monetization ──',
|
|
414
|
-
'new.firebase.module.revenuecat': '💰
|
|
415
|
-
'new.firebase.module.sentry': '🚨 Crash
|
|
423
|
+
'new.firebase.module.revenuecat': '💰 RevenueCat (enables Subscriptions)',
|
|
424
|
+
'new.firebase.module.sentry': '🚨 Crash Reports (Sentry)',
|
|
416
425
|
'new.firebase.module.analytics': '📊 Analytics (Mixpanel)',
|
|
417
|
-
'new.firebase.module.facebook': '👤 Facebook
|
|
418
|
-
'new.firebase.module.web': '🌐 Web (
|
|
419
|
-
'new.firebase.module.widget': '📱 Home
|
|
420
|
-
'new.firebase.module.
|
|
421
|
-
'new.firebase.module.
|
|
422
|
-
'new.firebase.module.
|
|
423
|
-
'new.firebase.module.
|
|
424
|
-
'new.firebase.module.
|
|
425
|
-
'new.firebase.module.onboarding': '👋 Onboarding (welcome screens, profile questions)',
|
|
426
|
+
'new.firebase.module.facebook': '👤 Facebook (Login + Ads)',
|
|
427
|
+
'new.firebase.module.web': '🌐 Web Support (PWA, Firebase only)',
|
|
428
|
+
'new.firebase.module.widget': '📱 Home Widget (iOS/Android)',
|
|
429
|
+
'new.firebase.module.llm_chat': '🤖 AI Chat (OpenAI/Gemini)',
|
|
430
|
+
'new.firebase.module.local_notifications': '🔔 Local Reminders (no server)',
|
|
431
|
+
'new.firebase.module.feedback': '💬 Feature Requests (requires Firebase or Supabase)',
|
|
432
|
+
'new.firebase.module.ci': '⚙️ CI/CD (GitHub + Codemagic)',
|
|
433
|
+
'new.firebase.module.onboarding': '👋 Onboarding (welcome flow)',
|
|
426
434
|
|
|
427
435
|
'new.firebase.q.secrets.configureNow': 'Configure server secrets now? (RevenueCat webhook, Meta Ads)',
|
|
428
436
|
'new.firebase.q.secrets.configureNow.hint': 'If not now, see the README for commands to add them later',
|
|
@@ -633,7 +641,7 @@ const MESSAGES = {
|
|
|
633
641
|
'notifications.summary.demo': 'Home → Features (demo):',
|
|
634
642
|
'notifications.summary.reminder': 'Settings → Reminders (scheduled):',
|
|
635
643
|
|
|
636
|
-
'add.list.title': '
|
|
644
|
+
'add.list.title': 'Project features',
|
|
637
645
|
'add.error.noModule': 'Provide a feature name or use --list to see available features.',
|
|
638
646
|
'add.error.notKasyProject': 'No kit_setup.json found. Run this command from inside a Kasy project.',
|
|
639
647
|
'add.error.unknownModule': 'Unknown feature: {module}\nAvailable: {list}',
|
|
@@ -692,7 +700,7 @@ const MESSAGES = {
|
|
|
692
700
|
'remove.warn.ci': 'CI files removed. If you had custom workflow files, restore them from git.',
|
|
693
701
|
'remove.warn.sentry.shared': 'sentry_flutter kept — still required by active features (revenuecat/facebook).',
|
|
694
702
|
'cli.command.update.description': '⬆️ Update features/components in an existing project',
|
|
695
|
-
'cli.command.update.targetArg': 'Target to update (e.g.
|
|
703
|
+
'cli.command.update.targetArg': 'Target to update (e.g. revenuecat, sentry, components)',
|
|
696
704
|
'update.error.noProject': 'No kit_setup.json found. Run this command from inside a Kasy project.',
|
|
697
705
|
'update.error.unknownModule': 'Unknown feature: {module}\nAvailable: {list}',
|
|
698
706
|
'update.error.unknownTarget': 'Unknown update target: {module}\nAvailable: {list}',
|
|
@@ -759,7 +767,7 @@ const MESSAGES = {
|
|
|
759
767
|
'cli.command.setup.langName': 'idioma',
|
|
760
768
|
'cli.command.setup.langOption': 'Idioma dos prompts (en, pt, es)',
|
|
761
769
|
'cli.command.setup.backendOption': 'Adapter de backend (firebase, supabase, api)',
|
|
762
|
-
'cli.command.setup.featuresOption': 'Features opcionais separadas por virgula (web,widget,
|
|
770
|
+
'cli.command.setup.featuresOption': 'Features opcionais separadas por virgula (web,widget,llm_chat,revenuecat,ci)',
|
|
763
771
|
'cli.command.help.paramName': 'comando',
|
|
764
772
|
'cli.command.doctor.description': '🩺 Verifica ambiente e dependencias',
|
|
765
773
|
'cli.command.modules.description': '🧩 Lista backends e features disponiveis',
|
|
@@ -798,22 +806,31 @@ const MESSAGES = {
|
|
|
798
806
|
'doctor.requiredMissing': 'Dependencias obrigatorias ausentes. Corrija os erros acima e execute o doctor novamente.',
|
|
799
807
|
'doctor.requiredPassed': '✓ Verificacoes obrigatorias de ambiente aprovadas.',
|
|
800
808
|
'modules.backends': 'Backends disponiveis:',
|
|
801
|
-
'modules.
|
|
809
|
+
'modules.featuresBase': 'Sempre incluido:',
|
|
810
|
+
'modules.features': 'Features opcionais:',
|
|
811
|
+
'modules.tag.firebaseOnly': 'somente Firebase',
|
|
812
|
+
'modules.tag.requiresDb': 'requer Firebase ou Supabase',
|
|
813
|
+
'modules.tag.enhances': 'ativa {target}',
|
|
814
|
+
'modules.hint.subscriptionNoRc': 'Dica: a tela de Subscriptions esta inclusa mas inativa. Rode `kasy add revenuecat` para habilitar pagamentos reais.',
|
|
815
|
+
'modules.feature.base.authentication.description': 'Cadastro, login e gerenciamento de conta',
|
|
816
|
+
'modules.feature.base.home.description': 'Tela principal do app apos o login',
|
|
817
|
+
'modules.feature.base.settings.description': 'Tema, idioma, conta e preferencias',
|
|
818
|
+
'modules.feature.base.notifications.description': 'Notificacoes push via Firebase Cloud Messaging (funciona com qualquer backend)',
|
|
819
|
+
'modules.feature.base.subscription.description': 'Tela e modelo de assinatura premium (adicione RevenueCat para habilitar pagamentos reais)',
|
|
802
820
|
'modules.backend.firebase.description': 'Adapter de backend Firebase',
|
|
803
821
|
'modules.backend.supabase.description': 'Adapter de backend Supabase',
|
|
804
822
|
'modules.backend.api.description': 'Adapter de backend API REST/GraphQL',
|
|
805
823
|
'modules.feature.sentry.description': 'Rastreamento de erros e crashes com Sentry',
|
|
806
824
|
'modules.feature.analytics.description': 'Rastreamento de eventos de analytics (Firebase Analytics)',
|
|
807
|
-
'modules.feature.facebook.description': '
|
|
808
|
-
'modules.feature.revenuecat.description': '
|
|
809
|
-
'modules.feature.onboarding.description': '
|
|
810
|
-
'modules.feature.web.description': '
|
|
811
|
-
'modules.feature.widget.description': '
|
|
812
|
-
'modules.feature.
|
|
813
|
-
'modules.feature.
|
|
814
|
-
'modules.feature.
|
|
815
|
-
'modules.feature.
|
|
816
|
-
'modules.feature.ci.description': 'CI: GitHub/GitLab (testes + build) + Codemagic (publicacao nas lojas)',
|
|
825
|
+
'modules.feature.facebook.description': 'Login do Facebook e rastreamento de eventos Meta Ads (vem sempre junto)',
|
|
826
|
+
'modules.feature.revenuecat.description': 'Habilita pagamentos reais na tela de Subscriptions',
|
|
827
|
+
'modules.feature.onboarding.description': 'Fluxo de boas-vindas mostrado no primeiro acesso',
|
|
828
|
+
'modules.feature.web.description': 'Suporte Web/PWA',
|
|
829
|
+
'modules.feature.widget.description': 'Widget de tela inicial iOS/Android',
|
|
830
|
+
'modules.feature.llm_chat.description': 'Tela de chat com IA usando OpenAI ou Gemini',
|
|
831
|
+
'modules.feature.feedback.description': 'Pedidos e votacao de features dentro do app',
|
|
832
|
+
'modules.feature.local_notifications.description': 'Lembretes locais agendados pelo usuario (sem servidor)',
|
|
833
|
+
'modules.feature.ci.description': 'CI/CD: GitHub/GitLab (testes + build) + Codemagic (publicacao nas lojas)',
|
|
817
834
|
'checks.checking': 'Verificando {name}...',
|
|
818
835
|
'checks.found': '{name} encontrado',
|
|
819
836
|
'checks.foundWithVersion': '{name} pronto ({version})',
|
|
@@ -973,7 +990,7 @@ const MESSAGES = {
|
|
|
973
990
|
'new.q.preset': 'Quais features incluir?',
|
|
974
991
|
'new.q.preset.starter': '⚡ Starter — analytics + erros + onboarding',
|
|
975
992
|
'new.q.preset.saas': '💰 SaaS — assinaturas + analytics + onboarding + feedback',
|
|
976
|
-
'new.q.preset.content': '📱 Conteudo — analytics + onboarding +
|
|
993
|
+
'new.q.preset.content': '📱 Conteudo — crash reports + analytics + onboarding + AI chat',
|
|
977
994
|
'new.q.preset.full': '🚀 Completo — todas as features',
|
|
978
995
|
'new.q.preset.custom': '⚙️ Personalizar — escolha feature a feature',
|
|
979
996
|
'new.q.preset.none': '○ Nenhum — so o core',
|
|
@@ -1136,18 +1153,17 @@ const MESSAGES = {
|
|
|
1136
1153
|
'new.modules.header.feedback': '── Feedback (Firebase + Supabase) ──',
|
|
1137
1154
|
'new.modules.header.ci': '── CI/CD ──',
|
|
1138
1155
|
'new.modules.header.monetization': '── Monetizacao ──',
|
|
1139
|
-
'new.firebase.module.revenuecat': '💰
|
|
1140
|
-
'new.firebase.module.sentry': '🚨
|
|
1156
|
+
'new.firebase.module.revenuecat': '💰 RevenueCat (ativa Subscriptions)',
|
|
1157
|
+
'new.firebase.module.sentry': '🚨 Crash Reports (Sentry)',
|
|
1141
1158
|
'new.firebase.module.analytics': '📊 Analytics (Mixpanel)',
|
|
1142
|
-
'new.firebase.module.facebook': '👤
|
|
1143
|
-
'new.firebase.module.web': '🌐 Web (
|
|
1144
|
-
'new.firebase.module.widget': '📱 Widget
|
|
1145
|
-
'new.firebase.module.
|
|
1146
|
-
'new.firebase.module.
|
|
1147
|
-
'new.firebase.module.
|
|
1148
|
-
'new.firebase.module.
|
|
1149
|
-
'new.firebase.module.
|
|
1150
|
-
'new.firebase.module.onboarding': '👋 Onboarding (telas de boas-vindas, perguntas de perfil)',
|
|
1159
|
+
'new.firebase.module.facebook': '👤 Facebook (Login + Ads)',
|
|
1160
|
+
'new.firebase.module.web': '🌐 Web Support (PWA, somente Firebase)',
|
|
1161
|
+
'new.firebase.module.widget': '📱 Home Widget (iOS/Android)',
|
|
1162
|
+
'new.firebase.module.llm_chat': '🤖 AI Chat (OpenAI/Gemini)',
|
|
1163
|
+
'new.firebase.module.local_notifications': '🔔 Local Reminders (sem servidor)',
|
|
1164
|
+
'new.firebase.module.feedback': '💬 Feature Requests (requer Firebase ou Supabase)',
|
|
1165
|
+
'new.firebase.module.ci': '⚙️ CI/CD (GitHub + Codemagic)',
|
|
1166
|
+
'new.firebase.module.onboarding': '👋 Onboarding (fluxo de boas-vindas)',
|
|
1151
1167
|
|
|
1152
1168
|
'new.firebase.q.secrets.configureNow': 'Configurar as secrets do servidor agora? (webhook RevenueCat, Meta Ads)',
|
|
1153
1169
|
'new.firebase.q.secrets.configureNow.hint': 'Se nao agora, veja o README para os comandos de configuracao depois',
|
|
@@ -1358,7 +1374,7 @@ const MESSAGES = {
|
|
|
1358
1374
|
'notifications.summary.demo': 'Home → Features (demo):',
|
|
1359
1375
|
'notifications.summary.reminder': 'Configuracoes → Lembretes (agendado):',
|
|
1360
1376
|
|
|
1361
|
-
'add.list.title': 'Features
|
|
1377
|
+
'add.list.title': 'Features do projeto',
|
|
1362
1378
|
'add.error.noModule': 'Informe o nome da feature ou use --list para ver as disponiveis.',
|
|
1363
1379
|
'add.error.notKasyProject': 'kit_setup.json nao encontrado. Execute este comando dentro de um projeto Kasy.',
|
|
1364
1380
|
'add.error.unknownModule': 'Feature desconhecida: {module}\nDisponiveis: {list}',
|
|
@@ -1417,7 +1433,7 @@ const MESSAGES = {
|
|
|
1417
1433
|
'remove.warn.ci': 'Arquivos de CI removidos. Se tinha workflows customizados, restaure-os pelo git.',
|
|
1418
1434
|
'remove.warn.sentry.shared': 'sentry_flutter mantido — ainda necessario para features ativas (revenuecat/facebook).',
|
|
1419
1435
|
'cli.command.update.description': '⬆️ Atualiza features/componentes em um projeto existente',
|
|
1420
|
-
'cli.command.update.targetArg': 'Alvo para atualizar (ex.:
|
|
1436
|
+
'cli.command.update.targetArg': 'Alvo para atualizar (ex.: revenuecat, sentry, components)',
|
|
1421
1437
|
'update.error.noProject': 'kit_setup.json nao encontrado. Execute dentro de um projeto Kasy.',
|
|
1422
1438
|
'update.error.unknownModule': 'Modulo desconhecido: {module}\nDisponiveis: {list}',
|
|
1423
1439
|
'update.error.unknownTarget': 'Alvo de atualizacao desconhecido: {module}\nDisponiveis: {list}',
|
|
@@ -1484,7 +1500,7 @@ const MESSAGES = {
|
|
|
1484
1500
|
'cli.command.setup.langName': 'idioma',
|
|
1485
1501
|
'cli.command.setup.langOption': 'Idioma de prompts (en, pt, es)',
|
|
1486
1502
|
'cli.command.setup.backendOption': 'Adapter de backend (firebase, supabase, api)',
|
|
1487
|
-
'cli.command.setup.featuresOption': 'Features opcionales separadas por coma (web,widget,
|
|
1503
|
+
'cli.command.setup.featuresOption': 'Features opcionales separadas por coma (web,widget,llm_chat,revenuecat,ci)',
|
|
1488
1504
|
'cli.command.help.paramName': 'comando',
|
|
1489
1505
|
'cli.command.doctor.description': '🩺 Verifica entorno y dependencias',
|
|
1490
1506
|
'cli.command.modules.description': '🧩 Lista backends y features disponibles',
|
|
@@ -1523,22 +1539,31 @@ const MESSAGES = {
|
|
|
1523
1539
|
'doctor.requiredMissing': 'Faltan dependencias obligatorias. Corrige los errores anteriores y ejecuta doctor nuevamente.',
|
|
1524
1540
|
'doctor.requiredPassed': '✓ Verificaciones obligatorias de entorno aprobadas.',
|
|
1525
1541
|
'modules.backends': 'Backends disponibles:',
|
|
1526
|
-
'modules.
|
|
1542
|
+
'modules.featuresBase': 'Siempre incluido:',
|
|
1543
|
+
'modules.features': 'Features opcionales:',
|
|
1544
|
+
'modules.tag.firebaseOnly': 'solo Firebase',
|
|
1545
|
+
'modules.tag.requiresDb': 'requiere Firebase o Supabase',
|
|
1546
|
+
'modules.tag.enhances': 'activa {target}',
|
|
1547
|
+
'modules.hint.subscriptionNoRc': 'Tip: la pantalla de Subscriptions esta incluida pero inactiva. Ejecuta `kasy add revenuecat` para habilitar pagos reales.',
|
|
1548
|
+
'modules.feature.base.authentication.description': 'Registro, inicio de sesion y gestion de cuenta',
|
|
1549
|
+
'modules.feature.base.home.description': 'Pantalla principal de la app despues del login',
|
|
1550
|
+
'modules.feature.base.settings.description': 'Tema, idioma, cuenta y preferencias',
|
|
1551
|
+
'modules.feature.base.notifications.description': 'Notificaciones push via Firebase Cloud Messaging (funciona con cualquier backend)',
|
|
1552
|
+
'modules.feature.base.subscription.description': 'Pantalla y modelo de suscripcion premium (anade RevenueCat para habilitar pagos reales)',
|
|
1527
1553
|
'modules.backend.firebase.description': 'Adapter de backend Firebase',
|
|
1528
1554
|
'modules.backend.supabase.description': 'Adapter de backend Supabase',
|
|
1529
1555
|
'modules.backend.api.description': 'Adapter de backend API REST/GraphQL',
|
|
1530
1556
|
'modules.feature.sentry.description': 'Seguimiento de errores y crashes con Sentry',
|
|
1531
1557
|
'modules.feature.analytics.description': 'Seguimiento de eventos de analytics (Firebase Analytics)',
|
|
1532
|
-
'modules.feature.facebook.description': '
|
|
1533
|
-
'modules.feature.revenuecat.description': '
|
|
1534
|
-
'modules.feature.onboarding.description': '
|
|
1535
|
-
'modules.feature.web.description': '
|
|
1536
|
-
'modules.feature.widget.description': '
|
|
1537
|
-
'modules.feature.
|
|
1538
|
-
'modules.feature.
|
|
1539
|
-
'modules.feature.
|
|
1540
|
-
'modules.feature.
|
|
1541
|
-
'modules.feature.ci.description': 'CI: GitHub/GitLab (tests + build) + Codemagic (publicacion en tiendas)',
|
|
1558
|
+
'modules.feature.facebook.description': 'Login de Facebook y seguimiento de eventos Meta Ads (siempre juntos)',
|
|
1559
|
+
'modules.feature.revenuecat.description': 'Habilita pagos reales en la pantalla de Subscriptions',
|
|
1560
|
+
'modules.feature.onboarding.description': 'Flujo de bienvenida mostrado en el primer acceso',
|
|
1561
|
+
'modules.feature.web.description': 'Soporte Web/PWA',
|
|
1562
|
+
'modules.feature.widget.description': 'Widget de pantalla inicial iOS/Android',
|
|
1563
|
+
'modules.feature.llm_chat.description': 'Pantalla de chat con IA usando OpenAI o Gemini',
|
|
1564
|
+
'modules.feature.feedback.description': 'Solicitudes y votacion de features dentro de la app',
|
|
1565
|
+
'modules.feature.local_notifications.description': 'Recordatorios locales programados por el usuario (sin servidor)',
|
|
1566
|
+
'modules.feature.ci.description': 'CI/CD: GitHub/GitLab (tests + build) + Codemagic (publicacion en tiendas)',
|
|
1542
1567
|
'checks.checking': 'Verificando {name}...',
|
|
1543
1568
|
'checks.found': '{name} encontrado',
|
|
1544
1569
|
'checks.foundWithVersion': '{name} listo ({version})',
|
|
@@ -1700,7 +1725,7 @@ const MESSAGES = {
|
|
|
1700
1725
|
'new.q.preset': '¿Qué features incluir?',
|
|
1701
1726
|
'new.q.preset.starter': '⚡ Starter — analytics + errores + onboarding',
|
|
1702
1727
|
'new.q.preset.saas': '💰 SaaS — suscripciones + analytics + onboarding + feedback',
|
|
1703
|
-
'new.q.preset.content': '📱 Contenido — analytics + onboarding +
|
|
1728
|
+
'new.q.preset.content': '📱 Contenido — crash reports + analytics + onboarding + AI chat',
|
|
1704
1729
|
'new.q.preset.full': '🚀 Completo — todas las features',
|
|
1705
1730
|
'new.q.preset.custom': '⚙️ Personalizar — elige feature a feature',
|
|
1706
1731
|
'new.q.preset.none': '○ Ninguno — solo el core',
|
|
@@ -1861,18 +1886,17 @@ const MESSAGES = {
|
|
|
1861
1886
|
'new.modules.header.feedback': '── Feedback (Firebase + Supabase) ──',
|
|
1862
1887
|
'new.modules.header.ci': '── CI/CD ──',
|
|
1863
1888
|
'new.modules.header.monetization': '── Monetizacion ──',
|
|
1864
|
-
'new.firebase.module.revenuecat': '💰
|
|
1865
|
-
'new.firebase.module.sentry': '🚨
|
|
1889
|
+
'new.firebase.module.revenuecat': '💰 RevenueCat (activa Subscriptions)',
|
|
1890
|
+
'new.firebase.module.sentry': '🚨 Crash Reports (Sentry)',
|
|
1866
1891
|
'new.firebase.module.analytics': '📊 Analytics (Mixpanel)',
|
|
1867
|
-
'new.firebase.module.facebook': '👤
|
|
1868
|
-
'new.firebase.module.web': '🌐 Web (
|
|
1869
|
-
'new.firebase.module.widget': '📱 Widget
|
|
1870
|
-
'new.firebase.module.
|
|
1871
|
-
'new.firebase.module.
|
|
1872
|
-
'new.firebase.module.
|
|
1873
|
-
'new.firebase.module.
|
|
1874
|
-
'new.firebase.module.
|
|
1875
|
-
'new.firebase.module.onboarding': '👋 Onboarding (pantallas de bienvenida, preguntas de perfil)',
|
|
1892
|
+
'new.firebase.module.facebook': '👤 Facebook (Login + Ads)',
|
|
1893
|
+
'new.firebase.module.web': '🌐 Web Support (PWA, solo Firebase)',
|
|
1894
|
+
'new.firebase.module.widget': '📱 Home Widget (iOS/Android)',
|
|
1895
|
+
'new.firebase.module.llm_chat': '🤖 AI Chat (OpenAI/Gemini)',
|
|
1896
|
+
'new.firebase.module.local_notifications': '🔔 Local Reminders (sin servidor)',
|
|
1897
|
+
'new.firebase.module.feedback': '💬 Feature Requests (requiere Firebase o Supabase)',
|
|
1898
|
+
'new.firebase.module.ci': '⚙️ CI/CD (GitHub + Codemagic)',
|
|
1899
|
+
'new.firebase.module.onboarding': '👋 Onboarding (flujo de bienvenida)',
|
|
1876
1900
|
|
|
1877
1901
|
'new.firebase.q.secrets.configureNow': '¿Configurar los secrets del servidor ahora? (webhook RevenueCat, Meta Ads)',
|
|
1878
1902
|
'new.firebase.q.secrets.configureNow.hint': 'Si no ahora, vea el README para los comandos de configuración después',
|
|
@@ -2083,7 +2107,7 @@ const MESSAGES = {
|
|
|
2083
2107
|
'notifications.summary.demo': 'Home → Features (demo):',
|
|
2084
2108
|
'notifications.summary.reminder': 'Ajustes → Recordatorios (programado):',
|
|
2085
2109
|
|
|
2086
|
-
'add.list.title': 'Features
|
|
2110
|
+
'add.list.title': 'Features del proyecto',
|
|
2087
2111
|
'add.error.noModule': 'Indica el nombre de la feature o usa --list para ver las disponibles.',
|
|
2088
2112
|
'add.error.notKasyProject': 'kit_setup.json no encontrado. Ejecuta este comando dentro de un proyecto Kasy.',
|
|
2089
2113
|
'add.error.unknownModule': 'Feature desconocida: {module}\nDisponibles: {list}',
|
|
@@ -2142,7 +2166,7 @@ const MESSAGES = {
|
|
|
2142
2166
|
'remove.warn.ci': 'Archivos de CI eliminados. Si tenias workflows personalizados, restauralos desde git.',
|
|
2143
2167
|
'remove.warn.sentry.shared': 'sentry_flutter conservado — todavia requerido por features activas (revenuecat/facebook).',
|
|
2144
2168
|
'cli.command.update.description': '⬆️ Actualiza features/componentes en un proyecto existente',
|
|
2145
|
-
'cli.command.update.targetArg': 'Objetivo a actualizar (ej.:
|
|
2169
|
+
'cli.command.update.targetArg': 'Objetivo a actualizar (ej.: revenuecat, sentry, components)',
|
|
2146
2170
|
'update.error.noProject': 'kit_setup.json no encontrado. Ejecuta dentro de un proyecto Kasy.',
|
|
2147
2171
|
'update.error.unknownModule': 'Modulo desconocido: {module}\nDisponibles: {list}',
|
|
2148
2172
|
'update.error.unknownTarget': 'Objetivo de actualizacion desconocido: {module}\nDisponibles: {list}',
|
package/lib/utils/prompts.js
CHANGED
|
@@ -6,14 +6,6 @@ const {
|
|
|
6
6
|
detectDefaultLanguage,
|
|
7
7
|
normalizeLanguage
|
|
8
8
|
} = require('./i18n');
|
|
9
|
-
const {
|
|
10
|
-
AVAILABLE_BACKENDS,
|
|
11
|
-
getVisibleFeatures,
|
|
12
|
-
normalizeBackend,
|
|
13
|
-
parseFeatureList
|
|
14
|
-
} = require('../scaffold/catalog');
|
|
15
|
-
|
|
16
|
-
const KASY_AUDIENCE = process.env.KASY_INTERNAL === '1' ? 'internal' : 'public';
|
|
17
9
|
|
|
18
10
|
function makeCancel(t) {
|
|
19
11
|
return () => {
|
|
@@ -65,89 +57,9 @@ async function promptProjectName(options = {}) {
|
|
|
65
57
|
return String(value).trim();
|
|
66
58
|
}
|
|
67
59
|
|
|
68
|
-
async function runSetupWizard(options = {}) {
|
|
69
|
-
const t = options.t || createTranslator(options.language);
|
|
70
|
-
const cancel = makeCancel(t);
|
|
71
|
-
const defaultAppName = options.defaultAppName || 'MyApp';
|
|
72
|
-
const selectedFeaturesFromArgv = parseFeatureList(options.selectedFeatures);
|
|
73
|
-
|
|
74
|
-
const appName = await ui.text({
|
|
75
|
-
message: t('prompt.appName.enter'),
|
|
76
|
-
initialValue: defaultAppName,
|
|
77
|
-
validate: (v) => (v && v.trim() ? undefined : t('prompt.appName.required')),
|
|
78
|
-
onCancel: cancel,
|
|
79
|
-
});
|
|
80
|
-
const bundleId = await ui.text({
|
|
81
|
-
message: t('prompt.bundleId.enter'),
|
|
82
|
-
initialValue: 'com.example.app',
|
|
83
|
-
validate: (v) => {
|
|
84
|
-
if (!v || !v.trim()) return t('prompt.bundleId.required');
|
|
85
|
-
const valid = /^[a-zA-Z][\w]*(\.[a-zA-Z][\w]*)+$/.test(v.trim());
|
|
86
|
-
return valid ? undefined : t('prompt.bundleId.invalid');
|
|
87
|
-
},
|
|
88
|
-
onCancel: cancel,
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
const core = { appName, bundleId };
|
|
92
|
-
|
|
93
|
-
const backendFromArgv = normalizeBackend(options.selectedBackend);
|
|
94
|
-
let backend = backendFromArgv;
|
|
95
|
-
if (!backend) {
|
|
96
|
-
backend = await ui.select({
|
|
97
|
-
message: t('prompt.backend.select'),
|
|
98
|
-
initialValue: AVAILABLE_BACKENDS[0]?.id,
|
|
99
|
-
options: AVAILABLE_BACKENDS.map((b) => ({ value: b.id, label: b.id })),
|
|
100
|
-
onCancel: cancel,
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
let features;
|
|
105
|
-
if (selectedFeaturesFromArgv.length > 0) {
|
|
106
|
-
features = selectedFeaturesFromArgv;
|
|
107
|
-
} else {
|
|
108
|
-
const selected = await ui.multiselect({
|
|
109
|
-
message: t('prompt.features.select'),
|
|
110
|
-
options: getVisibleFeatures({ audience: KASY_AUDIENCE }).map((feature) => ({
|
|
111
|
-
value: feature.id,
|
|
112
|
-
label: feature.status === 'internal' ? `${feature.id} [beta]` : feature.id,
|
|
113
|
-
})),
|
|
114
|
-
initialValues: [],
|
|
115
|
-
required: false,
|
|
116
|
-
onCancel: cancel,
|
|
117
|
-
});
|
|
118
|
-
features = parseFeatureList(selected);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (backend === 'firebase') {
|
|
122
|
-
const firebaseProjectId = await ui.text({
|
|
123
|
-
message: t('prompt.firebase.projectId.enter'),
|
|
124
|
-
validate: (v) => (v && v.trim() ? undefined : t('prompt.firebase.projectId.required')),
|
|
125
|
-
onCancel: cancel,
|
|
126
|
-
});
|
|
127
|
-
return { ...core, backend, features, firebaseProjectId };
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (backend === 'supabase') {
|
|
131
|
-
const supabaseUrl = await ui.text({
|
|
132
|
-
message: t('prompt.supabase.url.enter'),
|
|
133
|
-
validate: (v) => (v && v.trim() ? undefined : t('prompt.supabase.url.required')),
|
|
134
|
-
onCancel: cancel,
|
|
135
|
-
});
|
|
136
|
-
const supabaseAnonKey = await ui.text({
|
|
137
|
-
message: t('prompt.supabase.anonKey.enter'),
|
|
138
|
-
validate: (v) => (v && v.trim() ? undefined : t('prompt.supabase.anonKey.required')),
|
|
139
|
-
onCancel: cancel,
|
|
140
|
-
});
|
|
141
|
-
return { ...core, backend, features, supabaseUrl, supabaseAnonKey };
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
return { ...core, backend, features };
|
|
145
|
-
}
|
|
146
|
-
|
|
147
60
|
module.exports = {
|
|
148
61
|
createTranslator,
|
|
149
62
|
promptLicenseKey,
|
|
150
63
|
promptLanguage,
|
|
151
64
|
promptProjectName,
|
|
152
|
-
runSetupWizard
|
|
153
65
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import 'package:flutter/foundation.dart' show kDebugMode;
|
|
2
1
|
import 'package:flutter/material.dart';
|
|
3
2
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
4
|
-
import 'package:go_router/go_router.dart';
|
|
5
3
|
import 'package:kasy_kit/components/kasy_app_bar.dart';
|
|
6
4
|
import 'package:kasy_kit/core/bottom_menu/bart_inner_paths.dart';
|
|
7
5
|
import 'package:kasy_kit/core/bottom_menu/kasy_bart_navigation.dart';
|
|
@@ -15,7 +13,6 @@ import 'package:kasy_kit/features/home/home_components_page.dart';
|
|
|
15
13
|
import 'package:kasy_kit/features/home/home_features_page.dart';
|
|
16
14
|
import 'package:kasy_kit/features/notifications/shared/att_permission.dart';
|
|
17
15
|
import 'package:kasy_kit/features/notifications/shared/notification_permission_bottom_sheet.dart';
|
|
18
|
-
import 'package:kasy_kit/features/settings/ui/components/admin/admin_routes.dart';
|
|
19
16
|
import 'package:kasy_kit/features/subscription/shared/maybeshow_premium.dart';
|
|
20
17
|
import 'package:kasy_kit/i18n/translations.g.dart';
|
|
21
18
|
|
|
@@ -118,22 +115,6 @@ class HomePage extends ConsumerWidget {
|
|
|
118
115
|
);
|
|
119
116
|
},
|
|
120
117
|
),
|
|
121
|
-
if (kDebugMode) ...[
|
|
122
|
-
const SizedBox(height: KasySpacing.md),
|
|
123
|
-
FilledButton.icon(
|
|
124
|
-
onPressed: () =>
|
|
125
|
-
context.push(adminRouteKeyboardTest),
|
|
126
|
-
icon: const Icon(Icons.keyboard, size: 18),
|
|
127
|
-
label: const Text('Keyboard Test'),
|
|
128
|
-
style: FilledButton.styleFrom(
|
|
129
|
-
backgroundColor: Colors.deepOrange,
|
|
130
|
-
minimumSize: const Size(double.infinity, 48),
|
|
131
|
-
shape: RoundedRectangleBorder(
|
|
132
|
-
borderRadius: BorderRadius.circular(12),
|
|
133
|
-
),
|
|
134
|
-
),
|
|
135
|
-
),
|
|
136
|
-
],
|
|
137
118
|
const SizedBox(height: KasySpacing.xl),
|
|
138
119
|
]),
|
|
139
120
|
),
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import 'dart:async';
|
|
2
|
+
|
|
1
3
|
import 'package:flutter/material.dart';
|
|
2
4
|
import 'package:flutter/rendering.dart';
|
|
3
5
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
@@ -5,6 +7,7 @@ import 'package:kasy_kit/components/kasy_app_bar.dart';
|
|
|
5
7
|
import 'package:kasy_kit/core/theme/theme.dart';
|
|
6
8
|
import 'package:kasy_kit/features/notifications/providers/models/notification.dart'
|
|
7
9
|
as app;
|
|
10
|
+
import 'package:kasy_kit/features/notifications/providers/models/notification_list.dart';
|
|
8
11
|
import 'package:kasy_kit/features/notifications/providers/notifications_provider.dart';
|
|
9
12
|
import 'package:kasy_kit/features/notifications/ui/components/notification_settings_sheet.dart';
|
|
10
13
|
import 'package:kasy_kit/features/notifications/ui/components/notification_tile.dart';
|
|
@@ -19,6 +22,7 @@ class NotificationsPage extends ConsumerStatefulWidget {
|
|
|
19
22
|
|
|
20
23
|
class _NotificationsPageState extends ConsumerState<NotificationsPage> {
|
|
21
24
|
final ScrollController _scrollController = ScrollController();
|
|
25
|
+
Timer? _autoReadTimer;
|
|
22
26
|
|
|
23
27
|
@override
|
|
24
28
|
void initState() {
|
|
@@ -29,6 +33,7 @@ class _NotificationsPageState extends ConsumerState<NotificationsPage> {
|
|
|
29
33
|
|
|
30
34
|
@override
|
|
31
35
|
void dispose() {
|
|
36
|
+
_autoReadTimer?.cancel();
|
|
32
37
|
_scrollController.removeListener(_onScrollChange);
|
|
33
38
|
_scrollController.dispose();
|
|
34
39
|
super.dispose();
|
|
@@ -47,27 +52,50 @@ class _NotificationsPageState extends ConsumerState<NotificationsPage> {
|
|
|
47
52
|
}
|
|
48
53
|
|
|
49
54
|
void requestReadAll() {
|
|
50
|
-
|
|
51
|
-
|
|
55
|
+
_autoReadTimer?.cancel();
|
|
56
|
+
_autoReadTimer = Timer(const Duration(milliseconds: 1500), () {
|
|
57
|
+
if (!mounted) return;
|
|
52
58
|
ref.read(notificationsProvider.notifier).readAll();
|
|
53
59
|
});
|
|
54
60
|
}
|
|
55
61
|
|
|
62
|
+
bool _hasUnread(AsyncValue<NotificationsList> state) {
|
|
63
|
+
if (!state.hasValue) return false;
|
|
64
|
+
return state.value!.data.any((n) => !n.seen);
|
|
65
|
+
}
|
|
66
|
+
|
|
56
67
|
@override
|
|
57
68
|
Widget build(BuildContext context) {
|
|
58
69
|
final notificationsState = ref.watch(notificationsProvider);
|
|
70
|
+
final hasUnread = _hasUnread(notificationsState);
|
|
59
71
|
|
|
60
72
|
return KasyOverlayScaffold(
|
|
61
73
|
title: t.notifications.title,
|
|
62
74
|
appBarStyle: KasyAppBarStyle.rootTab,
|
|
63
75
|
scrollController: _scrollController,
|
|
64
76
|
trailing: Builder(
|
|
65
|
-
builder: (ctx) =>
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
77
|
+
builder: (ctx) => Row(
|
|
78
|
+
mainAxisSize: MainAxisSize.min,
|
|
79
|
+
children: [
|
|
80
|
+
if (hasUnread)
|
|
81
|
+
KasyChromeOrbIconButton(
|
|
82
|
+
key: const Key('mark_all_read_button'),
|
|
83
|
+
icon: KasyIcons.check,
|
|
84
|
+
iconSize: 18,
|
|
85
|
+
foregroundColor: ctx.colors.onSurface,
|
|
86
|
+
onPressed: () =>
|
|
87
|
+
ref.read(notificationsProvider.notifier).readAll(),
|
|
88
|
+
tooltip: t.notifications.mark_all_read,
|
|
89
|
+
),
|
|
90
|
+
if (hasUnread) const SizedBox(width: KasySpacing.xs),
|
|
91
|
+
KasyChromeOrbIconButton(
|
|
92
|
+
icon: KasyIcons.moreVert,
|
|
93
|
+
iconSize: 18,
|
|
94
|
+
foregroundColor: ctx.colors.onSurface,
|
|
95
|
+
onPressed: () => showNotificationSettingsSheet(ctx),
|
|
96
|
+
tooltip: 'Opções',
|
|
97
|
+
),
|
|
98
|
+
],
|
|
71
99
|
),
|
|
72
100
|
),
|
|
73
101
|
onRefresh: () async {
|
|
@@ -7,7 +7,6 @@ import 'package:kasy_kit/features/subscription/ui/component/premium_page_factory
|
|
|
7
7
|
const String adminRoutePaywalls = '/admin/paywalls';
|
|
8
8
|
const String adminRouteHomeWidgets = '/admin/home-widgets';
|
|
9
9
|
const String adminRouteSendPush = '/admin/send-push';
|
|
10
|
-
const String adminRouteKeyboardTest = '/admin/keyboard-test';
|
|
11
10
|
|
|
12
11
|
String adminRoutePremiumPreview(String variant) => '/admin/premium/$variant';
|
|
13
12
|
|
|
@@ -14,7 +14,6 @@ import 'package:kasy_kit/features/authentication/ui/phone_auth_page.dart';
|
|
|
14
14
|
import 'package:kasy_kit/features/authentication/ui/recover_password_page.dart';
|
|
15
15
|
import 'package:kasy_kit/features/authentication/ui/signin_page.dart';
|
|
16
16
|
import 'package:kasy_kit/features/authentication/ui/signup_page.dart';
|
|
17
|
-
import 'package:kasy_kit/features/dev/keyboard_test_page.dart';
|
|
18
17
|
import 'package:kasy_kit/features/feedbacks/ui/feedback_page.dart';
|
|
19
18
|
import 'package:kasy_kit/features/llm_chat/llm_chat_page.dart';
|
|
20
19
|
import 'package:kasy_kit/features/local_reminder/ui/reminder_page.dart';
|
|
@@ -205,14 +204,6 @@ GoRouter generateRouter({
|
|
|
205
204
|
child: const SendPushNotificationPage(),
|
|
206
205
|
),
|
|
207
206
|
),
|
|
208
|
-
GoRoute(
|
|
209
|
-
name: 'keyboard_test',
|
|
210
|
-
path: adminRouteKeyboardTest,
|
|
211
|
-
pageBuilder: (context, state) => kasyTransitionPage(
|
|
212
|
-
key: state.pageKey,
|
|
213
|
-
child: const KeyboardTestPage(),
|
|
214
|
-
),
|
|
215
|
-
),
|
|
216
207
|
GoRoute(
|
|
217
208
|
name: '404',
|
|
218
209
|
path: '/404',
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import 'package:flutter/material.dart';
|
|
1
2
|
import 'package:flutter_test/flutter_test.dart';
|
|
2
3
|
import 'package:kasy_kit/core/data/models/user.dart';
|
|
3
4
|
import 'package:kasy_kit/core/states/models/user_state.dart';
|
|
@@ -8,61 +9,86 @@ import 'package:kasy_kit/features/notifications/ui/widgets/notification_tile.dar
|
|
|
8
9
|
import '../../../test_utils.dart';
|
|
9
10
|
|
|
10
11
|
void main() {
|
|
11
|
-
|
|
12
|
+
UserState authenticatedUser() => UserState(
|
|
13
|
+
user: User.authenticated(
|
|
14
|
+
id: '1',
|
|
15
|
+
email: 'user@email.com',
|
|
16
|
+
name: 'user name',
|
|
17
|
+
onboarded: true,
|
|
18
|
+
creationDate: DateTime.now().subtract(const Duration(days: 4)),
|
|
19
|
+
),
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
group('NotificationsPage — rendering', () {
|
|
23
|
+
testWidgets('renders title and notification tiles', (tester) async {
|
|
24
|
+
await tester.pumpPage(
|
|
25
|
+
userState: authenticatedUser(),
|
|
26
|
+
home: NotificationsPage(),
|
|
27
|
+
);
|
|
28
|
+
await tester.pumpAndSettle();
|
|
29
|
+
|
|
30
|
+
expect(find.byType(NotificationsPage), findsOneWidget);
|
|
31
|
+
expect(find.text('Notifications'), findsOneWidget);
|
|
32
|
+
expect(find.byType(NotificationTile), findsAtLeastNWidgets(3));
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
group('NotificationsPage — auto-read behavior', () {
|
|
12
37
|
testWidgets(
|
|
13
|
-
'
|
|
38
|
+
'after 1.5s on screen, all notifications are marked as read',
|
|
14
39
|
(tester) async {
|
|
15
40
|
await tester.pumpPage(
|
|
16
|
-
userState:
|
|
17
|
-
user: User.authenticated(
|
|
18
|
-
id: '1',
|
|
19
|
-
email: 'user@email.com',
|
|
20
|
-
name: 'user name',
|
|
21
|
-
onboarded: true,
|
|
22
|
-
creationDate: DateTime.now().subtract(const Duration(days: 4)),
|
|
23
|
-
),
|
|
24
|
-
),
|
|
41
|
+
userState: authenticatedUser(),
|
|
25
42
|
home: NotificationsPage(),
|
|
26
43
|
);
|
|
27
|
-
await tester.pump(const Duration(
|
|
44
|
+
await tester.pump(const Duration(milliseconds: 1600));
|
|
28
45
|
await tester.pumpAndSettle();
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
46
|
+
|
|
47
|
+
final tiles = tester
|
|
48
|
+
.widgetList<NotificationTileComponent>(
|
|
49
|
+
find.byType(NotificationTileComponent),
|
|
50
|
+
)
|
|
51
|
+
.toList();
|
|
52
|
+
expect(tiles, isNotEmpty);
|
|
53
|
+
for (final tile in tiles) {
|
|
54
|
+
expect(
|
|
55
|
+
tile.notification.readAt,
|
|
56
|
+
isNotNull,
|
|
57
|
+
reason: 'All notifications should be read after auto-read fires',
|
|
58
|
+
);
|
|
59
|
+
}
|
|
32
60
|
},
|
|
33
61
|
);
|
|
62
|
+
});
|
|
34
63
|
|
|
64
|
+
group('NotificationsPage — mark all read button', () {
|
|
35
65
|
testWidgets(
|
|
36
|
-
'
|
|
66
|
+
'tapping the button marks all notifications as read immediately',
|
|
37
67
|
(tester) async {
|
|
38
68
|
await tester.pumpPage(
|
|
39
|
-
userState:
|
|
40
|
-
user: User.authenticated(
|
|
41
|
-
id: '1',
|
|
42
|
-
email: 'user@email.com',
|
|
43
|
-
name: 'user name',
|
|
44
|
-
onboarded: true,
|
|
45
|
-
creationDate: DateTime.now().subtract(const Duration(days: 4)),
|
|
46
|
-
),
|
|
47
|
-
),
|
|
69
|
+
userState: authenticatedUser(),
|
|
48
70
|
home: NotificationsPage(),
|
|
49
71
|
);
|
|
50
|
-
var firstNotification = tester.firstWidget<NotificationTileComponent>(
|
|
51
|
-
find.byType(NotificationTileComponent),
|
|
52
|
-
);
|
|
53
|
-
expect(firstNotification.notification.readAt, isNull);
|
|
54
|
-
|
|
55
|
-
await tester.pump(const Duration(seconds: 3));
|
|
56
72
|
await tester.pumpAndSettle();
|
|
57
|
-
firstNotification = tester.firstWidget<NotificationTileComponent>(
|
|
58
|
-
find.byType(NotificationTileComponent),
|
|
59
|
-
);
|
|
60
73
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
)
|
|
74
|
+
// Auto-read may have already run during pumpAndSettle.
|
|
75
|
+
// The button is hidden when no unread notifications remain — that is
|
|
76
|
+
// expected and itself a correct behavior.
|
|
77
|
+
final button = find.byKey(const Key('mark_all_read_button'));
|
|
78
|
+
if (button.evaluate().isNotEmpty) {
|
|
79
|
+
await tester.tap(button);
|
|
80
|
+
await tester.pumpAndSettle();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
final tiles = tester
|
|
84
|
+
.widgetList<NotificationTileComponent>(
|
|
85
|
+
find.byType(NotificationTileComponent),
|
|
86
|
+
)
|
|
87
|
+
.toList();
|
|
88
|
+
expect(tiles, isNotEmpty);
|
|
89
|
+
for (final tile in tiles) {
|
|
90
|
+
expect(tile.notification.readAt, isNotNull);
|
|
91
|
+
}
|
|
66
92
|
},
|
|
67
93
|
);
|
|
68
94
|
});
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import 'package:flutter/material.dart';
|
|
2
|
-
import 'package:kasy_kit/components/kasy_text_field.dart';
|
|
3
|
-
import 'package:kasy_kit/core/theme/theme.dart';
|
|
4
|
-
|
|
5
|
-
class KeyboardTestPage extends StatefulWidget {
|
|
6
|
-
const KeyboardTestPage({super.key});
|
|
7
|
-
|
|
8
|
-
@override
|
|
9
|
-
State<KeyboardTestPage> createState() => _KeyboardTestPageState();
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
class _KeyboardTestPageState extends State<KeyboardTestPage> {
|
|
13
|
-
final _name = TextEditingController();
|
|
14
|
-
final _email = TextEditingController();
|
|
15
|
-
final _phone = TextEditingController();
|
|
16
|
-
final _password = TextEditingController();
|
|
17
|
-
final _bio = TextEditingController();
|
|
18
|
-
|
|
19
|
-
@override
|
|
20
|
-
void dispose() {
|
|
21
|
-
_name.dispose();
|
|
22
|
-
_email.dispose();
|
|
23
|
-
_phone.dispose();
|
|
24
|
-
_password.dispose();
|
|
25
|
-
_bio.dispose();
|
|
26
|
-
super.dispose();
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
@override
|
|
30
|
-
Widget build(BuildContext context) {
|
|
31
|
-
return Scaffold(
|
|
32
|
-
appBar: AppBar(title: const Text('Keyboard Test')),
|
|
33
|
-
body: ListView(
|
|
34
|
-
padding: EdgeInsets.fromLTRB(
|
|
35
|
-
KasySpacing.pageHorizontalGutter,
|
|
36
|
-
KasySpacing.md,
|
|
37
|
-
KasySpacing.pageHorizontalGutter,
|
|
38
|
-
MediaQuery.paddingOf(context).bottom + 40,
|
|
39
|
-
),
|
|
40
|
-
children: [
|
|
41
|
-
const Text(
|
|
42
|
-
'Tap between the fields — the keyboard should stay open without closing and reopening.',
|
|
43
|
-
style: TextStyle(fontSize: 13, color: Colors.grey),
|
|
44
|
-
),
|
|
45
|
-
const SizedBox(height: KasySpacing.lg),
|
|
46
|
-
KasyTextField(
|
|
47
|
-
controller: _name,
|
|
48
|
-
label: 'Name',
|
|
49
|
-
hint: 'Your full name',
|
|
50
|
-
textInputAction: TextInputAction.next,
|
|
51
|
-
textCapitalization: TextCapitalization.words,
|
|
52
|
-
),
|
|
53
|
-
const SizedBox(height: KasyTextField.adjacentFieldSpacing),
|
|
54
|
-
KasyTextField(
|
|
55
|
-
controller: _email,
|
|
56
|
-
label: 'Email',
|
|
57
|
-
hint: 'you@example.com',
|
|
58
|
-
contentType: KasyTextFieldContentType.email,
|
|
59
|
-
keyboardType: TextInputType.emailAddress,
|
|
60
|
-
textInputAction: TextInputAction.next,
|
|
61
|
-
),
|
|
62
|
-
const SizedBox(height: KasyTextField.adjacentFieldSpacing),
|
|
63
|
-
KasyTextField(
|
|
64
|
-
controller: _phone,
|
|
65
|
-
label: 'Phone',
|
|
66
|
-
hint: '+55 11 99999-9999',
|
|
67
|
-
contentType: KasyTextFieldContentType.phone,
|
|
68
|
-
keyboardType: TextInputType.phone,
|
|
69
|
-
textInputAction: TextInputAction.next,
|
|
70
|
-
),
|
|
71
|
-
const SizedBox(height: KasyTextField.adjacentFieldSpacing),
|
|
72
|
-
KasyTextField(
|
|
73
|
-
controller: _password,
|
|
74
|
-
label: 'Password',
|
|
75
|
-
hint: 'Min 8 characters',
|
|
76
|
-
contentType: KasyTextFieldContentType.password,
|
|
77
|
-
textInputAction: TextInputAction.next,
|
|
78
|
-
),
|
|
79
|
-
const SizedBox(height: KasyTextField.adjacentFieldSpacing),
|
|
80
|
-
KasyTextField(
|
|
81
|
-
controller: _bio,
|
|
82
|
-
label: 'Bio',
|
|
83
|
-
hint: 'Tell us about yourself',
|
|
84
|
-
textInputAction: TextInputAction.done,
|
|
85
|
-
minLines: 3,
|
|
86
|
-
maxLines: 5,
|
|
87
|
-
textCapitalization: TextCapitalization.sentences,
|
|
88
|
-
),
|
|
89
|
-
],
|
|
90
|
-
),
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
}
|