kasy-cli 1.14.0 → 1.16.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 +18 -5
- package/lib/commands/icon.js +29 -1
- package/lib/commands/ios.js +8 -2
- package/lib/commands/reset.js +100 -2
- package/lib/commands/run.js +61 -2
- package/lib/commands/splash.js +11 -0
- package/lib/scaffold/backends/api/pubspec.yaml.tpl +4 -2
- package/lib/scaffold/backends/supabase/pubspec.yaml.tpl +4 -2
- package/lib/utils/apple-release.js +30 -0
- package/lib/utils/checks.js +41 -2
- package/lib/utils/debug.js +75 -0
- package/lib/utils/friendly-error.js +91 -0
- package/lib/utils/i18n/messages-en.js +977 -0
- package/lib/utils/i18n/messages-es.js +975 -0
- package/lib/utils/i18n/messages-pt.js +975 -0
- package/lib/utils/i18n.js +21 -2818
- package/lib/utils/png-padding.js +252 -0
- package/package.json +8 -3
- package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MyWidget.kt +12 -11
- package/templates/firebase/android/app/src/main/res/drawable-hdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-hdpi/ic_launcher_background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.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/ic_launcher_background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.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-mdpi/android12splash.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-xxhdpi/android12splash.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-xhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xhdpi/ic_launcher_background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.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/ic_launcher_background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.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/ic_launcher_background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png +0 -0
- package/templates/firebase/android/app/src/main/res/layout/widget_preview.xml +18 -11
- package/templates/firebase/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +9 -0
- package/templates/firebase/assets/images/icon_android.png +0 -0
- package/templates/firebase/assets/images/icon_foreground_empty.png +0 -0
- package/templates/firebase/assets/images/splash_logo_dark_android12.png +0 -0
- package/templates/firebase/assets/images/splash_logo_light_android12.png +0 -0
- package/templates/firebase/lib/components/components.dart +1 -0
- package/templates/firebase/lib/components/kasy_avatar.dart +88 -57
- package/templates/firebase/lib/components/kasy_avatar_presets.dart +116 -74
- package/templates/firebase/lib/components/kasy_tabs.dart +431 -0
- package/templates/firebase/lib/core/home_widgets/home_widget_mywidget_service.dart +12 -6
- package/templates/firebase/lib/features/home/home_components_page.dart +1 -1
- package/templates/firebase/lib/features/home/home_components_preview_registry.dart +316 -93
- package/templates/firebase/pubspec.yaml +4 -2
- package/templates/firebase/web/index.html +9 -0
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
import 'package:flutter/material.dart';
|
|
2
|
+
import 'package:kasy_kit/core/theme/theme.dart';
|
|
3
|
+
|
|
4
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
5
|
+
// Data model
|
|
6
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
/// Data model for a single tab item.
|
|
9
|
+
class KasyTabItem {
|
|
10
|
+
final String label;
|
|
11
|
+
final IconData? icon;
|
|
12
|
+
final bool enabled;
|
|
13
|
+
|
|
14
|
+
const KasyTabItem({
|
|
15
|
+
required this.label,
|
|
16
|
+
this.icon,
|
|
17
|
+
this.enabled = true,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
22
|
+
// Enums
|
|
23
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
/// Visual style for [KasyTabs].
|
|
26
|
+
enum KasyTabsVariant {
|
|
27
|
+
/// Sliding white pill on a gray container.
|
|
28
|
+
primary,
|
|
29
|
+
|
|
30
|
+
/// Blue underline indicator with a bottom divider.
|
|
31
|
+
secondary,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/// Sizing mode for [KasyTabs].
|
|
35
|
+
enum KasyTabsMode {
|
|
36
|
+
/// Wraps content (intrinsic width tabs).
|
|
37
|
+
hug,
|
|
38
|
+
|
|
39
|
+
/// Each tab stretches to fill the available width equally.
|
|
40
|
+
fill,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
44
|
+
// KasyTabs
|
|
45
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
/// Animated tab bar with primary (pill) and secondary (underline) variants.
|
|
48
|
+
///
|
|
49
|
+
/// Usage:
|
|
50
|
+
/// ```dart
|
|
51
|
+
/// KasyTabs(
|
|
52
|
+
/// tabs: ['General', 'Appearance', 'Notifications'],
|
|
53
|
+
/// selectedIndex: _index,
|
|
54
|
+
/// onTabSelected: (i) => setState(() => _index = i),
|
|
55
|
+
/// )
|
|
56
|
+
/// ```
|
|
57
|
+
///
|
|
58
|
+
/// Supports [KasyTabItem] for per-tab icons and disabled state. The short-form
|
|
59
|
+
/// constructor accepts plain [String] labels.
|
|
60
|
+
class KasyTabs extends StatefulWidget {
|
|
61
|
+
/// Plain-string convenience constructor.
|
|
62
|
+
///
|
|
63
|
+
/// Converts each string to a [KasyTabItem] with default settings.
|
|
64
|
+
KasyTabs({
|
|
65
|
+
super.key,
|
|
66
|
+
required List<String> tabs,
|
|
67
|
+
required this.selectedIndex,
|
|
68
|
+
required this.onTabSelected,
|
|
69
|
+
this.variant = KasyTabsVariant.primary,
|
|
70
|
+
this.mode = KasyTabsMode.hug,
|
|
71
|
+
}) : items = tabs.map((l) => KasyTabItem(label: l)).toList();
|
|
72
|
+
|
|
73
|
+
/// Full constructor accepting [KasyTabItem] instances (supports icons/disabled).
|
|
74
|
+
const KasyTabs.items({
|
|
75
|
+
super.key,
|
|
76
|
+
required this.items,
|
|
77
|
+
required this.selectedIndex,
|
|
78
|
+
required this.onTabSelected,
|
|
79
|
+
this.variant = KasyTabsVariant.primary,
|
|
80
|
+
this.mode = KasyTabsMode.hug,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
final List<KasyTabItem> items;
|
|
84
|
+
|
|
85
|
+
/// Index of the currently selected tab.
|
|
86
|
+
final int selectedIndex;
|
|
87
|
+
|
|
88
|
+
/// Called when the user taps an enabled tab.
|
|
89
|
+
final ValueChanged<int> onTabSelected;
|
|
90
|
+
|
|
91
|
+
final KasyTabsVariant variant;
|
|
92
|
+
final KasyTabsMode mode;
|
|
93
|
+
|
|
94
|
+
@override
|
|
95
|
+
State<KasyTabs> createState() => _KasyTabsState();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
class _KasyTabsState extends State<KasyTabs> {
|
|
99
|
+
// One GlobalKey per tab to measure position/size after layout.
|
|
100
|
+
late List<GlobalKey> _keys;
|
|
101
|
+
|
|
102
|
+
// Indicator geometry (left offset, width) resolved from measured keys.
|
|
103
|
+
double _indicatorLeft = 0;
|
|
104
|
+
double _indicatorWidth = 0;
|
|
105
|
+
|
|
106
|
+
// Whether we have a valid measurement yet.
|
|
107
|
+
bool _measured = false;
|
|
108
|
+
|
|
109
|
+
@override
|
|
110
|
+
void initState() {
|
|
111
|
+
super.initState();
|
|
112
|
+
_keys = List.generate(
|
|
113
|
+
widget.items.length,
|
|
114
|
+
(_) => GlobalKey(),
|
|
115
|
+
);
|
|
116
|
+
WidgetsBinding.instance.addPostFrameCallback((_) => _measure());
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
@override
|
|
120
|
+
void didUpdateWidget(KasyTabs old) {
|
|
121
|
+
super.didUpdateWidget(old);
|
|
122
|
+
// Rebuild keys list if tab count changes.
|
|
123
|
+
if (old.items.length != widget.items.length) {
|
|
124
|
+
_keys = List.generate(
|
|
125
|
+
widget.items.length,
|
|
126
|
+
(_) => GlobalKey(),
|
|
127
|
+
);
|
|
128
|
+
_measured = false;
|
|
129
|
+
}
|
|
130
|
+
// Re-measure whenever selected index changes or keys were rebuilt.
|
|
131
|
+
WidgetsBinding.instance.addPostFrameCallback((_) => _measure());
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
void _measure() {
|
|
135
|
+
if (!mounted) return;
|
|
136
|
+
if (widget.items.isEmpty) return;
|
|
137
|
+
|
|
138
|
+
final int clampedIndex = widget.selectedIndex.clamp(
|
|
139
|
+
0,
|
|
140
|
+
widget.items.length - 1,
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
final RenderBox? containerBox =
|
|
144
|
+
context.findRenderObject() as RenderBox?;
|
|
145
|
+
if (containerBox == null) return;
|
|
146
|
+
|
|
147
|
+
final GlobalKey key = _keys[clampedIndex];
|
|
148
|
+
final RenderBox? tabBox =
|
|
149
|
+
key.currentContext?.findRenderObject() as RenderBox?;
|
|
150
|
+
if (tabBox == null) return;
|
|
151
|
+
|
|
152
|
+
final Offset tabOffset = tabBox.localToGlobal(
|
|
153
|
+
Offset.zero,
|
|
154
|
+
ancestor: containerBox,
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
final double newLeft = tabOffset.dx;
|
|
158
|
+
final double newWidth = tabBox.size.width;
|
|
159
|
+
|
|
160
|
+
if (!_measured ||
|
|
161
|
+
(_indicatorLeft - newLeft).abs() > 0.1 ||
|
|
162
|
+
(_indicatorWidth - newWidth).abs() > 0.1) {
|
|
163
|
+
setState(() {
|
|
164
|
+
_indicatorLeft = newLeft;
|
|
165
|
+
_indicatorWidth = newWidth;
|
|
166
|
+
_measured = true;
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
@override
|
|
172
|
+
Widget build(BuildContext context) {
|
|
173
|
+
if (widget.items.isEmpty) return const SizedBox.shrink();
|
|
174
|
+
|
|
175
|
+
return widget.variant == KasyTabsVariant.primary
|
|
176
|
+
? _buildPrimary(context)
|
|
177
|
+
: _buildSecondary(context);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ── Primary (pill indicator) ───────────────────────────────────────────────
|
|
181
|
+
|
|
182
|
+
Widget _buildPrimary(BuildContext context) {
|
|
183
|
+
final KasyColors c = context.colors;
|
|
184
|
+
final bool isDark = context.isDark;
|
|
185
|
+
|
|
186
|
+
return DecoratedBox(
|
|
187
|
+
decoration: BoxDecoration(
|
|
188
|
+
color: c.avatarFallbackFill,
|
|
189
|
+
borderRadius: BorderRadius.circular(KasyRadius.full),
|
|
190
|
+
),
|
|
191
|
+
child: Padding(
|
|
192
|
+
padding: const EdgeInsets.all(4),
|
|
193
|
+
child: Stack(
|
|
194
|
+
children: [
|
|
195
|
+
// Animated pill background.
|
|
196
|
+
if (_measured)
|
|
197
|
+
AnimatedPositioned(
|
|
198
|
+
duration: const Duration(milliseconds: 250),
|
|
199
|
+
curve: Curves.easeInOut,
|
|
200
|
+
left: _indicatorLeft,
|
|
201
|
+
top: 0,
|
|
202
|
+
bottom: 0,
|
|
203
|
+
width: _indicatorWidth,
|
|
204
|
+
child: DecoratedBox(
|
|
205
|
+
decoration: BoxDecoration(
|
|
206
|
+
color: c.surface,
|
|
207
|
+
borderRadius: BorderRadius.circular(KasyRadius.full),
|
|
208
|
+
boxShadow: [
|
|
209
|
+
BoxShadow(
|
|
210
|
+
color: Colors.black.withValues(
|
|
211
|
+
alpha: isDark ? 0.18 : 0.08,
|
|
212
|
+
),
|
|
213
|
+
blurRadius: 8,
|
|
214
|
+
offset: const Offset(0, 2),
|
|
215
|
+
),
|
|
216
|
+
BoxShadow(
|
|
217
|
+
color: Colors.black.withValues(
|
|
218
|
+
alpha: isDark ? 0.10 : 0.04,
|
|
219
|
+
),
|
|
220
|
+
spreadRadius: 1,
|
|
221
|
+
),
|
|
222
|
+
],
|
|
223
|
+
),
|
|
224
|
+
),
|
|
225
|
+
),
|
|
226
|
+
// Tab labels (on top of the pill).
|
|
227
|
+
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
|
+
}),
|
|
242
|
+
),
|
|
243
|
+
],
|
|
244
|
+
),
|
|
245
|
+
),
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// ── Secondary (underline indicator) ───────────────────────────────────────
|
|
250
|
+
|
|
251
|
+
Widget _buildSecondary(BuildContext context) {
|
|
252
|
+
final KasyColors c = context.colors;
|
|
253
|
+
|
|
254
|
+
return Stack(
|
|
255
|
+
clipBehavior: Clip.none,
|
|
256
|
+
children: [
|
|
257
|
+
// Full-width bottom divider.
|
|
258
|
+
Positioned(
|
|
259
|
+
left: 0,
|
|
260
|
+
right: 0,
|
|
261
|
+
bottom: 0,
|
|
262
|
+
child: DecoratedBox(
|
|
263
|
+
decoration: BoxDecoration(
|
|
264
|
+
color: c.outline.withValues(alpha: 0.2),
|
|
265
|
+
),
|
|
266
|
+
child: const SizedBox(height: 1),
|
|
267
|
+
),
|
|
268
|
+
),
|
|
269
|
+
// Animated underline indicator.
|
|
270
|
+
if (_measured)
|
|
271
|
+
AnimatedPositioned(
|
|
272
|
+
duration: const Duration(milliseconds: 250),
|
|
273
|
+
curve: Curves.easeInOut,
|
|
274
|
+
left: _indicatorLeft,
|
|
275
|
+
bottom: 0,
|
|
276
|
+
width: _indicatorWidth,
|
|
277
|
+
height: 2,
|
|
278
|
+
child: DecoratedBox(
|
|
279
|
+
decoration: BoxDecoration(
|
|
280
|
+
color: c.primary,
|
|
281
|
+
borderRadius: BorderRadius.circular(1),
|
|
282
|
+
),
|
|
283
|
+
),
|
|
284
|
+
),
|
|
285
|
+
// Tab labels.
|
|
286
|
+
Row(
|
|
287
|
+
mainAxisSize: widget.mode == KasyTabsMode.fill
|
|
288
|
+
? MainAxisSize.max
|
|
289
|
+
: MainAxisSize.min,
|
|
290
|
+
children: List.generate(widget.items.length, (i) {
|
|
291
|
+
final KasyTabItem item = widget.items[i];
|
|
292
|
+
final bool selected = i == widget.selectedIndex;
|
|
293
|
+
return _SecondaryTab(
|
|
294
|
+
key: _keys[i],
|
|
295
|
+
item: item,
|
|
296
|
+
selected: selected,
|
|
297
|
+
expand: widget.mode == KasyTabsMode.fill,
|
|
298
|
+
onTap: item.enabled ? () => widget.onTabSelected(i) : null,
|
|
299
|
+
);
|
|
300
|
+
}),
|
|
301
|
+
),
|
|
302
|
+
],
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
308
|
+
// Internal tab widgets
|
|
309
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
310
|
+
|
|
311
|
+
class _PrimaryTab extends StatelessWidget {
|
|
312
|
+
const _PrimaryTab({
|
|
313
|
+
super.key,
|
|
314
|
+
required this.item,
|
|
315
|
+
required this.selected,
|
|
316
|
+
required this.expand,
|
|
317
|
+
required this.onTap,
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
final KasyTabItem item;
|
|
321
|
+
final bool selected;
|
|
322
|
+
final bool expand;
|
|
323
|
+
final VoidCallback? onTap;
|
|
324
|
+
|
|
325
|
+
Widget _tabContent(BuildContext context) {
|
|
326
|
+
final KasyColors c = context.colors;
|
|
327
|
+
final bool disabled = !item.enabled;
|
|
328
|
+
|
|
329
|
+
return GestureDetector(
|
|
330
|
+
onTap: onTap,
|
|
331
|
+
behavior: HitTestBehavior.opaque,
|
|
332
|
+
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
|
+
),
|
|
357
|
+
),
|
|
358
|
+
),
|
|
359
|
+
],
|
|
360
|
+
),
|
|
361
|
+
),
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
@override
|
|
366
|
+
Widget build(BuildContext context) {
|
|
367
|
+
final Widget inner = _tabContent(context);
|
|
368
|
+
return expand ? Expanded(child: inner) : inner;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
class _SecondaryTab extends StatelessWidget {
|
|
373
|
+
const _SecondaryTab({
|
|
374
|
+
super.key,
|
|
375
|
+
required this.item,
|
|
376
|
+
required this.selected,
|
|
377
|
+
required this.expand,
|
|
378
|
+
required this.onTap,
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
final KasyTabItem item;
|
|
382
|
+
final bool selected;
|
|
383
|
+
final bool expand;
|
|
384
|
+
final VoidCallback? onTap;
|
|
385
|
+
|
|
386
|
+
Widget _tabContent(BuildContext context) {
|
|
387
|
+
final KasyColors c = context.colors;
|
|
388
|
+
final bool disabled = !item.enabled;
|
|
389
|
+
|
|
390
|
+
return GestureDetector(
|
|
391
|
+
onTap: onTap,
|
|
392
|
+
behavior: HitTestBehavior.opaque,
|
|
393
|
+
child: Padding(
|
|
394
|
+
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
|
|
395
|
+
child: Row(
|
|
396
|
+
mainAxisSize: MainAxisSize.min,
|
|
397
|
+
mainAxisAlignment: MainAxisAlignment.center,
|
|
398
|
+
children: [
|
|
399
|
+
if (item.icon != null) ...[
|
|
400
|
+
Opacity(
|
|
401
|
+
opacity: disabled ? 0.4 : 1.0,
|
|
402
|
+
child: Icon(
|
|
403
|
+
item.icon,
|
|
404
|
+
size: 16,
|
|
405
|
+
color: selected ? c.onSurface : c.muted,
|
|
406
|
+
),
|
|
407
|
+
),
|
|
408
|
+
const SizedBox(width: 6),
|
|
409
|
+
],
|
|
410
|
+
Opacity(
|
|
411
|
+
opacity: disabled ? 0.4 : 1.0,
|
|
412
|
+
child: Text(
|
|
413
|
+
item.label,
|
|
414
|
+
style: context.textTheme.labelLarge?.copyWith(
|
|
415
|
+
color: selected ? c.onSurface : c.muted,
|
|
416
|
+
fontWeight: selected ? FontWeight.w600 : FontWeight.w400,
|
|
417
|
+
),
|
|
418
|
+
),
|
|
419
|
+
),
|
|
420
|
+
],
|
|
421
|
+
),
|
|
422
|
+
),
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
@override
|
|
427
|
+
Widget build(BuildContext context) {
|
|
428
|
+
final Widget inner = _tabContent(context);
|
|
429
|
+
return expand ? Expanded(child: inner) : inner;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
@@ -152,13 +152,19 @@ class MyWidgetHomeWidget extends _$MyWidgetHomeWidget
|
|
|
152
152
|
await HomeWidget.saveWidgetData<String>('isPro', data['isPro'] ?? 'false');
|
|
153
153
|
await HomeWidget.saveWidgetData<String>('quote', data['quote'] ?? '');
|
|
154
154
|
|
|
155
|
-
// On Android, saveWidgetData writes
|
|
156
|
-
// Glance's HomeWidgetGlanceStateDefinition reads from
|
|
157
|
-
// a tight saveWidgetData→updateWidget sequence can
|
|
158
|
-
// Glance
|
|
159
|
-
// right after a locale change).
|
|
155
|
+
// On Android, saveWidgetData writes via SharedPreferences.apply() which
|
|
156
|
+
// is asynchronous. Glance's HomeWidgetGlanceStateDefinition reads from
|
|
157
|
+
// the same prefs, but a tight saveWidgetData→updateWidget sequence can
|
|
158
|
+
// race the apply() flush — Glance recomposes with the previous values
|
|
159
|
+
// (most visible right after a locale change). Fire two updates spaced
|
|
160
|
+
// out by 400ms each so even slow devices catch the new state.
|
|
160
161
|
if (!kIsWeb && Platform.isAndroid) {
|
|
161
|
-
await Future<void>.delayed(const Duration(milliseconds:
|
|
162
|
+
await Future<void>.delayed(const Duration(milliseconds: 400));
|
|
163
|
+
await HomeWidget.updateWidget(
|
|
164
|
+
name: _androidWidgetName,
|
|
165
|
+
iOSName: _iosWidgetName,
|
|
166
|
+
);
|
|
167
|
+
await Future<void>.delayed(const Duration(milliseconds: 400));
|
|
162
168
|
}
|
|
163
169
|
|
|
164
170
|
await HomeWidget.updateWidget(
|
|
@@ -184,6 +184,7 @@ const Set<String> _kReadyComponents = <String>{
|
|
|
184
184
|
'Design System',
|
|
185
185
|
'Dialog',
|
|
186
186
|
'Hover',
|
|
187
|
+
'Tabs',
|
|
187
188
|
'TextArea',
|
|
188
189
|
'TextField',
|
|
189
190
|
'Sidebar',
|
|
@@ -218,7 +219,6 @@ const Set<String> _kWebReadyComponents = <String>{
|
|
|
218
219
|
|
|
219
220
|
const Set<String> _kUrgentComponents = <String>{
|
|
220
221
|
'Switch',
|
|
221
|
-
'Tabs',
|
|
222
222
|
'Radio Group',
|
|
223
223
|
'DatePicker',
|
|
224
224
|
};
|