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.
- package/bin/kasy.js +15 -2
- package/lib/commands/add.js +7 -7
- package/lib/commands/configure.js +548 -0
- package/lib/commands/deploy.js +4 -4
- package/lib/commands/doctor.js +17 -0
- package/lib/commands/favicon.js +4 -4
- package/lib/commands/icon.js +5 -5
- package/lib/commands/new.js +403 -238
- package/lib/commands/run.js +1 -1
- package/lib/commands/splash.js +5 -5
- package/lib/commands/update.js +9 -9
- package/lib/scaffold/CHANGELOG.json +14 -0
- package/lib/scaffold/backends/firebase/enable-auth-via-cli.js +108 -0
- package/lib/scaffold/backends/firebase/setup-from-scratch.js +44 -5
- package/lib/scaffold/generate.js +24 -8
- package/lib/scaffold/shared/post-build.js +8 -0
- package/lib/utils/brand.js +16 -12
- package/lib/utils/flutter-run.js +139 -11
- package/lib/utils/i18n/messages-en.js +58 -5
- package/lib/utils/i18n/messages-es.js +58 -5
- package/lib/utils/i18n/messages-pt.js +59 -6
- package/lib/utils/ui.js +79 -4
- package/package.json +1 -1
- package/templates/firebase/README.en.md +1 -1
- package/templates/firebase/README.es.md +1 -1
- package/templates/firebase/README.md +1 -1
- package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MainActivity.kt +0 -15
- package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MyWidget.kt +65 -26
- package/templates/firebase/android/app/src/main/res/drawable/widget_add_button.xml +2 -2
- package/templates/firebase/android/app/src/main/res/drawable/widget_gradient_bg.xml +7 -2
- package/templates/firebase/android/app/src/main/res/drawable/widget_gradient_inner.xml +6 -6
- package/templates/firebase/android/app/src/main/res/drawable/widget_plan_pill_bg.xml +1 -1
- package/templates/firebase/android/app/src/main/res/drawable/widget_preview_image.xml +2 -2
- package/templates/firebase/android/app/src/main/res/drawable/widget_pro_pill_bg.xml +1 -1
- package/templates/firebase/android/app/src/main/res/drawable-hdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-hdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-mdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-mdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-hdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-hdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-mdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-mdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xxhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xxxhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xxhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xxhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xxxhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xxxhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/layout/widget_preview.xml +3 -3
- package/templates/firebase/android/app/src/main/res/values/colors.xml +32 -0
- package/templates/firebase/assets/images/splash_logo_dark.png +0 -0
- package/templates/firebase/assets/images/splash_logo_dark_android12.png +0 -0
- package/templates/firebase/assets/images/splash_logo_light.png +0 -0
- package/templates/firebase/assets/images/splash_logo_light_android12.png +0 -0
- package/templates/firebase/ios/HomeWidgetExtension/MyWidget.swift +75 -29
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@3x.png +0 -0
- package/templates/firebase/ios/Runner/Base.lproj/LaunchScreen.storyboard +1 -1
- package/templates/firebase/lib/components/components.dart +1 -0
- package/templates/firebase/lib/components/kasy_avatar.dart +65 -17
- package/templates/firebase/lib/components/kasy_avatar_presets.dart +121 -97
- package/templates/firebase/lib/components/kasy_button.dart +8 -8
- package/templates/firebase/lib/components/kasy_date_picker.dart +834 -0
- package/templates/firebase/lib/components/kasy_tabs.dart +145 -61
- package/templates/firebase/lib/core/home_widgets/home_widget_mywidget_service.dart +12 -18
- package/templates/firebase/lib/features/home/home_components_preview_registry.dart +565 -77
- package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +10 -5
- package/templates/firebase/lib/i18n/en.i18n.json +2 -1
- package/templates/firebase/lib/i18n/es.i18n.json +2 -1
- package/templates/firebase/lib/i18n/pt.i18n.json +2 -1
- package/templates/firebase/pubspec.yaml +1 -1
- package/templates/firebase/web/index.html +9 -0
- package/templates/firebase/web/splash/img/dark-1x.png +0 -0
- package/templates/firebase/web/splash/img/dark-2x.png +0 -0
- package/templates/firebase/web/splash/img/dark-3x.png +0 -0
- package/templates/firebase/web/splash/img/dark-4x.png +0 -0
- package/templates/firebase/web/splash/img/light-1x.png +0 -0
- package/templates/firebase/web/splash/img/light-2x.png +0 -0
- package/templates/firebase/web/splash/img/light-3x.png +0 -0
- package/templates/firebase/web/splash/img/light-4x.png +0 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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:
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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
|
-
|
|
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)
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
|
|
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) {
|