neoagent 2.4.1-beta.10 → 2.4.1-beta.11
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/flutter_app/lib/features/onboarding/onboarding_companion_step.dart +373 -43
- package/flutter_app/lib/src/desktop_companion_actions.dart +9 -3
- package/package.json +1 -1
- package/server/public/.last_build_id +1 -1
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +53874 -53679
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import 'dart:convert';
|
|
2
|
+
import 'package:flutter/foundation.dart';
|
|
1
3
|
import 'package:flutter/material.dart';
|
|
2
4
|
import 'package:flutter_animate/flutter_animate.dart';
|
|
5
|
+
import 'package:http/http.dart' as http;
|
|
3
6
|
import 'package:url_launcher/url_launcher.dart';
|
|
4
7
|
|
|
5
8
|
import '../../main.dart';
|
|
@@ -21,6 +24,149 @@ class OnboardingCompanionStep extends StatefulWidget {
|
|
|
21
24
|
|
|
22
25
|
class _OnboardingCompanionStepState extends State<OnboardingCompanionStep> {
|
|
23
26
|
final Set<String> _clickedDownloads = <String>{};
|
|
27
|
+
String _selectedChannel = 'stable';
|
|
28
|
+
TargetPlatform _selectedDesktopPlatform = TargetPlatform.macOS;
|
|
29
|
+
|
|
30
|
+
bool _isLoadingReleases = false;
|
|
31
|
+
Map<String, Map<TargetPlatform, String>> _downloadUrls = {
|
|
32
|
+
'stable': <TargetPlatform, String>{},
|
|
33
|
+
'beta': <TargetPlatform, String>{},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
@override
|
|
37
|
+
void initState() {
|
|
38
|
+
super.initState();
|
|
39
|
+
switch (defaultTargetPlatform) {
|
|
40
|
+
case TargetPlatform.windows:
|
|
41
|
+
_selectedDesktopPlatform = TargetPlatform.windows;
|
|
42
|
+
break;
|
|
43
|
+
case TargetPlatform.linux:
|
|
44
|
+
_selectedDesktopPlatform = TargetPlatform.linux;
|
|
45
|
+
break;
|
|
46
|
+
default:
|
|
47
|
+
_selectedDesktopPlatform = TargetPlatform.macOS;
|
|
48
|
+
}
|
|
49
|
+
_fetchReleases();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
Future<void> _fetchReleases() async {
|
|
53
|
+
if (mounted) {
|
|
54
|
+
setState(() {
|
|
55
|
+
_isLoadingReleases = true;
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
final response = await http.get(
|
|
60
|
+
Uri.parse('https://api.github.com/repos/NeoLabs-Systems/NeoAgent/releases'),
|
|
61
|
+
headers: const <String, String>{
|
|
62
|
+
'Accept': 'application/vnd.github.v3+json',
|
|
63
|
+
},
|
|
64
|
+
).timeout(const Duration(seconds: 8));
|
|
65
|
+
|
|
66
|
+
if (response.statusCode == 200) {
|
|
67
|
+
final List<dynamic> releasesJson = jsonDecode(response.body) as List<dynamic>;
|
|
68
|
+
final urls = _parseReleases(releasesJson);
|
|
69
|
+
if (mounted) {
|
|
70
|
+
setState(() {
|
|
71
|
+
_downloadUrls = urls;
|
|
72
|
+
_isLoadingReleases = false;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
throw Exception('Failed to load releases: status ${response.statusCode}');
|
|
77
|
+
}
|
|
78
|
+
} catch (e) {
|
|
79
|
+
debugPrint('Error fetching releases: $e');
|
|
80
|
+
if (mounted) {
|
|
81
|
+
setState(() {
|
|
82
|
+
_isLoadingReleases = false;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
Map<String, Map<TargetPlatform, String>> _parseReleases(List<dynamic> releases) {
|
|
89
|
+
final Map<String, Map<TargetPlatform, String>> result = {
|
|
90
|
+
'stable': <TargetPlatform, String>{},
|
|
91
|
+
'beta': <TargetPlatform, String>{},
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
dynamic latestStable;
|
|
95
|
+
dynamic latestBeta;
|
|
96
|
+
|
|
97
|
+
for (final release in releases) {
|
|
98
|
+
if (release is! Map<String, dynamic>) continue;
|
|
99
|
+
final bool isPrerelease = release['prerelease'] == true;
|
|
100
|
+
final String tagName = (release['tag_name'] ?? '').toString();
|
|
101
|
+
final bool isBetaTag = tagName.contains('-beta');
|
|
102
|
+
|
|
103
|
+
if ((isPrerelease || isBetaTag) && latestBeta == null) {
|
|
104
|
+
latestBeta = release;
|
|
105
|
+
} else if (!isPrerelease && !isBetaTag && latestStable == null) {
|
|
106
|
+
latestStable = release;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (latestStable != null && latestBeta != null) {
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
latestStable ??= latestBeta;
|
|
115
|
+
latestBeta ??= latestStable;
|
|
116
|
+
|
|
117
|
+
if (latestStable != null) {
|
|
118
|
+
result['stable'] = _extractUrlsFromRelease(latestStable);
|
|
119
|
+
}
|
|
120
|
+
if (latestBeta != null) {
|
|
121
|
+
result['beta'] = _extractUrlsFromRelease(latestBeta);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
Map<TargetPlatform, String> _extractUrlsFromRelease(dynamic release) {
|
|
128
|
+
final Map<TargetPlatform, String> urls = <TargetPlatform, String>{};
|
|
129
|
+
if (release is! Map<String, dynamic>) return urls;
|
|
130
|
+
final List<dynamic> assets = release['assets'] as List<dynamic>? ?? const <dynamic>[];
|
|
131
|
+
|
|
132
|
+
for (final asset in assets) {
|
|
133
|
+
if (asset is! Map<String, dynamic>) continue;
|
|
134
|
+
final String name = (asset['name'] ?? '').toString().toLowerCase();
|
|
135
|
+
final String downloadUrl = (asset['browser_download_url'] ?? '').toString();
|
|
136
|
+
|
|
137
|
+
if (name.endsWith('.dmg')) {
|
|
138
|
+
urls[TargetPlatform.macOS] = downloadUrl;
|
|
139
|
+
} else if (name.endsWith('.exe') && name.contains('setup')) {
|
|
140
|
+
urls[TargetPlatform.windows] = downloadUrl;
|
|
141
|
+
} else if (name.endsWith('.exe')) {
|
|
142
|
+
urls.putIfAbsent(TargetPlatform.windows, () => downloadUrl);
|
|
143
|
+
} else if (name.endsWith('.deb')) {
|
|
144
|
+
urls[TargetPlatform.linux] = downloadUrl;
|
|
145
|
+
} else if (name.endsWith('.apk') && !name.contains('-launcher')) {
|
|
146
|
+
urls[TargetPlatform.android] = downloadUrl;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return urls;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
String _getFallbackUrl(String channel, TargetPlatform platform) {
|
|
153
|
+
final String tag = channel == 'beta' ? 'v2.4.1-beta.9' : 'v2.4.0';
|
|
154
|
+
final String ver = channel == 'beta' ? '2.4.1-beta.9' : '2.4.0';
|
|
155
|
+
final String debVer = ver.replaceAll('-', '~');
|
|
156
|
+
|
|
157
|
+
switch (platform) {
|
|
158
|
+
case TargetPlatform.macOS:
|
|
159
|
+
return 'https://github.com/NeoLabs-Systems/NeoAgent/releases/download/$tag/neoagent-macos-$ver.dmg';
|
|
160
|
+
case TargetPlatform.windows:
|
|
161
|
+
return 'https://github.com/NeoLabs-Systems/NeoAgent/releases/download/$tag/neoagent-windows-x64-setup-$ver.exe';
|
|
162
|
+
case TargetPlatform.linux:
|
|
163
|
+
return 'https://github.com/NeoLabs-Systems/NeoAgent/releases/download/$tag/neoagent-linux-amd64-$debVer.deb';
|
|
164
|
+
case TargetPlatform.android:
|
|
165
|
+
return 'https://github.com/NeoLabs-Systems/NeoAgent/releases/download/$tag/neoagent-android-$ver.apk';
|
|
166
|
+
default:
|
|
167
|
+
return 'https://github.com/NeoLabs-Systems/NeoAgent/releases/tag/$tag';
|
|
168
|
+
}
|
|
169
|
+
}
|
|
24
170
|
|
|
25
171
|
Future<void> _launchUrl(String urlString) async {
|
|
26
172
|
final url = Uri.parse(urlString);
|
|
@@ -29,6 +175,75 @@ class _OnboardingCompanionStepState extends State<OnboardingCompanionStep> {
|
|
|
29
175
|
}
|
|
30
176
|
}
|
|
31
177
|
|
|
178
|
+
String _getFileExtensionForPlatform(TargetPlatform platform) {
|
|
179
|
+
switch (platform) {
|
|
180
|
+
case TargetPlatform.macOS:
|
|
181
|
+
return '.dmg';
|
|
182
|
+
case TargetPlatform.windows:
|
|
183
|
+
return '.exe';
|
|
184
|
+
case TargetPlatform.linux:
|
|
185
|
+
return '.deb';
|
|
186
|
+
default:
|
|
187
|
+
return '';
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
Widget _buildChannelSelector() {
|
|
192
|
+
return Container(
|
|
193
|
+
padding: const EdgeInsets.all(4),
|
|
194
|
+
decoration: BoxDecoration(
|
|
195
|
+
color: Colors.white.withValues(alpha: 0.05),
|
|
196
|
+
borderRadius: BorderRadius.circular(16),
|
|
197
|
+
border: Border.all(color: Colors.white.withValues(alpha: 0.08)),
|
|
198
|
+
),
|
|
199
|
+
child: Row(
|
|
200
|
+
mainAxisSize: MainAxisSize.min,
|
|
201
|
+
children: <Widget>[
|
|
202
|
+
_buildChannelButton('stable', 'Stable Release'),
|
|
203
|
+
_buildChannelButton('beta', 'Beta Release'),
|
|
204
|
+
],
|
|
205
|
+
),
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
Widget _buildChannelButton(String channel, String label) {
|
|
210
|
+
final isSelected = _selectedChannel == channel;
|
|
211
|
+
return Material(
|
|
212
|
+
color: Colors.transparent,
|
|
213
|
+
child: InkWell(
|
|
214
|
+
onTap: () {
|
|
215
|
+
setState(() {
|
|
216
|
+
_selectedChannel = channel;
|
|
217
|
+
});
|
|
218
|
+
},
|
|
219
|
+
borderRadius: BorderRadius.circular(12),
|
|
220
|
+
child: AnimatedContainer(
|
|
221
|
+
duration: const Duration(milliseconds: 200),
|
|
222
|
+
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
|
223
|
+
decoration: BoxDecoration(
|
|
224
|
+
color: isSelected
|
|
225
|
+
? Theme.of(context).colorScheme.primary.withValues(alpha: 0.16)
|
|
226
|
+
: Colors.transparent,
|
|
227
|
+
borderRadius: BorderRadius.circular(12),
|
|
228
|
+
border: Border.all(
|
|
229
|
+
color: isSelected
|
|
230
|
+
? Theme.of(context).colorScheme.primary.withValues(alpha: 0.6)
|
|
231
|
+
: Colors.transparent,
|
|
232
|
+
),
|
|
233
|
+
),
|
|
234
|
+
child: Text(
|
|
235
|
+
label,
|
|
236
|
+
style: TextStyle(
|
|
237
|
+
color: isSelected ? Colors.white : Colors.white.withValues(alpha: 0.6),
|
|
238
|
+
fontSize: 14,
|
|
239
|
+
fontWeight: FontWeight.w700,
|
|
240
|
+
),
|
|
241
|
+
),
|
|
242
|
+
),
|
|
243
|
+
),
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
32
247
|
@override
|
|
33
248
|
Widget build(BuildContext context) {
|
|
34
249
|
final width = MediaQuery.sizeOf(context).width;
|
|
@@ -62,10 +277,14 @@ class _OnboardingCompanionStepState extends State<OnboardingCompanionStep> {
|
|
|
62
277
|
icon: Icons.laptop_mac_rounded,
|
|
63
278
|
accentColor: desktopConnected ? const Color(0xFF10A37F) : const Color(0xFF8A5CF5),
|
|
64
279
|
connected: desktopConnected,
|
|
65
|
-
buttonText: desktopConnected
|
|
280
|
+
buttonText: desktopConnected
|
|
281
|
+
? 'Desktop Connected'
|
|
282
|
+
: 'Download for ${_selectedDesktopPlatform.name.toUpperCase()} (${_getFileExtensionForPlatform(_selectedDesktopPlatform)})',
|
|
66
283
|
onTap: () async {
|
|
67
284
|
setState(() => _clickedDownloads.add('desktop'));
|
|
68
|
-
|
|
285
|
+
final url = _downloadUrls[_selectedChannel]?[_selectedDesktopPlatform] ??
|
|
286
|
+
_getFallbackUrl(_selectedChannel, _selectedDesktopPlatform);
|
|
287
|
+
await _launchUrl(url);
|
|
69
288
|
},
|
|
70
289
|
),
|
|
71
290
|
_CompanionItemData(
|
|
@@ -78,7 +297,9 @@ class _OnboardingCompanionStepState extends State<OnboardingCompanionStep> {
|
|
|
78
297
|
buttonText: 'Download Android APK',
|
|
79
298
|
onTap: () async {
|
|
80
299
|
setState(() => _clickedDownloads.add('mobile'));
|
|
81
|
-
|
|
300
|
+
final url = _downloadUrls[_selectedChannel]?[TargetPlatform.android] ??
|
|
301
|
+
_getFallbackUrl(_selectedChannel, TargetPlatform.android);
|
|
302
|
+
await _launchUrl(url);
|
|
82
303
|
},
|
|
83
304
|
),
|
|
84
305
|
];
|
|
@@ -103,42 +324,79 @@ class _OnboardingCompanionStepState extends State<OnboardingCompanionStep> {
|
|
|
103
324
|
),
|
|
104
325
|
],
|
|
105
326
|
),
|
|
106
|
-
child:
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
)
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
327
|
+
child: Column(
|
|
328
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
329
|
+
children: <Widget>[
|
|
330
|
+
Row(
|
|
331
|
+
mainAxisAlignment: MainAxisAlignment.center,
|
|
332
|
+
children: [
|
|
333
|
+
_buildChannelSelector(),
|
|
334
|
+
if (_isLoadingReleases) ...[
|
|
335
|
+
const SizedBox(width: 12),
|
|
336
|
+
const SizedBox(
|
|
337
|
+
width: 14,
|
|
338
|
+
height: 14,
|
|
339
|
+
child: CircularProgressIndicator(
|
|
340
|
+
strokeWidth: 2,
|
|
341
|
+
valueColor: AlwaysStoppedAnimation<Color>(Colors.white54),
|
|
342
|
+
),
|
|
343
|
+
),
|
|
344
|
+
],
|
|
345
|
+
],
|
|
346
|
+
),
|
|
347
|
+
const SizedBox(height: 20),
|
|
348
|
+
Expanded(
|
|
349
|
+
child: useGrid
|
|
350
|
+
? GridView.builder(
|
|
351
|
+
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
|
352
|
+
crossAxisCount: columns,
|
|
353
|
+
crossAxisSpacing: 14,
|
|
354
|
+
mainAxisSpacing: 14,
|
|
355
|
+
childAspectRatio: width >= 1050 ? 1.42 : 1.25,
|
|
356
|
+
),
|
|
357
|
+
itemCount: items.length,
|
|
358
|
+
itemBuilder: (context, index) {
|
|
359
|
+
final item = items[index];
|
|
360
|
+
return _CompanionCard(
|
|
361
|
+
item: item,
|
|
362
|
+
compact: true,
|
|
363
|
+
isClicked: _clickedDownloads.contains(item.id),
|
|
364
|
+
selectedDesktopPlatform: _selectedDesktopPlatform,
|
|
365
|
+
onDesktopPlatformChanged: (platform) {
|
|
366
|
+
setState(() {
|
|
367
|
+
_selectedDesktopPlatform = platform;
|
|
368
|
+
});
|
|
369
|
+
},
|
|
370
|
+
)
|
|
371
|
+
.animate()
|
|
372
|
+
.fadeIn(duration: 420.ms, delay: (180 + (index * 80)).ms)
|
|
373
|
+
.slideY(begin: 0.16, end: 0);
|
|
374
|
+
},
|
|
375
|
+
)
|
|
376
|
+
: ListView.separated(
|
|
377
|
+
itemCount: items.length,
|
|
378
|
+
separatorBuilder: (_, __) => const SizedBox(height: 14),
|
|
379
|
+
itemBuilder: (context, index) {
|
|
380
|
+
final item = items[index];
|
|
381
|
+
return _CompanionCard(
|
|
382
|
+
item: item,
|
|
383
|
+
compact: false,
|
|
384
|
+
isClicked: _clickedDownloads.contains(item.id),
|
|
385
|
+
selectedDesktopPlatform: _selectedDesktopPlatform,
|
|
386
|
+
onDesktopPlatformChanged: (platform) {
|
|
387
|
+
setState(() {
|
|
388
|
+
_selectedDesktopPlatform = platform;
|
|
389
|
+
});
|
|
390
|
+
},
|
|
391
|
+
)
|
|
392
|
+
.animate()
|
|
393
|
+
.fadeIn(duration: 420.ms, delay: (180 + (index * 80)).ms)
|
|
394
|
+
.slideY(begin: 0.16, end: 0);
|
|
395
|
+
},
|
|
396
|
+
),
|
|
397
|
+
),
|
|
398
|
+
],
|
|
399
|
+
),
|
|
142
400
|
);
|
|
143
401
|
},
|
|
144
402
|
);
|
|
@@ -172,11 +430,15 @@ class _CompanionCard extends StatelessWidget {
|
|
|
172
430
|
required this.item,
|
|
173
431
|
required this.compact,
|
|
174
432
|
required this.isClicked,
|
|
433
|
+
required this.selectedDesktopPlatform,
|
|
434
|
+
required this.onDesktopPlatformChanged,
|
|
175
435
|
});
|
|
176
436
|
|
|
177
437
|
final _CompanionItemData item;
|
|
178
438
|
final bool compact;
|
|
179
439
|
final bool isClicked;
|
|
440
|
+
final TargetPlatform selectedDesktopPlatform;
|
|
441
|
+
final ValueChanged<TargetPlatform> onDesktopPlatformChanged;
|
|
180
442
|
|
|
181
443
|
@override
|
|
182
444
|
Widget build(BuildContext context) {
|
|
@@ -231,6 +493,11 @@ class _CompanionCard extends StatelessWidget {
|
|
|
231
493
|
),
|
|
232
494
|
),
|
|
233
495
|
const SizedBox(height: 12),
|
|
496
|
+
if (item.id == 'desktop' && !item.connected)
|
|
497
|
+
_PlatformSelector(
|
|
498
|
+
selectedPlatform: selectedDesktopPlatform,
|
|
499
|
+
onPlatformChanged: onDesktopPlatformChanged,
|
|
500
|
+
),
|
|
234
501
|
_DownloadButton(item: item, isClicked: isClicked),
|
|
235
502
|
],
|
|
236
503
|
)
|
|
@@ -274,6 +541,11 @@ class _CompanionCard extends StatelessWidget {
|
|
|
274
541
|
),
|
|
275
542
|
),
|
|
276
543
|
const SizedBox(height: 14),
|
|
544
|
+
if (item.id == 'desktop' && !item.connected)
|
|
545
|
+
_PlatformSelector(
|
|
546
|
+
selectedPlatform: selectedDesktopPlatform,
|
|
547
|
+
onPlatformChanged: onDesktopPlatformChanged,
|
|
548
|
+
),
|
|
277
549
|
_DownloadButton(item: item, isClicked: isClicked),
|
|
278
550
|
],
|
|
279
551
|
),
|
|
@@ -357,13 +629,13 @@ class _DownloadButton extends StatelessWidget {
|
|
|
357
629
|
return Container(
|
|
358
630
|
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
|
|
359
631
|
decoration: BoxDecoration(
|
|
360
|
-
color: isClicked
|
|
361
|
-
? Colors.white.withValues(alpha: 0.08)
|
|
632
|
+
color: isClicked
|
|
633
|
+
? Colors.white.withValues(alpha: 0.08)
|
|
362
634
|
: item.accentColor.withValues(alpha: 0.12),
|
|
363
635
|
borderRadius: BorderRadius.circular(12),
|
|
364
636
|
border: Border.all(
|
|
365
|
-
color: isClicked
|
|
366
|
-
? Colors.white.withValues(alpha: 0.2)
|
|
637
|
+
color: isClicked
|
|
638
|
+
? Colors.white.withValues(alpha: 0.2)
|
|
367
639
|
: item.accentColor.withValues(alpha: 0.25),
|
|
368
640
|
),
|
|
369
641
|
),
|
|
@@ -389,3 +661,61 @@ class _DownloadButton extends StatelessWidget {
|
|
|
389
661
|
);
|
|
390
662
|
}
|
|
391
663
|
}
|
|
664
|
+
|
|
665
|
+
class _PlatformSelector extends StatelessWidget {
|
|
666
|
+
const _PlatformSelector({
|
|
667
|
+
required this.selectedPlatform,
|
|
668
|
+
required this.onPlatformChanged,
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
final TargetPlatform selectedPlatform;
|
|
672
|
+
final ValueChanged<TargetPlatform> onPlatformChanged;
|
|
673
|
+
|
|
674
|
+
@override
|
|
675
|
+
Widget build(BuildContext context) {
|
|
676
|
+
return Container(
|
|
677
|
+
margin: const EdgeInsets.only(bottom: 12),
|
|
678
|
+
padding: const EdgeInsets.all(2),
|
|
679
|
+
decoration: BoxDecoration(
|
|
680
|
+
color: Colors.white.withValues(alpha: 0.04),
|
|
681
|
+
borderRadius: BorderRadius.circular(10),
|
|
682
|
+
border: Border.all(color: Colors.white.withValues(alpha: 0.06)),
|
|
683
|
+
),
|
|
684
|
+
child: Row(
|
|
685
|
+
mainAxisSize: MainAxisSize.min,
|
|
686
|
+
children: <Widget>[
|
|
687
|
+
_buildTab(TargetPlatform.macOS, 'macOS'),
|
|
688
|
+
_buildTab(TargetPlatform.windows, 'Windows'),
|
|
689
|
+
_buildTab(TargetPlatform.linux, 'Linux'),
|
|
690
|
+
],
|
|
691
|
+
),
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
Widget _buildTab(TargetPlatform platform, String label) {
|
|
696
|
+
final isSelected = selectedPlatform == platform;
|
|
697
|
+
return Material(
|
|
698
|
+
color: Colors.transparent,
|
|
699
|
+
child: InkWell(
|
|
700
|
+
onTap: () => onPlatformChanged(platform),
|
|
701
|
+
borderRadius: BorderRadius.circular(8),
|
|
702
|
+
child: AnimatedContainer(
|
|
703
|
+
duration: const Duration(milliseconds: 180),
|
|
704
|
+
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
|
705
|
+
decoration: BoxDecoration(
|
|
706
|
+
color: isSelected ? Colors.white.withValues(alpha: 0.08) : Colors.transparent,
|
|
707
|
+
borderRadius: BorderRadius.circular(8),
|
|
708
|
+
),
|
|
709
|
+
child: Text(
|
|
710
|
+
label,
|
|
711
|
+
style: TextStyle(
|
|
712
|
+
color: isSelected ? Colors.white : Colors.white.withValues(alpha: 0.5),
|
|
713
|
+
fontSize: 11,
|
|
714
|
+
fontWeight: FontWeight.w700,
|
|
715
|
+
),
|
|
716
|
+
),
|
|
717
|
+
),
|
|
718
|
+
),
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
@@ -100,9 +100,15 @@ class DesktopCompanionActions {
|
|
|
100
100
|
if (bytes is! Uint8List || bytes.isEmpty) {
|
|
101
101
|
return null;
|
|
102
102
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
final
|
|
103
|
+
// Prefer dimensions reported by the native bridge; only fall back to a
|
|
104
|
+
// pure-Dart image decode (which is slow) when the bridge omits them.
|
|
105
|
+
final nativeWidth = (frame['width'] as num?)?.round();
|
|
106
|
+
final nativeHeight = (frame['height'] as num?)?.round();
|
|
107
|
+
final decoded = (nativeWidth == null || nativeHeight == null)
|
|
108
|
+
? img.decodeImage(bytes)
|
|
109
|
+
: null;
|
|
110
|
+
final width = nativeWidth ?? decoded?.width ?? 0;
|
|
111
|
+
final height = nativeHeight ?? decoded?.height ?? 0;
|
|
106
112
|
final displays = _normalizeDisplays(
|
|
107
113
|
frame['displays'],
|
|
108
114
|
fallbackDisplayId:
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
178383462d4c7125ce032b3783b45564
|
|
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"4c525dac5ebe5971c5708ef73558ed8edcf4a3
|
|
|
37
37
|
|
|
38
38
|
_flutter.loader.load({
|
|
39
39
|
serviceWorkerSettings: {
|
|
40
|
-
serviceWorkerVersion: "
|
|
40
|
+
serviceWorkerVersion: "559469558" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
|
|
41
41
|
}
|
|
42
42
|
});
|