kasy-cli 1.17.0 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/bin/kasy.js +15 -2
  2. package/lib/commands/add.js +7 -7
  3. package/lib/commands/configure.js +548 -0
  4. package/lib/commands/deploy.js +4 -4
  5. package/lib/commands/doctor.js +17 -0
  6. package/lib/commands/favicon.js +4 -4
  7. package/lib/commands/icon.js +5 -5
  8. package/lib/commands/new.js +403 -238
  9. package/lib/commands/run.js +1 -1
  10. package/lib/commands/splash.js +5 -5
  11. package/lib/commands/update.js +9 -9
  12. package/lib/scaffold/CHANGELOG.json +14 -0
  13. package/lib/scaffold/backends/firebase/enable-auth-via-cli.js +108 -0
  14. package/lib/scaffold/backends/firebase/setup-from-scratch.js +44 -5
  15. package/lib/scaffold/generate.js +24 -8
  16. package/lib/scaffold/shared/post-build.js +8 -0
  17. package/lib/utils/brand.js +16 -12
  18. package/lib/utils/flutter-run.js +139 -11
  19. package/lib/utils/i18n/messages-en.js +58 -5
  20. package/lib/utils/i18n/messages-es.js +58 -5
  21. package/lib/utils/i18n/messages-pt.js +59 -6
  22. package/lib/utils/ui.js +79 -4
  23. package/package.json +1 -1
  24. package/templates/firebase/README.en.md +1 -1
  25. package/templates/firebase/README.es.md +1 -1
  26. package/templates/firebase/README.md +1 -1
  27. package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MainActivity.kt +0 -15
  28. package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MyWidget.kt +65 -26
  29. package/templates/firebase/android/app/src/main/res/drawable/widget_add_button.xml +2 -2
  30. package/templates/firebase/android/app/src/main/res/drawable/widget_gradient_bg.xml +7 -2
  31. package/templates/firebase/android/app/src/main/res/drawable/widget_gradient_inner.xml +6 -6
  32. package/templates/firebase/android/app/src/main/res/drawable/widget_plan_pill_bg.xml +1 -1
  33. package/templates/firebase/android/app/src/main/res/drawable/widget_preview_image.xml +2 -2
  34. package/templates/firebase/android/app/src/main/res/drawable/widget_pro_pill_bg.xml +1 -1
  35. package/templates/firebase/android/app/src/main/res/drawable-hdpi/android12splash.png +0 -0
  36. package/templates/firebase/android/app/src/main/res/drawable-hdpi/splash.png +0 -0
  37. package/templates/firebase/android/app/src/main/res/drawable-mdpi/android12splash.png +0 -0
  38. package/templates/firebase/android/app/src/main/res/drawable-mdpi/splash.png +0 -0
  39. package/templates/firebase/android/app/src/main/res/drawable-night-hdpi/android12splash.png +0 -0
  40. package/templates/firebase/android/app/src/main/res/drawable-night-hdpi/splash.png +0 -0
  41. package/templates/firebase/android/app/src/main/res/drawable-night-mdpi/android12splash.png +0 -0
  42. package/templates/firebase/android/app/src/main/res/drawable-night-mdpi/splash.png +0 -0
  43. package/templates/firebase/android/app/src/main/res/drawable-night-xhdpi/android12splash.png +0 -0
  44. package/templates/firebase/android/app/src/main/res/drawable-night-xhdpi/splash.png +0 -0
  45. package/templates/firebase/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png +0 -0
  46. package/templates/firebase/android/app/src/main/res/drawable-night-xxhdpi/splash.png +0 -0
  47. package/templates/firebase/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png +0 -0
  48. package/templates/firebase/android/app/src/main/res/drawable-night-xxxhdpi/splash.png +0 -0
  49. package/templates/firebase/android/app/src/main/res/drawable-xhdpi/android12splash.png +0 -0
  50. package/templates/firebase/android/app/src/main/res/drawable-xhdpi/splash.png +0 -0
  51. package/templates/firebase/android/app/src/main/res/drawable-xxhdpi/android12splash.png +0 -0
  52. package/templates/firebase/android/app/src/main/res/drawable-xxhdpi/splash.png +0 -0
  53. package/templates/firebase/android/app/src/main/res/drawable-xxxhdpi/android12splash.png +0 -0
  54. package/templates/firebase/android/app/src/main/res/drawable-xxxhdpi/splash.png +0 -0
  55. package/templates/firebase/android/app/src/main/res/layout/widget_preview.xml +3 -3
  56. package/templates/firebase/android/app/src/main/res/values/colors.xml +32 -0
  57. package/templates/firebase/assets/images/splash_logo_dark.png +0 -0
  58. package/templates/firebase/assets/images/splash_logo_dark_android12.png +0 -0
  59. package/templates/firebase/assets/images/splash_logo_light.png +0 -0
  60. package/templates/firebase/assets/images/splash_logo_light_android12.png +0 -0
  61. package/templates/firebase/ios/HomeWidgetExtension/MyWidget.swift +75 -29
  62. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png +0 -0
  63. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png +0 -0
  64. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png +0 -0
  65. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark.png +0 -0
  66. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png +0 -0
  67. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@3x.png +0 -0
  68. package/templates/firebase/ios/Runner/Base.lproj/LaunchScreen.storyboard +1 -1
  69. package/templates/firebase/lib/components/components.dart +1 -0
  70. package/templates/firebase/lib/components/kasy_avatar.dart +65 -17
  71. package/templates/firebase/lib/components/kasy_avatar_presets.dart +121 -97
  72. package/templates/firebase/lib/components/kasy_button.dart +8 -8
  73. package/templates/firebase/lib/components/kasy_date_picker.dart +834 -0
  74. package/templates/firebase/lib/components/kasy_tabs.dart +145 -61
  75. package/templates/firebase/lib/core/home_widgets/home_widget_mywidget_service.dart +12 -18
  76. package/templates/firebase/lib/features/home/home_components_preview_registry.dart +565 -77
  77. package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +10 -5
  78. package/templates/firebase/lib/i18n/en.i18n.json +2 -1
  79. package/templates/firebase/lib/i18n/es.i18n.json +2 -1
  80. package/templates/firebase/lib/i18n/pt.i18n.json +2 -1
  81. package/templates/firebase/pubspec.yaml +1 -1
  82. package/templates/firebase/web/index.html +9 -0
  83. package/templates/firebase/web/splash/img/dark-1x.png +0 -0
  84. package/templates/firebase/web/splash/img/dark-2x.png +0 -0
  85. package/templates/firebase/web/splash/img/dark-3x.png +0 -0
  86. package/templates/firebase/web/splash/img/dark-4x.png +0 -0
  87. package/templates/firebase/web/splash/img/light-1x.png +0 -0
  88. package/templates/firebase/web/splash/img/light-2x.png +0 -0
  89. package/templates/firebase/web/splash/img/light-3x.png +0 -0
  90. package/templates/firebase/web/splash/img/light-4x.png +0 -0
