neoagent 2.3.1-beta.90 → 2.3.1-beta.91
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/.env.example +4 -0
- package/flutter_app/lib/main_app_shell.dart +6 -4
- package/flutter_app/lib/main_controller.dart +5 -0
- package/flutter_app/lib/main_devices.dart +80 -745
- package/flutter_app/lib/main_settings.dart +50 -0
- package/package.json +1 -1
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +47867 -47755
- package/server/services/ai/capabilityHealth.js +2 -3
- package/server/services/android/controller.js +456 -2851
- package/server/services/runtime/backends/local-vm.js +2 -136
- package/server/services/runtime/docker-vm-manager.js +392 -0
- package/server/services/runtime/manager.js +53 -38
- package/server/services/runtime/settings.js +12 -10
- package/server/services/runtime/validation.js +4 -1
- package/server/utils/deployment.js +8 -2
- package/server/services/runtime/qemu.js +0 -1118
|
@@ -28,6 +28,7 @@ class _DevicesPanelState extends State<DevicesPanel> {
|
|
|
28
28
|
late final TextEditingController _textEntryController;
|
|
29
29
|
Timer? _surfaceFrameTimer;
|
|
30
30
|
_DeviceSurface _surface = _DeviceSurface.browser;
|
|
31
|
+
_DeviceSurface? _runningSurface;
|
|
31
32
|
|
|
32
33
|
@override
|
|
33
34
|
void initState() {
|
|
@@ -66,6 +67,20 @@ class _DevicesPanelState extends State<DevicesPanel> {
|
|
|
66
67
|
bool get _isBrowser => _surface == _DeviceSurface.browser;
|
|
67
68
|
bool get _isDesktop => _surface == _DeviceSurface.desktop;
|
|
68
69
|
|
|
70
|
+
bool get _isCurrentSurfaceBusy =>
|
|
71
|
+
widget.controller.isRunningDeviceAction &&
|
|
72
|
+
(_runningSurface == null || _runningSurface == _surface);
|
|
73
|
+
|
|
74
|
+
Future<T> _runOnSurface<T>(Future<T> Function() action) async {
|
|
75
|
+
final surface = _surface;
|
|
76
|
+
if (mounted) setState(() => _runningSurface = surface);
|
|
77
|
+
try {
|
|
78
|
+
return await action();
|
|
79
|
+
} finally {
|
|
80
|
+
if (mounted) setState(() => _runningSurface = null);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
69
84
|
bool get _androidOnline {
|
|
70
85
|
final status = widget.controller.androidRuntime;
|
|
71
86
|
final devices = _jsonMapList(status['devices'], fallbackToMapValues: true);
|
|
@@ -164,18 +179,14 @@ class _DevicesPanelState extends State<DevicesPanel> {
|
|
|
164
179
|
await widget.controller.refreshAndroidFrameRuntime();
|
|
165
180
|
}
|
|
166
181
|
|
|
167
|
-
Future<void>
|
|
168
|
-
|
|
169
|
-
final currentIndex = surfaces.indexOf(_surface);
|
|
170
|
-
final nextIndex = (currentIndex + delta) % surfaces.length;
|
|
171
|
-
setState(
|
|
172
|
-
() =>
|
|
173
|
-
_surface = surfaces[nextIndex < 0 ? surfaces.length - 1 : nextIndex],
|
|
174
|
-
);
|
|
182
|
+
Future<void> _selectSurface(_DeviceSurface surface) async {
|
|
183
|
+
setState(() => _surface = surface);
|
|
175
184
|
await _ensurePreview();
|
|
176
185
|
}
|
|
177
186
|
|
|
178
|
-
Future<void> _openPrimary()
|
|
187
|
+
Future<void> _openPrimary() => _runOnSurface(_openPrimaryInner);
|
|
188
|
+
|
|
189
|
+
Future<void> _openPrimaryInner() async {
|
|
179
190
|
final controller = widget.controller;
|
|
180
191
|
if (_isBrowser) {
|
|
181
192
|
await controller.navigateBrowserRuntime(
|
|
@@ -237,7 +248,9 @@ class _DevicesPanelState extends State<DevicesPanel> {
|
|
|
237
248
|
await controller.openAndroidAppRuntime(packageName: raw);
|
|
238
249
|
}
|
|
239
250
|
|
|
240
|
-
Future<void> _sleepPrimary()
|
|
251
|
+
Future<void> _sleepPrimary() => _runOnSurface(_sleepPrimaryInner);
|
|
252
|
+
|
|
253
|
+
Future<void> _sleepPrimaryInner() async {
|
|
241
254
|
final controller = widget.controller;
|
|
242
255
|
if (_isBrowser) {
|
|
243
256
|
if (controller.browserRuntime['launched'] != true) {
|
|
@@ -259,7 +272,7 @@ class _DevicesPanelState extends State<DevicesPanel> {
|
|
|
259
272
|
await controller.stopAndroidRuntime();
|
|
260
273
|
}
|
|
261
274
|
|
|
262
|
-
Future<void> _sendText() async {
|
|
275
|
+
Future<void> _sendText() => _runOnSurface(() async {
|
|
263
276
|
final text = _textEntryController.text;
|
|
264
277
|
if (text.trim().isEmpty) {
|
|
265
278
|
return;
|
|
@@ -274,9 +287,9 @@ class _DevicesPanelState extends State<DevicesPanel> {
|
|
|
274
287
|
'pressEnter': true,
|
|
275
288
|
});
|
|
276
289
|
}
|
|
277
|
-
}
|
|
290
|
+
});
|
|
278
291
|
|
|
279
|
-
Future<void> _handleTap(Offset point) async {
|
|
292
|
+
Future<void> _handleTap(Offset point) => _runOnSurface(() async {
|
|
280
293
|
if (_isBrowser) {
|
|
281
294
|
await widget.controller.clickBrowserPointRuntime(
|
|
282
295
|
x: point.dx.round(),
|
|
@@ -302,9 +315,9 @@ class _DevicesPanelState extends State<DevicesPanel> {
|
|
|
302
315
|
'x': point.dx.round(),
|
|
303
316
|
'y': point.dy.round(),
|
|
304
317
|
});
|
|
305
|
-
}
|
|
318
|
+
});
|
|
306
319
|
|
|
307
|
-
Future<void> _handleSwipe(Offset start, Offset end) async {
|
|
320
|
+
Future<void> _handleSwipe(Offset start, Offset end) => _runOnSurface(() async {
|
|
308
321
|
if (_isBrowser) {
|
|
309
322
|
await widget.controller.scrollBrowserRuntime(
|
|
310
323
|
deltaY: (start.dy - end.dy).round(),
|
|
@@ -333,9 +346,9 @@ class _DevicesPanelState extends State<DevicesPanel> {
|
|
|
333
346
|
'y2': end.dy.round(),
|
|
334
347
|
'durationMs': 280,
|
|
335
348
|
});
|
|
336
|
-
}
|
|
349
|
+
});
|
|
337
350
|
|
|
338
|
-
Future<void> _runQuickAction(String action) async {
|
|
351
|
+
Future<void> _runQuickAction(String action) => _runOnSurface(() async {
|
|
339
352
|
final controller = widget.controller;
|
|
340
353
|
switch (action) {
|
|
341
354
|
case 'browser_refresh':
|
|
@@ -369,7 +382,7 @@ class _DevicesPanelState extends State<DevicesPanel> {
|
|
|
369
382
|
await _ensurePreview();
|
|
370
383
|
break;
|
|
371
384
|
}
|
|
372
|
-
}
|
|
385
|
+
});
|
|
373
386
|
|
|
374
387
|
@override
|
|
375
388
|
Widget build(BuildContext context) {
|
|
@@ -471,7 +484,7 @@ class _DevicesPanelState extends State<DevicesPanel> {
|
|
|
471
484
|
),
|
|
472
485
|
);
|
|
473
486
|
}).toList(),
|
|
474
|
-
onChanged:
|
|
487
|
+
onChanged: _isCurrentSurfaceBusy
|
|
475
488
|
? null
|
|
476
489
|
: (value) {
|
|
477
490
|
if (value == null || value.isEmpty) {
|
|
@@ -533,7 +546,7 @@ class _DevicesPanelState extends State<DevicesPanel> {
|
|
|
533
546
|
? _desktopOnline
|
|
534
547
|
: _androidOnline || _androidStarting),
|
|
535
548
|
starting: !_isBrowser && !_isDesktop && _androidStarting,
|
|
536
|
-
busy:
|
|
549
|
+
busy: _isCurrentSurfaceBusy,
|
|
537
550
|
onSubmit: _openPrimary,
|
|
538
551
|
onSleep: _sleepPrimary,
|
|
539
552
|
),
|
|
@@ -542,7 +555,7 @@ class _DevicesPanelState extends State<DevicesPanel> {
|
|
|
542
555
|
surface: _surface,
|
|
543
556
|
controller: controller,
|
|
544
557
|
screenshotPath: _activeScreenshotPath,
|
|
545
|
-
busy:
|
|
558
|
+
busy: _isCurrentSurfaceBusy,
|
|
546
559
|
wakingUp: !_isBrowser && !_isDesktop && _androidStarting,
|
|
547
560
|
enabled: _isBrowser || _isDesktop || _androidOnline,
|
|
548
561
|
connectRequired: _desktopRequiresSelection,
|
|
@@ -553,7 +566,7 @@ class _DevicesPanelState extends State<DevicesPanel> {
|
|
|
553
566
|
if (!_isBrowser && !_isDesktop) ...<Widget>[
|
|
554
567
|
const SizedBox(height: 12),
|
|
555
568
|
_AndroidNavDock(
|
|
556
|
-
busy:
|
|
569
|
+
busy: _isCurrentSurfaceBusy,
|
|
557
570
|
androidOnline: _androidOnline,
|
|
558
571
|
onAction: _runQuickAction,
|
|
559
572
|
),
|
|
@@ -561,7 +574,7 @@ class _DevicesPanelState extends State<DevicesPanel> {
|
|
|
561
574
|
const SizedBox(height: 14),
|
|
562
575
|
AndroidApkDropZone(
|
|
563
576
|
enabled: _androidOnline,
|
|
564
|
-
busy:
|
|
577
|
+
busy: _isCurrentSurfaceBusy,
|
|
565
578
|
onInstall: ({required filename, required bytes}) {
|
|
566
579
|
return controller.installAndroidApkRuntime(
|
|
567
580
|
filename: filename,
|
|
@@ -574,7 +587,7 @@ class _DevicesPanelState extends State<DevicesPanel> {
|
|
|
574
587
|
const SizedBox(height: 18),
|
|
575
588
|
_DeviceTypeDock(
|
|
576
589
|
controller: _textEntryController,
|
|
577
|
-
busy:
|
|
590
|
+
busy: _isCurrentSurfaceBusy,
|
|
578
591
|
surface: _surface,
|
|
579
592
|
onSubmit: _sendText,
|
|
580
593
|
),
|
|
@@ -583,15 +596,14 @@ class _DevicesPanelState extends State<DevicesPanel> {
|
|
|
583
596
|
_DeviceQuickActions(
|
|
584
597
|
surface: _surface,
|
|
585
598
|
androidOnline: _androidOnline,
|
|
586
|
-
busy:
|
|
599
|
+
busy: _isCurrentSurfaceBusy,
|
|
587
600
|
onAction: _runQuickAction,
|
|
588
601
|
),
|
|
589
602
|
],
|
|
590
603
|
const SizedBox(height: 14),
|
|
591
604
|
_SurfaceSwitcher(
|
|
592
605
|
surface: _surface,
|
|
593
|
-
|
|
594
|
-
onNext: () => _switchSurface(1),
|
|
606
|
+
onSelect: _selectSurface,
|
|
595
607
|
),
|
|
596
608
|
],
|
|
597
609
|
),
|
|
@@ -1093,83 +1105,54 @@ class _AndroidNavDock extends StatelessWidget {
|
|
|
1093
1105
|
class _SurfaceSwitcher extends StatelessWidget {
|
|
1094
1106
|
const _SurfaceSwitcher({
|
|
1095
1107
|
required this.surface,
|
|
1096
|
-
required this.
|
|
1097
|
-
required this.onNext,
|
|
1108
|
+
required this.onSelect,
|
|
1098
1109
|
});
|
|
1099
1110
|
|
|
1100
1111
|
final _DeviceSurface surface;
|
|
1101
|
-
final
|
|
1102
|
-
final VoidCallback onNext;
|
|
1112
|
+
final Future<void> Function(_DeviceSurface) onSelect;
|
|
1103
1113
|
|
|
1104
1114
|
@override
|
|
1105
1115
|
Widget build(BuildContext context) {
|
|
1106
|
-
return
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
textAlign: TextAlign.center,
|
|
1121
|
-
maxLines: compact ? 3 : 2,
|
|
1122
|
-
overflow: TextOverflow.ellipsis,
|
|
1123
|
-
style: TextStyle(color: _textSecondary),
|
|
1124
|
-
),
|
|
1125
|
-
],
|
|
1126
|
-
);
|
|
1127
|
-
|
|
1128
|
-
if (compact) {
|
|
1129
|
-
return Column(
|
|
1130
|
-
mainAxisSize: MainAxisSize.min,
|
|
1131
|
-
children: <Widget>[
|
|
1132
|
-
Row(
|
|
1133
|
-
mainAxisAlignment: MainAxisAlignment.center,
|
|
1134
|
-
children: <Widget>[
|
|
1135
|
-
IconButton.filledTonal(
|
|
1136
|
-
tooltip: 'Previous surface',
|
|
1137
|
-
onPressed: onPrevious,
|
|
1138
|
-
icon: Icon(Icons.arrow_back_ios_new_rounded),
|
|
1139
|
-
),
|
|
1140
|
-
const SizedBox(width: 14),
|
|
1141
|
-
Flexible(child: labelColumn),
|
|
1142
|
-
const SizedBox(width: 14),
|
|
1143
|
-
IconButton.filledTonal(
|
|
1144
|
-
tooltip: 'Next surface',
|
|
1145
|
-
onPressed: onNext,
|
|
1146
|
-
icon: Icon(Icons.arrow_forward_ios_rounded),
|
|
1147
|
-
),
|
|
1148
|
-
],
|
|
1116
|
+
return Column(
|
|
1117
|
+
mainAxisSize: MainAxisSize.min,
|
|
1118
|
+
children: <Widget>[
|
|
1119
|
+
Wrap(
|
|
1120
|
+
spacing: 8,
|
|
1121
|
+
runSpacing: 8,
|
|
1122
|
+
alignment: WrapAlignment.center,
|
|
1123
|
+
children: _DeviceSurface.values.map((s) {
|
|
1124
|
+
final selected = s == surface;
|
|
1125
|
+
return ChoiceChip(
|
|
1126
|
+
avatar: Icon(
|
|
1127
|
+
s.icon,
|
|
1128
|
+
size: 16,
|
|
1129
|
+
color: selected ? _textPrimary : _textSecondary,
|
|
1149
1130
|
),
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1131
|
+
label: Text(s.label),
|
|
1132
|
+
selected: selected,
|
|
1133
|
+
onSelected: (_) => onSelect(s),
|
|
1134
|
+
selectedColor: _accentMuted,
|
|
1135
|
+
backgroundColor: _bgCard,
|
|
1136
|
+
side: BorderSide(
|
|
1137
|
+
color: selected
|
|
1138
|
+
? _accent.withValues(alpha: 0.42)
|
|
1139
|
+
: _borderLight,
|
|
1140
|
+
),
|
|
1141
|
+
labelStyle: TextStyle(
|
|
1142
|
+
color: selected ? _textPrimary : _textSecondary,
|
|
1143
|
+
fontWeight:
|
|
1144
|
+
selected ? FontWeight.w700 : FontWeight.w500,
|
|
1145
|
+
),
|
|
1146
|
+
);
|
|
1147
|
+
}).toList(),
|
|
1148
|
+
),
|
|
1149
|
+
const SizedBox(height: 8),
|
|
1150
|
+
Text(
|
|
1151
|
+
surface.helper,
|
|
1152
|
+
textAlign: TextAlign.center,
|
|
1153
|
+
style: TextStyle(color: _textSecondary),
|
|
1154
|
+
),
|
|
1155
|
+
],
|
|
1173
1156
|
);
|
|
1174
1157
|
}
|
|
1175
1158
|
}
|
|
@@ -1592,656 +1575,8 @@ class _EmptySurfaceState extends StatelessWidget {
|
|
|
1592
1575
|
}
|
|
1593
1576
|
}
|
|
1594
1577
|
|
|
1595
|
-
// ignore: unused_element
|
|
1596
|
-
class _RuntimeControlCard extends StatelessWidget {
|
|
1597
|
-
const _RuntimeControlCard({
|
|
1598
|
-
required this.title,
|
|
1599
|
-
required this.subtitle,
|
|
1600
|
-
required this.status,
|
|
1601
|
-
required this.child,
|
|
1602
|
-
});
|
|
1603
|
-
|
|
1604
|
-
final String title;
|
|
1605
|
-
final String subtitle;
|
|
1606
|
-
final Widget status;
|
|
1607
|
-
final Widget child;
|
|
1608
|
-
|
|
1609
|
-
@override
|
|
1610
|
-
Widget build(BuildContext context) {
|
|
1611
|
-
return Card(
|
|
1612
|
-
child: Padding(
|
|
1613
|
-
padding: const EdgeInsets.all(20),
|
|
1614
|
-
child: Column(
|
|
1615
|
-
crossAxisAlignment: CrossAxisAlignment.start,
|
|
1616
|
-
children: <Widget>[
|
|
1617
|
-
Row(
|
|
1618
|
-
crossAxisAlignment: CrossAxisAlignment.start,
|
|
1619
|
-
children: <Widget>[
|
|
1620
|
-
Expanded(
|
|
1621
|
-
child: Column(
|
|
1622
|
-
crossAxisAlignment: CrossAxisAlignment.start,
|
|
1623
|
-
children: <Widget>[
|
|
1624
|
-
Text(
|
|
1625
|
-
title,
|
|
1626
|
-
style: TextStyle(
|
|
1627
|
-
fontSize: 20,
|
|
1628
|
-
fontWeight: FontWeight.w800,
|
|
1629
|
-
),
|
|
1630
|
-
),
|
|
1631
|
-
const SizedBox(height: 6),
|
|
1632
|
-
Text(
|
|
1633
|
-
subtitle,
|
|
1634
|
-
style: TextStyle(color: _textSecondary, height: 1.5),
|
|
1635
|
-
),
|
|
1636
|
-
],
|
|
1637
|
-
),
|
|
1638
|
-
),
|
|
1639
|
-
const SizedBox(width: 12),
|
|
1640
|
-
status,
|
|
1641
|
-
],
|
|
1642
|
-
),
|
|
1643
|
-
const SizedBox(height: 18),
|
|
1644
|
-
child,
|
|
1645
|
-
],
|
|
1646
|
-
),
|
|
1647
|
-
),
|
|
1648
|
-
);
|
|
1649
|
-
}
|
|
1650
|
-
}
|
|
1651
|
-
|
|
1652
|
-
// ignore: unused_element
|
|
1653
|
-
class _BrowserControls extends StatelessWidget {
|
|
1654
|
-
const _BrowserControls({
|
|
1655
|
-
required this.controller,
|
|
1656
|
-
required this.browserStatus,
|
|
1657
|
-
required this.browserPageInfo,
|
|
1658
|
-
required this.urlController,
|
|
1659
|
-
required this.waitForController,
|
|
1660
|
-
required this.clickSelectorController,
|
|
1661
|
-
required this.clickTextController,
|
|
1662
|
-
required this.fillSelectorController,
|
|
1663
|
-
required this.fillValueController,
|
|
1664
|
-
});
|
|
1665
1578
|
|
|
1666
|
-
final NeoAgentController controller;
|
|
1667
|
-
final Map<String, dynamic> browserStatus;
|
|
1668
|
-
final Map<String, dynamic> browserPageInfo;
|
|
1669
|
-
final TextEditingController urlController;
|
|
1670
|
-
final TextEditingController waitForController;
|
|
1671
|
-
final TextEditingController clickSelectorController;
|
|
1672
|
-
final TextEditingController clickTextController;
|
|
1673
|
-
final TextEditingController fillSelectorController;
|
|
1674
|
-
final TextEditingController fillValueController;
|
|
1675
1579
|
|
|
1676
|
-
@override
|
|
1677
|
-
Widget build(BuildContext context) {
|
|
1678
|
-
final launched = browserStatus['launched'] == true;
|
|
1679
|
-
return Column(
|
|
1680
|
-
crossAxisAlignment: CrossAxisAlignment.start,
|
|
1681
|
-
children: <Widget>[
|
|
1682
|
-
Wrap(
|
|
1683
|
-
spacing: 10,
|
|
1684
|
-
runSpacing: 10,
|
|
1685
|
-
children: <Widget>[
|
|
1686
|
-
_MetaPill(
|
|
1687
|
-
label: launched ? 'Launched' : 'Idle',
|
|
1688
|
-
icon: Icons.language_outlined,
|
|
1689
|
-
),
|
|
1690
|
-
_MetaPill(
|
|
1691
|
-
label: 'Pages ${browserStatus['pages'] ?? 0}',
|
|
1692
|
-
icon: Icons.filter_none_outlined,
|
|
1693
|
-
),
|
|
1694
|
-
_MetaPill(
|
|
1695
|
-
label: browserStatus['headless'] == false
|
|
1696
|
-
? 'Visible window'
|
|
1697
|
-
: 'Headless',
|
|
1698
|
-
icon: Icons.visibility_outlined,
|
|
1699
|
-
),
|
|
1700
|
-
],
|
|
1701
|
-
),
|
|
1702
|
-
if ((browserPageInfo['url']?.toString().isNotEmpty ?? false) ||
|
|
1703
|
-
(browserPageInfo['title']?.toString().isNotEmpty ??
|
|
1704
|
-
false)) ...<Widget>[
|
|
1705
|
-
const SizedBox(height: 14),
|
|
1706
|
-
SelectableText(
|
|
1707
|
-
'${browserPageInfo['title'] ?? 'Untitled'}\n${browserPageInfo['url'] ?? ''}',
|
|
1708
|
-
style: TextStyle(color: _textSecondary, height: 1.5),
|
|
1709
|
-
),
|
|
1710
|
-
],
|
|
1711
|
-
const SizedBox(height: 18),
|
|
1712
|
-
_DeviceFieldRow(
|
|
1713
|
-
children: <Widget>[
|
|
1714
|
-
_DeviceField(
|
|
1715
|
-
label: 'URL',
|
|
1716
|
-
child: TextField(controller: urlController),
|
|
1717
|
-
),
|
|
1718
|
-
_DeviceField(
|
|
1719
|
-
label: 'Wait For Selector',
|
|
1720
|
-
child: TextField(controller: waitForController),
|
|
1721
|
-
),
|
|
1722
|
-
],
|
|
1723
|
-
),
|
|
1724
|
-
const SizedBox(height: 10),
|
|
1725
|
-
Wrap(
|
|
1726
|
-
spacing: 10,
|
|
1727
|
-
runSpacing: 10,
|
|
1728
|
-
children: <Widget>[
|
|
1729
|
-
FilledButton.icon(
|
|
1730
|
-
onPressed: controller.isRunningDeviceAction
|
|
1731
|
-
? null
|
|
1732
|
-
: controller.launchBrowserRuntime,
|
|
1733
|
-
icon: Icon(Icons.rocket_launch_outlined),
|
|
1734
|
-
label: Text('Launch'),
|
|
1735
|
-
),
|
|
1736
|
-
FilledButton.icon(
|
|
1737
|
-
onPressed: controller.isRunningDeviceAction
|
|
1738
|
-
? null
|
|
1739
|
-
: () => controller.navigateBrowserRuntime(
|
|
1740
|
-
url: urlController.text.trim(),
|
|
1741
|
-
waitFor: waitForController.text.trim(),
|
|
1742
|
-
),
|
|
1743
|
-
icon: Icon(Icons.open_in_browser_outlined),
|
|
1744
|
-
label: Text('Navigate'),
|
|
1745
|
-
),
|
|
1746
|
-
OutlinedButton.icon(
|
|
1747
|
-
onPressed: controller.isRunningDeviceAction
|
|
1748
|
-
? null
|
|
1749
|
-
: controller.screenshotBrowserRuntime,
|
|
1750
|
-
icon: Icon(Icons.photo_camera_back_outlined),
|
|
1751
|
-
label: Text('Screenshot'),
|
|
1752
|
-
),
|
|
1753
|
-
OutlinedButton.icon(
|
|
1754
|
-
onPressed: controller.isRunningDeviceAction
|
|
1755
|
-
? null
|
|
1756
|
-
: controller.closeBrowserRuntime,
|
|
1757
|
-
icon: Icon(Icons.close),
|
|
1758
|
-
label: Text('Close'),
|
|
1759
|
-
),
|
|
1760
|
-
],
|
|
1761
|
-
),
|
|
1762
|
-
const SizedBox(height: 18),
|
|
1763
|
-
_DeviceFieldRow(
|
|
1764
|
-
children: <Widget>[
|
|
1765
|
-
_DeviceField(
|
|
1766
|
-
label: 'Click Selector',
|
|
1767
|
-
child: TextField(controller: clickSelectorController),
|
|
1768
|
-
),
|
|
1769
|
-
_DeviceField(
|
|
1770
|
-
label: 'Click Text',
|
|
1771
|
-
child: TextField(controller: clickTextController),
|
|
1772
|
-
),
|
|
1773
|
-
],
|
|
1774
|
-
),
|
|
1775
|
-
const SizedBox(height: 10),
|
|
1776
|
-
Wrap(
|
|
1777
|
-
spacing: 10,
|
|
1778
|
-
runSpacing: 10,
|
|
1779
|
-
children: <Widget>[
|
|
1780
|
-
OutlinedButton(
|
|
1781
|
-
onPressed: controller.isRunningDeviceAction
|
|
1782
|
-
? null
|
|
1783
|
-
: () => controller.clickBrowserRuntime(
|
|
1784
|
-
selector: clickSelectorController.text.trim(),
|
|
1785
|
-
),
|
|
1786
|
-
child: Text('Click Selector'),
|
|
1787
|
-
),
|
|
1788
|
-
OutlinedButton(
|
|
1789
|
-
onPressed: controller.isRunningDeviceAction
|
|
1790
|
-
? null
|
|
1791
|
-
: () => controller.clickBrowserRuntime(
|
|
1792
|
-
text: clickTextController.text.trim(),
|
|
1793
|
-
),
|
|
1794
|
-
child: Text('Click Text'),
|
|
1795
|
-
),
|
|
1796
|
-
],
|
|
1797
|
-
),
|
|
1798
|
-
const SizedBox(height: 18),
|
|
1799
|
-
_DeviceFieldRow(
|
|
1800
|
-
children: <Widget>[
|
|
1801
|
-
_DeviceField(
|
|
1802
|
-
label: 'Type Selector',
|
|
1803
|
-
child: TextField(controller: fillSelectorController),
|
|
1804
|
-
),
|
|
1805
|
-
_DeviceField(
|
|
1806
|
-
label: 'Value',
|
|
1807
|
-
child: TextField(controller: fillValueController),
|
|
1808
|
-
),
|
|
1809
|
-
],
|
|
1810
|
-
),
|
|
1811
|
-
const SizedBox(height: 10),
|
|
1812
|
-
OutlinedButton.icon(
|
|
1813
|
-
onPressed: controller.isRunningDeviceAction
|
|
1814
|
-
? null
|
|
1815
|
-
: () => controller.fillBrowserRuntime(
|
|
1816
|
-
selector: fillSelectorController.text.trim(),
|
|
1817
|
-
value: fillValueController.text,
|
|
1818
|
-
),
|
|
1819
|
-
icon: Icon(Icons.keyboard_outlined),
|
|
1820
|
-
label: Text('Type Into Field'),
|
|
1821
|
-
),
|
|
1822
|
-
const SizedBox(height: 18),
|
|
1823
|
-
_RuntimePreview(
|
|
1824
|
-
title: 'Latest Browser Screenshot',
|
|
1825
|
-
screenshotPath: controller.browserScreenshotPath,
|
|
1826
|
-
controller: controller,
|
|
1827
|
-
),
|
|
1828
|
-
if (controller.browserLastResult?.trim().isNotEmpty ??
|
|
1829
|
-
false) ...<Widget>[
|
|
1830
|
-
const SizedBox(height: 14),
|
|
1831
|
-
_ResultBlock(
|
|
1832
|
-
label: 'Last browser result',
|
|
1833
|
-
value: controller.browserLastResult!,
|
|
1834
|
-
),
|
|
1835
|
-
],
|
|
1836
|
-
],
|
|
1837
|
-
);
|
|
1838
|
-
}
|
|
1839
|
-
}
|
|
1840
|
-
|
|
1841
|
-
// ignore: unused_element
|
|
1842
|
-
class _AndroidControls extends StatelessWidget {
|
|
1843
|
-
const _AndroidControls({
|
|
1844
|
-
required this.controller,
|
|
1845
|
-
required this.androidStatus,
|
|
1846
|
-
required this.androidDevices,
|
|
1847
|
-
required this.packageController,
|
|
1848
|
-
required this.activityController,
|
|
1849
|
-
required this.intentActionController,
|
|
1850
|
-
required this.intentDataController,
|
|
1851
|
-
required this.tapTextController,
|
|
1852
|
-
required this.tapDescriptionController,
|
|
1853
|
-
required this.tapResourceIdController,
|
|
1854
|
-
required this.tapXController,
|
|
1855
|
-
required this.tapYController,
|
|
1856
|
-
required this.typeTextController,
|
|
1857
|
-
required this.typeFieldTextController,
|
|
1858
|
-
required this.typeFieldDescriptionController,
|
|
1859
|
-
required this.waitTextController,
|
|
1860
|
-
required this.keyController,
|
|
1861
|
-
required this.swipeX1Controller,
|
|
1862
|
-
required this.swipeY1Controller,
|
|
1863
|
-
required this.swipeX2Controller,
|
|
1864
|
-
required this.swipeY2Controller,
|
|
1865
|
-
required this.toInt,
|
|
1866
|
-
});
|
|
1867
|
-
|
|
1868
|
-
final NeoAgentController controller;
|
|
1869
|
-
final Map<String, dynamic> androidStatus;
|
|
1870
|
-
final List<Map<String, dynamic>> androidDevices;
|
|
1871
|
-
final TextEditingController packageController;
|
|
1872
|
-
final TextEditingController activityController;
|
|
1873
|
-
final TextEditingController intentActionController;
|
|
1874
|
-
final TextEditingController intentDataController;
|
|
1875
|
-
final TextEditingController tapTextController;
|
|
1876
|
-
final TextEditingController tapDescriptionController;
|
|
1877
|
-
final TextEditingController tapResourceIdController;
|
|
1878
|
-
final TextEditingController tapXController;
|
|
1879
|
-
final TextEditingController tapYController;
|
|
1880
|
-
final TextEditingController typeTextController;
|
|
1881
|
-
final TextEditingController typeFieldTextController;
|
|
1882
|
-
final TextEditingController typeFieldDescriptionController;
|
|
1883
|
-
final TextEditingController waitTextController;
|
|
1884
|
-
final TextEditingController keyController;
|
|
1885
|
-
final TextEditingController swipeX1Controller;
|
|
1886
|
-
final TextEditingController swipeY1Controller;
|
|
1887
|
-
final TextEditingController swipeX2Controller;
|
|
1888
|
-
final TextEditingController swipeY2Controller;
|
|
1889
|
-
final int? Function(String text) toInt;
|
|
1890
|
-
|
|
1891
|
-
@override
|
|
1892
|
-
Widget build(BuildContext context) {
|
|
1893
|
-
return Column(
|
|
1894
|
-
crossAxisAlignment: CrossAxisAlignment.start,
|
|
1895
|
-
children: <Widget>[
|
|
1896
|
-
Wrap(
|
|
1897
|
-
spacing: 10,
|
|
1898
|
-
runSpacing: 10,
|
|
1899
|
-
children: <Widget>[
|
|
1900
|
-
_MetaPill(
|
|
1901
|
-
label: androidStatus['bootstrapped'] == true
|
|
1902
|
-
? 'SDK Ready'
|
|
1903
|
-
: 'Bootstrap Needed',
|
|
1904
|
-
icon: Icons.adb_outlined,
|
|
1905
|
-
),
|
|
1906
|
-
_MetaPill(
|
|
1907
|
-
label: androidStatus['serial']?.toString().isNotEmpty == true
|
|
1908
|
-
? androidStatus['serial'].toString()
|
|
1909
|
-
: 'No active serial',
|
|
1910
|
-
icon: Icons.phone_android_outlined,
|
|
1911
|
-
),
|
|
1912
|
-
_MetaPill(
|
|
1913
|
-
label: '${androidDevices.length} device(s)',
|
|
1914
|
-
icon: Icons.devices_other_outlined,
|
|
1915
|
-
),
|
|
1916
|
-
],
|
|
1917
|
-
),
|
|
1918
|
-
const SizedBox(height: 18),
|
|
1919
|
-
Wrap(
|
|
1920
|
-
spacing: 10,
|
|
1921
|
-
runSpacing: 10,
|
|
1922
|
-
children: <Widget>[
|
|
1923
|
-
FilledButton.icon(
|
|
1924
|
-
onPressed: controller.isRunningDeviceAction
|
|
1925
|
-
? null
|
|
1926
|
-
: controller.startAndroidRuntime,
|
|
1927
|
-
icon: Icon(Icons.play_arrow_outlined),
|
|
1928
|
-
label: Text('Start Emulator'),
|
|
1929
|
-
),
|
|
1930
|
-
OutlinedButton.icon(
|
|
1931
|
-
onPressed: controller.isRunningDeviceAction
|
|
1932
|
-
? null
|
|
1933
|
-
: controller.stopAndroidRuntime,
|
|
1934
|
-
icon: Icon(Icons.stop_circle_outlined),
|
|
1935
|
-
label: Text('Stop'),
|
|
1936
|
-
),
|
|
1937
|
-
OutlinedButton.icon(
|
|
1938
|
-
onPressed: controller.isRunningDeviceAction
|
|
1939
|
-
? null
|
|
1940
|
-
: controller.screenshotAndroidRuntime,
|
|
1941
|
-
icon: Icon(Icons.photo_camera_outlined),
|
|
1942
|
-
label: Text('Screenshot'),
|
|
1943
|
-
),
|
|
1944
|
-
OutlinedButton.icon(
|
|
1945
|
-
onPressed: controller.isRunningDeviceAction
|
|
1946
|
-
? null
|
|
1947
|
-
: controller.dumpAndroidUiRuntime,
|
|
1948
|
-
icon: Icon(Icons.data_object_outlined),
|
|
1949
|
-
label: Text('Dump UI'),
|
|
1950
|
-
),
|
|
1951
|
-
OutlinedButton.icon(
|
|
1952
|
-
onPressed: controller.isRunningDeviceAction
|
|
1953
|
-
? null
|
|
1954
|
-
: controller.refreshAndroidApps,
|
|
1955
|
-
icon: Icon(Icons.apps_outlined),
|
|
1956
|
-
label: Text('Load Apps'),
|
|
1957
|
-
),
|
|
1958
|
-
],
|
|
1959
|
-
),
|
|
1960
|
-
const SizedBox(height: 18),
|
|
1961
|
-
_DeviceFieldRow(
|
|
1962
|
-
children: <Widget>[
|
|
1963
|
-
_DeviceField(
|
|
1964
|
-
label: 'Package',
|
|
1965
|
-
child: TextField(controller: packageController),
|
|
1966
|
-
),
|
|
1967
|
-
_DeviceField(
|
|
1968
|
-
label: 'Activity',
|
|
1969
|
-
child: TextField(controller: activityController),
|
|
1970
|
-
),
|
|
1971
|
-
],
|
|
1972
|
-
),
|
|
1973
|
-
const SizedBox(height: 10),
|
|
1974
|
-
Wrap(
|
|
1975
|
-
spacing: 10,
|
|
1976
|
-
runSpacing: 10,
|
|
1977
|
-
children: <Widget>[
|
|
1978
|
-
OutlinedButton.icon(
|
|
1979
|
-
onPressed: controller.isRunningDeviceAction
|
|
1980
|
-
? null
|
|
1981
|
-
: () => controller.openAndroidAppRuntime(
|
|
1982
|
-
packageName: packageController.text.trim(),
|
|
1983
|
-
activity: activityController.text.trim(),
|
|
1984
|
-
),
|
|
1985
|
-
icon: Icon(Icons.apps),
|
|
1986
|
-
label: Text('Open App'),
|
|
1987
|
-
),
|
|
1988
|
-
if (controller.androidInstalledApps.isNotEmpty)
|
|
1989
|
-
SizedBox(width: 1, height: 1, child: Container()),
|
|
1990
|
-
],
|
|
1991
|
-
),
|
|
1992
|
-
if (controller.androidInstalledApps.isNotEmpty) ...<Widget>[
|
|
1993
|
-
const SizedBox(height: 8),
|
|
1994
|
-
Wrap(
|
|
1995
|
-
spacing: 8,
|
|
1996
|
-
runSpacing: 8,
|
|
1997
|
-
children: controller.androidInstalledApps.take(10).map((appId) {
|
|
1998
|
-
return ActionChip(
|
|
1999
|
-
label: Text(appId),
|
|
2000
|
-
onPressed: () => packageController.text = appId,
|
|
2001
|
-
);
|
|
2002
|
-
}).toList(),
|
|
2003
|
-
),
|
|
2004
|
-
],
|
|
2005
|
-
const SizedBox(height: 18),
|
|
2006
|
-
_DeviceFieldRow(
|
|
2007
|
-
children: <Widget>[
|
|
2008
|
-
_DeviceField(
|
|
2009
|
-
label: 'Intent Action',
|
|
2010
|
-
child: TextField(controller: intentActionController),
|
|
2011
|
-
),
|
|
2012
|
-
_DeviceField(
|
|
2013
|
-
label: 'Intent Data',
|
|
2014
|
-
child: TextField(controller: intentDataController),
|
|
2015
|
-
),
|
|
2016
|
-
],
|
|
2017
|
-
),
|
|
2018
|
-
const SizedBox(height: 10),
|
|
2019
|
-
OutlinedButton.icon(
|
|
2020
|
-
onPressed: controller.isRunningDeviceAction
|
|
2021
|
-
? null
|
|
2022
|
-
: () => controller.openAndroidIntentRuntime(
|
|
2023
|
-
action: intentActionController.text.trim(),
|
|
2024
|
-
dataUri: intentDataController.text.trim(),
|
|
2025
|
-
packageName: packageController.text.trim(),
|
|
2026
|
-
),
|
|
2027
|
-
icon: Icon(Icons.route_outlined),
|
|
2028
|
-
label: Text('Open Intent'),
|
|
2029
|
-
),
|
|
2030
|
-
const SizedBox(height: 18),
|
|
2031
|
-
_DeviceFieldRow(
|
|
2032
|
-
children: <Widget>[
|
|
2033
|
-
_DeviceField(
|
|
2034
|
-
label: 'Wait For Text',
|
|
2035
|
-
child: TextField(controller: waitTextController),
|
|
2036
|
-
),
|
|
2037
|
-
_DeviceField(
|
|
2038
|
-
label: 'Key',
|
|
2039
|
-
child: TextField(controller: keyController),
|
|
2040
|
-
),
|
|
2041
|
-
],
|
|
2042
|
-
),
|
|
2043
|
-
const SizedBox(height: 10),
|
|
2044
|
-
Wrap(
|
|
2045
|
-
spacing: 10,
|
|
2046
|
-
runSpacing: 10,
|
|
2047
|
-
children: <Widget>[
|
|
2048
|
-
OutlinedButton(
|
|
2049
|
-
onPressed: controller.isRunningDeviceAction
|
|
2050
|
-
? null
|
|
2051
|
-
: () => controller.waitForAndroidRuntime(<String, dynamic>{
|
|
2052
|
-
'text': waitTextController.text.trim(),
|
|
2053
|
-
'timeoutMs': 20000,
|
|
2054
|
-
'intervalMs': 1200,
|
|
2055
|
-
}),
|
|
2056
|
-
child: Text('Wait For UI'),
|
|
2057
|
-
),
|
|
2058
|
-
OutlinedButton(
|
|
2059
|
-
onPressed: controller.isRunningDeviceAction
|
|
2060
|
-
? null
|
|
2061
|
-
: () => controller.pressAndroidKeyRuntime(
|
|
2062
|
-
keyController.text.trim(),
|
|
2063
|
-
),
|
|
2064
|
-
child: Text('Press Key'),
|
|
2065
|
-
),
|
|
2066
|
-
],
|
|
2067
|
-
),
|
|
2068
|
-
const SizedBox(height: 18),
|
|
2069
|
-
_DeviceFieldRow(
|
|
2070
|
-
children: <Widget>[
|
|
2071
|
-
_DeviceField(
|
|
2072
|
-
label: 'Tap Text',
|
|
2073
|
-
child: TextField(controller: tapTextController),
|
|
2074
|
-
),
|
|
2075
|
-
_DeviceField(
|
|
2076
|
-
label: 'Tap Description',
|
|
2077
|
-
child: TextField(controller: tapDescriptionController),
|
|
2078
|
-
),
|
|
2079
|
-
],
|
|
2080
|
-
),
|
|
2081
|
-
const SizedBox(height: 10),
|
|
2082
|
-
_DeviceFieldRow(
|
|
2083
|
-
children: <Widget>[
|
|
2084
|
-
_DeviceField(
|
|
2085
|
-
label: 'Tap Resource Id',
|
|
2086
|
-
child: TextField(controller: tapResourceIdController),
|
|
2087
|
-
),
|
|
2088
|
-
_DeviceField(
|
|
2089
|
-
label: 'Tap X / Y',
|
|
2090
|
-
child: Row(
|
|
2091
|
-
children: <Widget>[
|
|
2092
|
-
Expanded(child: TextField(controller: tapXController)),
|
|
2093
|
-
const SizedBox(width: 8),
|
|
2094
|
-
Expanded(child: TextField(controller: tapYController)),
|
|
2095
|
-
],
|
|
2096
|
-
),
|
|
2097
|
-
),
|
|
2098
|
-
],
|
|
2099
|
-
),
|
|
2100
|
-
const SizedBox(height: 10),
|
|
2101
|
-
OutlinedButton.icon(
|
|
2102
|
-
onPressed: controller.isRunningDeviceAction
|
|
2103
|
-
? null
|
|
2104
|
-
: () => controller.tapAndroidRuntime(<String, dynamic>{
|
|
2105
|
-
if (tapTextController.text.trim().isNotEmpty)
|
|
2106
|
-
'text': tapTextController.text.trim(),
|
|
2107
|
-
if (tapDescriptionController.text.trim().isNotEmpty)
|
|
2108
|
-
'description': tapDescriptionController.text.trim(),
|
|
2109
|
-
if (tapResourceIdController.text.trim().isNotEmpty)
|
|
2110
|
-
'resourceId': tapResourceIdController.text.trim(),
|
|
2111
|
-
if (toInt(tapXController.text) != null)
|
|
2112
|
-
'x': toInt(tapXController.text),
|
|
2113
|
-
if (toInt(tapYController.text) != null)
|
|
2114
|
-
'y': toInt(tapYController.text),
|
|
2115
|
-
}),
|
|
2116
|
-
icon: Icon(Icons.touch_app_outlined),
|
|
2117
|
-
label: Text('Tap'),
|
|
2118
|
-
),
|
|
2119
|
-
const SizedBox(height: 18),
|
|
2120
|
-
_DeviceFieldRow(
|
|
2121
|
-
children: <Widget>[
|
|
2122
|
-
_DeviceField(
|
|
2123
|
-
label: 'Type Text',
|
|
2124
|
-
child: TextField(controller: typeTextController),
|
|
2125
|
-
),
|
|
2126
|
-
_DeviceField(
|
|
2127
|
-
label: 'Focus Field Text / Description',
|
|
2128
|
-
child: Row(
|
|
2129
|
-
children: <Widget>[
|
|
2130
|
-
Expanded(
|
|
2131
|
-
child: TextField(controller: typeFieldTextController),
|
|
2132
|
-
),
|
|
2133
|
-
const SizedBox(width: 8),
|
|
2134
|
-
Expanded(
|
|
2135
|
-
child: TextField(
|
|
2136
|
-
controller: typeFieldDescriptionController,
|
|
2137
|
-
),
|
|
2138
|
-
),
|
|
2139
|
-
],
|
|
2140
|
-
),
|
|
2141
|
-
),
|
|
2142
|
-
],
|
|
2143
|
-
),
|
|
2144
|
-
const SizedBox(height: 10),
|
|
2145
|
-
OutlinedButton.icon(
|
|
2146
|
-
onPressed: controller.isRunningDeviceAction
|
|
2147
|
-
? null
|
|
2148
|
-
: () => controller.typeAndroidRuntime(<String, dynamic>{
|
|
2149
|
-
'text': typeTextController.text,
|
|
2150
|
-
if (typeFieldTextController.text.trim().isNotEmpty)
|
|
2151
|
-
'textSelector': typeFieldTextController.text.trim(),
|
|
2152
|
-
if (typeFieldDescriptionController.text.trim().isNotEmpty)
|
|
2153
|
-
'description': typeFieldDescriptionController.text.trim(),
|
|
2154
|
-
'pressEnter': true,
|
|
2155
|
-
}),
|
|
2156
|
-
icon: Icon(Icons.keyboard_outlined),
|
|
2157
|
-
label: Text('Type'),
|
|
2158
|
-
),
|
|
2159
|
-
const SizedBox(height: 18),
|
|
2160
|
-
_DeviceFieldRow(
|
|
2161
|
-
children: <Widget>[
|
|
2162
|
-
_DeviceField(
|
|
2163
|
-
label: 'Swipe X1 / Y1',
|
|
2164
|
-
child: Row(
|
|
2165
|
-
children: <Widget>[
|
|
2166
|
-
Expanded(child: TextField(controller: swipeX1Controller)),
|
|
2167
|
-
const SizedBox(width: 8),
|
|
2168
|
-
Expanded(child: TextField(controller: swipeY1Controller)),
|
|
2169
|
-
],
|
|
2170
|
-
),
|
|
2171
|
-
),
|
|
2172
|
-
_DeviceField(
|
|
2173
|
-
label: 'Swipe X2 / Y2',
|
|
2174
|
-
child: Row(
|
|
2175
|
-
children: <Widget>[
|
|
2176
|
-
Expanded(child: TextField(controller: swipeX2Controller)),
|
|
2177
|
-
const SizedBox(width: 8),
|
|
2178
|
-
Expanded(child: TextField(controller: swipeY2Controller)),
|
|
2179
|
-
],
|
|
2180
|
-
),
|
|
2181
|
-
),
|
|
2182
|
-
],
|
|
2183
|
-
),
|
|
2184
|
-
const SizedBox(height: 10),
|
|
2185
|
-
OutlinedButton.icon(
|
|
2186
|
-
onPressed: controller.isRunningDeviceAction
|
|
2187
|
-
? null
|
|
2188
|
-
: () => controller.swipeAndroidRuntime(<String, dynamic>{
|
|
2189
|
-
'x1': toInt(swipeX1Controller.text),
|
|
2190
|
-
'y1': toInt(swipeY1Controller.text),
|
|
2191
|
-
'x2': toInt(swipeX2Controller.text),
|
|
2192
|
-
'y2': toInt(swipeY2Controller.text),
|
|
2193
|
-
}),
|
|
2194
|
-
icon: Icon(Icons.swipe_outlined),
|
|
2195
|
-
label: Text('Swipe'),
|
|
2196
|
-
),
|
|
2197
|
-
const SizedBox(height: 18),
|
|
2198
|
-
_RuntimePreview(
|
|
2199
|
-
title: 'Latest Android Screenshot',
|
|
2200
|
-
screenshotPath: controller.androidScreenshotPath,
|
|
2201
|
-
controller: controller,
|
|
2202
|
-
),
|
|
2203
|
-
if (controller.androidUiPreview.isNotEmpty) ...<Widget>[
|
|
2204
|
-
const SizedBox(height: 14),
|
|
2205
|
-
Text(
|
|
2206
|
-
'Latest UI dump preview',
|
|
2207
|
-
style: TextStyle(fontWeight: FontWeight.w700),
|
|
2208
|
-
),
|
|
2209
|
-
const SizedBox(height: 10),
|
|
2210
|
-
...controller.androidUiPreview.take(6).map((node) {
|
|
2211
|
-
final title = node['text']?.toString().trim().isNotEmpty == true
|
|
2212
|
-
? node['text'].toString()
|
|
2213
|
-
: node['description']?.toString().trim().isNotEmpty == true
|
|
2214
|
-
? node['description'].toString()
|
|
2215
|
-
: node['resourceId']?.toString().trim().isNotEmpty == true
|
|
2216
|
-
? node['resourceId'].toString()
|
|
2217
|
-
: node['className']?.toString() ?? 'node';
|
|
2218
|
-
return Container(
|
|
2219
|
-
margin: const EdgeInsets.only(bottom: 8),
|
|
2220
|
-
padding: const EdgeInsets.all(12),
|
|
2221
|
-
decoration: BoxDecoration(
|
|
2222
|
-
color: _bgSecondary,
|
|
2223
|
-
borderRadius: BorderRadius.circular(10),
|
|
2224
|
-
border: Border.all(color: _border),
|
|
2225
|
-
),
|
|
2226
|
-
child: Text(
|
|
2227
|
-
'$title\n${node['packageName'] ?? ''}',
|
|
2228
|
-
style: TextStyle(color: _textSecondary, height: 1.5),
|
|
2229
|
-
),
|
|
2230
|
-
);
|
|
2231
|
-
}),
|
|
2232
|
-
],
|
|
2233
|
-
if (controller.androidLastResult?.trim().isNotEmpty ??
|
|
2234
|
-
false) ...<Widget>[
|
|
2235
|
-
const SizedBox(height: 14),
|
|
2236
|
-
_ResultBlock(
|
|
2237
|
-
label: 'Last Android result',
|
|
2238
|
-
value: controller.androidLastResult!,
|
|
2239
|
-
),
|
|
2240
|
-
],
|
|
2241
|
-
],
|
|
2242
|
-
);
|
|
2243
|
-
}
|
|
2244
|
-
}
|
|
2245
1580
|
|
|
2246
1581
|
class _DeviceFieldRow extends StatelessWidget {
|
|
2247
1582
|
const _DeviceFieldRow({required this.children});
|