@@ -33,7 +33,7 @@ enum KasyTabsVariant {
33
33
 
34
34
  /// Sizing mode for [KasyTabs].
35
35
  enum KasyTabsMode {
36
- /// Wraps content (intrinsic width tabs).
36
+ /// Wraps content (intrinsic width tabs). Scrolls horizontally if needed.
37
37
  hug,
38
38
 
39
39
  /// Each tab stretches to fill the available width equally.
@@ -99,6 +99,10 @@ class _KasyTabsState extends State<KasyTabs> {
99
99
  // One GlobalKey per tab to measure position/size after layout.
100
100
  late List<GlobalKey> _keys;
101
101
 
102
+ // Key for the inner indicator Stack — used as the coordinate reference
103
+ // for measurements, ensuring correctness even when wrapped in a ScrollView.
104
+ final GlobalKey _stackKey = GlobalKey();
105
+
102
106
  // Indicator geometry (left offset, width) resolved from measured keys.
103
107
  double _indicatorLeft = 0;
104
108
  double _indicatorWidth = 0;
@@ -140,8 +144,10 @@ class _KasyTabsState extends State<KasyTabs> {
140
144
  widget.items.length - 1,
141
145
  );
142
146
 
147
+ // Measure relative to the inner Stack, not the outermost widget.
148
+ // This stays correct even when the component is inside a ScrollView.
143
149
  final RenderBox? containerBox =
144
- context.findRenderObject() as RenderBox?;
150
+ _stackKey.currentContext?.findRenderObject() as RenderBox?;
145
151
  if (containerBox == null) return;
146
152
 
147
153
  final GlobalKey key = _keys[clampedIndex];
@@ -166,6 +172,22 @@ class _KasyTabsState extends State<KasyTabs> {
166
172
  _measured = true;
167
173
  });
168
174
  }
175
+
176
+ // In hug mode, scroll the selected tab into view (handles overflow).
177
+ if (widget.mode == KasyTabsMode.hug) {
178
+ _ensureSelectedVisible(clampedIndex);
179
+ }
180
+ }
181
+
182
+ /// Scrolls the [SingleChildScrollView] so the selected tab is fully visible.
183
+ void _ensureSelectedVisible(int index) {
184
+ final BuildContext? ctx = _keys[index].currentContext;
185
+ if (ctx == null) return;
186
+ Scrollable.ensureVisible(
187
+ ctx,
188
+ duration: const Duration(milliseconds: 250),
189
+ curve: Curves.easeInOut,
190
+ );
169
191
  }
170
192
 
171
193
  @override
@@ -182,25 +204,31 @@ class _KasyTabsState extends State<KasyTabs> {
182
204
  Widget _buildPrimary(BuildContext context) {
183
205
  final KasyColors c = context.colors;
184
206
  final bool isDark = context.isDark;
207
+ final bool isFill = widget.mode == KasyTabsMode.fill;
185
208
 
186
- return DecoratedBox(
209
+ final Widget inner = DecoratedBox(
187
210
  decoration: BoxDecoration(
188
211
  color: c.avatarFallbackFill,
189
212
  borderRadius: BorderRadius.circular(KasyRadius.full),
190
213
  ),
191
214
  child: Padding(
192
- padding: const EdgeInsets.all(4),
215
+ // 8px horizontal, 4px vertical — per Figma spec.
216
+ padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
193
217
  child: Stack(
218
+ key: _stackKey,
219
+ // Allow pill to extend 4px beyond each tab edge (Figma: inset 0 -4px).
220
+ clipBehavior: Clip.none,
194
221
  children: [
195
222
  // Animated pill background.
196
223
  if (_measured)
197
224
  AnimatedPositioned(
198
225
  duration: const Duration(milliseconds: 250),
199
226
  curve: Curves.easeInOut,
200
- left: _indicatorLeft,
227
+ // Extends 4px on each side beyond the measured tab.
228
+ left: _indicatorLeft - 4,
201
229
  top: 0,
202
230
  bottom: 0,
203
- width: _indicatorWidth,
231
+ width: _indicatorWidth + 8,
204
232
  child: DecoratedBox(
205
233
  decoration: BoxDecoration(
206
234
  color: c.surface,
@@ -208,50 +236,63 @@ class _KasyTabsState extends State<KasyTabs> {
208
236
  boxShadow: [
209
237
  BoxShadow(
210
238
  color: Colors.black.withValues(
211
- alpha: isDark ? 0.18 : 0.08,
239
+ alpha: isDark ? 0.14 : 0.06,
212
240
  ),
213
241
  blurRadius: 8,
214
242
  offset: const Offset(0, 2),
215
243
  ),
216
- BoxShadow(
217
- color: Colors.black.withValues(
218
- alpha: isDark ? 0.10 : 0.04,
219
- ),
220
- spreadRadius: 1,
221
- ),
222
244
  ],
223
245
  ),
224
246
  ),
225
247
  ),
226
248
  // Tab labels (on top of the pill).
227
249
  Row(
228
- mainAxisSize: widget.mode == KasyTabsMode.fill
229
- ? MainAxisSize.max
230
- : MainAxisSize.min,
231
- children: List.generate(widget.items.length, (i) {
232
- final KasyTabItem item = widget.items[i];
233
- final bool selected = i == widget.selectedIndex;
234
- return _PrimaryTab(
235
- key: _keys[i],
236
- item: item,
237
- selected: selected,
238
- expand: widget.mode == KasyTabsMode.fill,
239
- onTap: item.enabled ? () => widget.onTabSelected(i) : null,
240
- );
241
- }),
250
+ mainAxisSize: isFill ? MainAxisSize.max : MainAxisSize.min,
251
+ children: [
252
+ for (int i = 0; i < widget.items.length; i++) ...[
253
+ // 2px gap between adjacent tabs — per Figma spec.
254
+ if (i > 0) const SizedBox(width: 2),
255
+ _PrimaryTab(
256
+ key: _keys[i],
257
+ item: widget.items[i],
258
+ selected: i == widget.selectedIndex,
259
+ expand: isFill,
260
+ onTap: widget.items[i].enabled
261
+ ? () => widget.onTabSelected(i)
262
+ : null,
263
+ ),
264
+ ],
265
+ ],
242
266
  ),
243
267
  ],
244
268
  ),
245
269
  ),
246
270
  );
271
+
272
+ // In hug mode, the component must size to its content — NOT stretch to
273
+ // parent width. SingleChildScrollView gives unconstrained horizontal
274
+ // space to its child, so the DecoratedBox → Stack → Row(min) chain
275
+ // naturally measures at intrinsic width. It also handles overflow
276
+ // gracefully (scrollable) when many long tab labels are used.
277
+ if (!isFill) {
278
+ return SingleChildScrollView(
279
+ scrollDirection: Axis.horizontal,
280
+ physics: const ClampingScrollPhysics(),
281
+ child: inner,
282
+ );
283
+ }
284
+
285
+ return inner;
247
286
  }
248
287
 
249
288
  // ── Secondary (underline indicator) ───────────────────────────────────────
250
289
 
251
290
  Widget _buildSecondary(BuildContext context) {
252
291
  final KasyColors c = context.colors;
292
+ final bool isFill = widget.mode == KasyTabsMode.fill;
253
293
 
254
- return Stack(
294
+ final Widget inner = Stack(
295
+ key: _stackKey,
255
296
  clipBehavior: Clip.none,
256
297
  children: [
257
298
  // Full-width bottom divider.
@@ -284,9 +325,7 @@ class _KasyTabsState extends State<KasyTabs> {
284
325
  ),
285
326
  // Tab labels.
286
327
  Row(
287
- mainAxisSize: widget.mode == KasyTabsMode.fill
288
- ? MainAxisSize.max
289
- : MainAxisSize.min,
328
+ mainAxisSize: isFill ? MainAxisSize.max : MainAxisSize.min,
290
329
  children: List.generate(widget.items.length, (i) {
291
330
  final KasyTabItem item = widget.items[i];
292
331
  final bool selected = i == widget.selectedIndex;
@@ -294,13 +333,24 @@ class _KasyTabsState extends State<KasyTabs> {
294
333
  key: _keys[i],
295
334
  item: item,
296
335
  selected: selected,
297
- expand: widget.mode == KasyTabsMode.fill,
336
+ expand: isFill,
298
337
  onTap: item.enabled ? () => widget.onTabSelected(i) : null,
299
338
  );
300
339
  }),
301
340
  ),
302
341
  ],
303
342
  );
343
+
344
+ // Same as primary: hug mode sizes to content via ScrollView.
345
+ if (!isFill) {
346
+ return SingleChildScrollView(
347
+ scrollDirection: Axis.horizontal,
348
+ physics: const ClampingScrollPhysics(),
349
+ child: inner,
350
+ );
351
+ }
352
+
353
+ return inner;
304
354
  }
305
355
  }
306
356
 
@@ -325,39 +375,66 @@ class _PrimaryTab extends StatelessWidget {
325
375
  Widget _tabContent(BuildContext context) {
326
376
  final KasyColors c = context.colors;
327
377
  final bool disabled = !item.enabled;
378
+ // Fill mode with icons uses a vertical (Column) layout per Figma spec:
379
+ // icon stacked above label, 12px all-sides padding, 12px font size.
380
+ final bool verticalLayout = expand && item.icon != null;
381
+
382
+ final Widget iconWidget = item.icon != null
383
+ ? Opacity(
384
+ opacity: disabled ? 0.4 : 1.0,
385
+ child: Icon(
386
+ item.icon,
387
+ size: 16,
388
+ color: selected ? c.onSurface : c.muted,
389
+ ),
390
+ )
391
+ : const SizedBox.shrink();
392
+
393
+ final Widget labelWidget = Opacity(
394
+ opacity: disabled ? 0.4 : 1.0,
395
+ child: Text(
396
+ item.label,
397
+ textAlign: TextAlign.center,
398
+ // Use labelLarge as defined in the Kasy theme (14px/w600).
399
+ // No fontWeight override — the theme token is the source of truth.
400
+ style: context.textTheme.labelLarge?.copyWith(
401
+ color: selected ? c.onSurface : c.muted,
402
+ // Fill+icon layout uses 12px (text-xs) — per Figma spec.
403
+ fontSize: verticalLayout ? 12 : null,
404
+ ),
405
+ ),
406
+ );
328
407
 
329
408
  return GestureDetector(
330
409
  onTap: onTap,
331
410
  behavior: HitTestBehavior.opaque,
332
411
  child: Padding(
333
- padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
334
- child: Row(
335
- mainAxisSize: MainAxisSize.min,
336
- mainAxisAlignment: MainAxisAlignment.center,
337
- children: [
338
- if (item.icon != null) ...[
339
- Opacity(
340
- opacity: disabled ? 0.4 : 1.0,
341
- child: Icon(
342
- item.icon,
343
- size: 16,
344
- color: selected ? c.onSurface : c.muted,
345
- ),
346
- ),
347
- const SizedBox(width: 6),
348
- ],
349
- Opacity(
350
- opacity: disabled ? 0.4 : 1.0,
351
- child: Text(
352
- item.label,
353
- style: context.textTheme.labelLarge?.copyWith(
354
- color: selected ? c.onSurface : c.muted,
355
- fontWeight: selected ? FontWeight.w500 : FontWeight.w400,
356
- ),
412
+ padding: verticalLayout
413
+ // Fill+icon: 12px all sides — per Figma spec.
414
+ ? const EdgeInsets.all(12)
415
+ // Default: 6px vertical, 12px horizontal — per Figma spec.
416
+ : const EdgeInsets.symmetric(vertical: 6, horizontal: 12),
417
+ child: verticalLayout
418
+ ? Column(
419
+ mainAxisSize: MainAxisSize.min,
420
+ mainAxisAlignment: MainAxisAlignment.center,
421
+ children: [
422
+ iconWidget,
423
+ const SizedBox(height: 6),
424
+ labelWidget,
425
+ ],
426
+ )
427
+ : Row(
428
+ mainAxisSize: MainAxisSize.min,
429
+ mainAxisAlignment: MainAxisAlignment.center,
430
+ children: [
431
+ if (item.icon != null) ...[
432
+ iconWidget,
433
+ const SizedBox(width: 6),
434
+ ],
435
+ labelWidget,
436
+ ],
357
437
  ),
358
- ),
359
- ],
360
- ),
361
438
  ),
362
439
  );
363
440
  }
@@ -391,7 +468,14 @@ class _SecondaryTab extends StatelessWidget {
391
468
  onTap: onTap,
392
469
  behavior: HitTestBehavior.opaque,
393
470
  child: Padding(
394
- padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
471
+ // top:4 bottom:6 horizontal:12 — per Figma spec.
472
+ // Extra bottom padding visually balances the 2px underline indicator.
473
+ padding: const EdgeInsets.only(
474
+ top: 4,
475
+ bottom: 6,
476
+ left: 12,
477
+ right: 12,
478
+ ),
395
479
  child: Row(
396
480
  mainAxisSize: MainAxisSize.min,
397
481
  mainAxisAlignment: MainAxisAlignment.center,
@@ -411,9 +495,9 @@ class _SecondaryTab extends StatelessWidget {
411
495
  opacity: disabled ? 0.4 : 1.0,
412
496
  child: Text(
413
497
  item.label,
498
+ // Use labelLarge as defined in the Kasy theme (14px/w600).
414
499
  style: context.textTheme.labelLarge?.copyWith(
415
500
  color: selected ? c.onSurface : c.muted,
416
- fontWeight: selected ? FontWeight.w600 : FontWeight.w400,
417
501
  ),
418
502
  ),
419
503
  ),
@@ -71,12 +71,12 @@ class MyWidgetHomeWidget extends _$MyWidgetHomeWidget
71
71
  final user = ref.read(userStateNotifierProvider).user;
72
72
 
73
73
  // Slang lazy-loads non-base locales: AppLocale.pt.translations falls back
74
- // silently to the base locale (en) until the locale's bundle is loaded
75
- // into memory. setLocale() kicks off that load asynchronously, so a tap
76
- // on "Português" immediately followed by updateForLocale(pt) would push
77
- // English text to the widget on the first try and the correct one on
78
- // the retry. Awaiting loadLocale here removes the race.
79
- await LocaleSettings.instance.loadLocale(locale);
74
+ // silently to the base locale (en) if the bundle isn't in the translation
75
+ // map yet. The async loadLocale() can't be used here as a safety net
76
+ // because it short-circuits ("already loading") when setLocale() is in
77
+ // flight in parallel leaving us reading translations that aren't loaded
78
+ // yet. loadLocaleSync forces the bundle into the map right now, no race.
79
+ LocaleSettings.instance.loadLocaleSync(locale);
80
80
  final t = locale.translations;
81
81
 
82
82
  // "Logged out" = no user id at all (post-logout in authRequired mode, or
@@ -111,6 +111,7 @@ class MyWidgetHomeWidget extends _$MyWidgetHomeWidget
111
111
  ? ''
112
112
  : (isPro ? t.home_widget.plan_pro : t.home_widget.plan_free);
113
113
  final quote = t.home_widget.quote;
114
+ final quoteAuthor = t.home_widget.quote_author;
114
115
 
115
116
  logger.d(
116
117
  'Widget payload → greeting: "$greeting", title: "$title", '
@@ -123,6 +124,7 @@ class MyWidgetHomeWidget extends _$MyWidgetHomeWidget
123
124
  'planText': planText,
124
125
  'isPro': isPro.toString(),
125
126
  'quote': quote,
127
+ 'quoteAuthor': quoteAuthor,
126
128
  });
127
129
  }
128
130
 
@@ -164,6 +166,10 @@ class MyWidgetHomeWidget extends _$MyWidgetHomeWidget
164
166
  await HomeWidget.saveWidgetData<String>('planText', data['planText'] ?? '');
165
167
  await HomeWidget.saveWidgetData<String>('isPro', data['isPro'] ?? 'false');
166
168
  await HomeWidget.saveWidgetData<String>('quote', data['quote'] ?? '');
169
+ await HomeWidget.saveWidgetData<String>(
170
+ 'quoteAuthor',
171
+ data['quoteAuthor'] ?? '',
172
+ );
167
173
 
168
174
  await HomeWidget.updateWidget(
169
175
  name: _androidWidgetName,
@@ -171,18 +177,6 @@ class MyWidgetHomeWidget extends _$MyWidgetHomeWidget
171
177
  );
172
178
  }
173
179
 
174
- Future<Map<String, dynamic>> getWidgetData() async {
175
- return {
176
- 'greeting': await HomeWidget.getWidgetData<String>('greeting'),
177
- 'title': await HomeWidget.getWidgetData<String>('title'),
178
- 'planText': await HomeWidget.getWidgetData<String>('planText'),
179
- 'isPro': await HomeWidget.getWidgetData<String>(
180
- 'isPro',
181
- defaultValue: 'false',
182
- ),
183
- };
184
- }
185
-
186
180
  /// Time-of-day greeting in the app language (matches what the user sees
187
181
  /// inside the app, not the device language).
188
182
  static String _greeting(Translations t) {