neoagent 3.1.1-beta.5 → 3.1.1-beta.6

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.
@@ -0,0 +1,22 @@
1
+ <svg viewBox="0 0 100 100" width="1024" height="1024" xmlns="http://www.w3.org/2000/svg"><defs>
2
+ <linearGradient id="tile" x1="0%" y1="0%" x2="100%" y2="100%">
3
+ <stop offset="0%" stop-color="#705331"/><stop offset="100%" stop-color="#22564c"/>
4
+ </linearGradient>
5
+ <radialGradient id="shine" cx="26%" cy="14%" r="72%">
6
+ <stop offset="0%" stop-color="#ffffff" stop-opacity="0.22"/><stop offset="100%" stop-color="#ffffff" stop-opacity="0"/>
7
+ </radialGradient>
8
+ <radialGradient id="core" cx="38%" cy="32%" r="75%">
9
+ <stop offset="0%" stop-color="#fffdf6"/><stop offset="48%" stop-color="#eee3cc"/><stop offset="100%" stop-color="#b5a888"/>
10
+ </radialGradient>
11
+ <radialGradient id="node" cx="36%" cy="30%" r="80%">
12
+ <stop offset="0%" stop-color="#f8dca8"/><stop offset="52%" stop-color="#d3a85f"/><stop offset="100%" stop-color="#9b6f2f"/>
13
+ </radialGradient>
14
+ <linearGradient id="tube" x1="0%" y1="0%" x2="0%" y2="100%">
15
+ <stop offset="0%" stop-color="#fffaf0"/><stop offset="52%" stop-color="#efe7d6"/><stop offset="100%" stop-color="#cabf9f"/>
16
+ </linearGradient></defs>
17
+ <rect width="100" height="100" rx="22.5" fill="url(#tile)"/>
18
+ <rect width="100" height="100" rx="22.5" fill="url(#shine)"/>
19
+ <circle cx="50" cy="50" r="17" fill="none" stroke="url(#tube)" stroke-width="5" stroke-dasharray="78 29" stroke-linecap="round" stroke-opacity=".92"/>
20
+ <circle cx="50" cy="50" r="30" fill="none" stroke="url(#tube)" stroke-width="5" stroke-dasharray="150 39" stroke-linecap="round" stroke-opacity=".8"/>
21
+ <path d="M42 46 L50 50 L58 46 L58 56 L42 56 Z" fill="url(#core)"/>
22
+ <circle cx="50" cy="20" r="5.6" fill="url(#node)"/></svg>
@@ -13,6 +13,7 @@ import 'package:flutter/material.dart';
13
13
  import 'package:flutter/services.dart';
14
14
  import 'package:flutter_local_notifications/flutter_local_notifications.dart';
15
15
  import 'package:flutter_markdown/flutter_markdown.dart';
16
+ import 'package:flutter_svg/flutter_svg.dart';
16
17
  import 'package:file_picker/file_picker.dart';
17
18
  import 'package:flutter_secure_storage/flutter_secure_storage.dart';
18
19
  import 'package:google_fonts/google_fonts.dart';
@@ -99,6 +100,7 @@ const String _desktopAssistantHotkeyLabel = 'Ctrl + Shift + Space';
99
100
  const String _desktopWindowIconAsset = 'assets/branding/app_icon_256.png';
100
101
  const String _desktopTrayTemplateIconAsset =
101
102
  'assets/branding/tray_icon_template.png';
103
+ const String _neoMailLogoAsset = 'assets/branding/neomail_logo.svg';
102
104
  const String _sessionCookiePrefsKey = 'auth.sessionCookie';
103
105
  const String _sessionCookieBackendPrefsKey = 'auth.sessionCookieBackend';
104
106
  const String _sessionCookieSecureStorageKey = 'auth.sessionCookie.secure';
@@ -39,14 +39,16 @@ class OfficialIntegrationsTab extends StatelessWidget {
39
39
 
40
40
  @override
41
41
  Widget build(BuildContext context) {
42
- final visibleIntegrations = controller.officialIntegrations
43
- .where(
44
- (item) =>
45
- item.env.configured ||
46
- item.env.setupMode == 'user' ||
47
- item.isConnected,
48
- )
49
- .toList();
42
+ final visibleIntegrations =
43
+ controller.officialIntegrations
44
+ .where(
45
+ (item) =>
46
+ item.env.configured ||
47
+ item.env.setupMode == 'user' ||
48
+ item.isConnected,
49
+ )
50
+ .toList()
51
+ ..sort(_compareOfficialIntegrationItems);
50
52
 
51
53
  if (visibleIntegrations.isEmpty) {
52
54
  return Card(
@@ -1379,17 +1381,27 @@ class _OfficialIntegrationIcon extends StatelessWidget {
1379
1381
 
1380
1382
  @override
1381
1383
  Widget build(BuildContext context) {
1384
+ if (item.icon == 'neomail') {
1385
+ return Container(
1386
+ width: 44,
1387
+ height: 44,
1388
+ decoration: BoxDecoration(
1389
+ borderRadius: BorderRadius.circular(14),
1390
+ border: Border.all(color: _border),
1391
+ ),
1392
+ clipBehavior: Clip.antiAlias,
1393
+ child: SvgPicture.asset(_neoMailLogoAsset, fit: BoxFit.cover),
1394
+ );
1395
+ }
1382
1396
  final color = switch (item.icon) {
1383
1397
  'google' => const Color(0xFF4285F4),
1384
1398
  'home_assistant' => const Color(0xFF41BDF5),
1385
- 'neomail' => const Color(0xFF0F9D8A),
1386
1399
  'trello' => const Color(0xFF0C66E4),
1387
1400
  _ => _accent,
1388
1401
  };
1389
1402
  final label = switch (item.icon) {
1390
1403
  'google' => 'G',
1391
1404
  'home_assistant' => 'H',
1392
- 'neomail' => 'N',
1393
1405
  'trello' => 'T',
1394
1406
  _ => item.label.isNotEmpty ? item.label[0] : '?',
1395
1407
  };
@@ -1414,6 +1426,25 @@ class _OfficialIntegrationIcon extends StatelessWidget {
1414
1426
  }
1415
1427
  }
1416
1428
 
1429
+ int _compareOfficialIntegrationItems(
1430
+ OfficialIntegrationItem a,
1431
+ OfficialIntegrationItem b,
1432
+ ) {
1433
+ final rankDelta = _officialIntegrationRank(a) - _officialIntegrationRank(b);
1434
+ if (rankDelta != 0) {
1435
+ return rankDelta;
1436
+ }
1437
+ return a.label.toLowerCase().compareTo(b.label.toLowerCase());
1438
+ }
1439
+
1440
+ int _officialIntegrationRank(OfficialIntegrationItem item) {
1441
+ return switch (item.id) {
1442
+ 'neomail' => 0,
1443
+ 'google_workspace' => 1,
1444
+ _ => 10,
1445
+ };
1446
+ }
1447
+
1417
1448
  class _IntegrationSectionTitle extends StatelessWidget {
1418
1449
  const _IntegrationSectionTitle({required this.title});
1419
1450
 
@@ -372,6 +372,14 @@ packages:
372
372
  url: "https://pub.dev"
373
373
  source: hosted
374
374
  version: "0.1.3"
375
+ flutter_svg:
376
+ dependency: "direct main"
377
+ description:
378
+ name: flutter_svg
379
+ sha256: "35882981abcbfb8c15b286f0cd690ff25bac12d95eff3e25ee207f37d4c42e7f"
380
+ url: "https://pub.dev"
381
+ source: hosted
382
+ version: "2.3.0"
375
383
  flutter_test:
376
384
  dependency: "direct dev"
377
385
  description: flutter
@@ -670,6 +678,14 @@ packages:
670
678
  url: "https://pub.dev"
671
679
  source: hosted
672
680
  version: "1.9.1"
681
+ path_parsing:
682
+ dependency: transitive
683
+ description:
684
+ name: path_parsing
685
+ sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca"
686
+ url: "https://pub.dev"
687
+ source: hosted
688
+ version: "1.1.0"
673
689
  path_provider:
674
690
  dependency: "direct main"
675
691
  description:
@@ -1171,6 +1187,30 @@ packages:
1171
1187
  url: "https://pub.dev"
1172
1188
  source: hosted
1173
1189
  version: "4.5.3"
1190
+ vector_graphics:
1191
+ dependency: transitive
1192
+ description:
1193
+ name: vector_graphics
1194
+ sha256: "2306c03da2ba81724afeb589c351ebbc0aa7d86005925be8f8735856dbe5e42d"
1195
+ url: "https://pub.dev"
1196
+ source: hosted
1197
+ version: "1.2.2"
1198
+ vector_graphics_codec:
1199
+ dependency: transitive
1200
+ description:
1201
+ name: vector_graphics_codec
1202
+ sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146"
1203
+ url: "https://pub.dev"
1204
+ source: hosted
1205
+ version: "1.1.13"
1206
+ vector_graphics_compiler:
1207
+ dependency: transitive
1208
+ description:
1209
+ name: vector_graphics_compiler
1210
+ sha256: b9b3f391857781aa96acacef96066f2f49b4cd03cf9fce3ca4d8da2ef5ea129e
1211
+ url: "https://pub.dev"
1212
+ source: hosted
1213
+ version: "1.2.3"
1174
1214
  vector_math:
1175
1215
  dependency: transitive
1176
1216
  description:
@@ -40,6 +40,7 @@ dependencies:
40
40
  video_player: ^2.9.2
41
41
  flutter_animate: ^4.5.2
42
42
  flutter_local_notifications: ^18.0.0
43
+ flutter_svg: ^2.3.0
43
44
 
44
45
  dependency_overrides:
45
46
  flutter_secure_storage_linux:
@@ -62,4 +63,5 @@ flutter:
62
63
  - assets/branding/app_icon_light_1024.png
63
64
  - assets/branding/tray_icon_template.png
64
65
  - assets/branding/tray_icon_light_template.png
66
+ - assets/branding/neomail_logo.svg
65
67
  - assets/branding/onboarding_intro.mp4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neoagent",
3
- "version": "3.1.1-beta.5",
3
+ "version": "3.1.1-beta.6",
4
4
  "description": "Self-hosted AI agent for long-running tasks, automation, messaging, device control, and local memory",
5
5
  "license": "AGPL-3.0-only",
6
6
  "main": "server/index.js",
@@ -194,6 +194,35 @@
194
194
  onclick="backToCredentials()">← Back</button>
195
195
  </div>
196
196
  </div>
197
+
198
+ <div id="step-setup" style="display:none;">
199
+ <h1 class="login-heading">Set up two-factor authentication</h1>
200
+ <p class="login-sub">Admin access requires 2FA. Scan the QR code, enter the current authenticator code, and save the recovery codes before continuing.</p>
201
+ <div class="alert alert-error" id="setup-error" role="alert"></div>
202
+ <div style="display:flex;justify-content:center;margin-bottom:14px;">
203
+ <div style="background:white;padding:12px;border-radius:16px;">
204
+ <img id="setup-qr" alt="Admin 2FA QR code" width="200" height="200">
205
+ </div>
206
+ </div>
207
+ <div class="field">
208
+ <label for="setup-manual-key">Manual key</label>
209
+ <input id="setup-manual-key" type="text" readonly>
210
+ </div>
211
+ <form id="setup-form" novalidate>
212
+ <div class="field">
213
+ <label for="setup-code">Authenticator code</label>
214
+ <input id="setup-code" type="text" inputmode="numeric" autocomplete="one-time-code" maxlength="16" placeholder="123 456" spellcheck="false" autocorrect="off" autocapitalize="none" required>
215
+ </div>
216
+ <button type="submit" class="btn btn-primary submit-btn" id="setup-btn">Enable 2FA</button>
217
+ </form>
218
+ </div>
219
+
220
+ <div id="step-recovery" style="display:none;">
221
+ <h1 class="login-heading">Save recovery codes</h1>
222
+ <p class="login-sub">These codes are shown once. Store them now before you continue into the admin dashboard.</p>
223
+ <div id="recovery-codes" style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:18px;"></div>
224
+ <button type="button" class="btn btn-primary submit-btn" id="recovery-btn">Continue to admin</button>
225
+ </div>
197
226
  </div>
198
227
  </div>
199
228
 
@@ -204,15 +233,48 @@
204
233
  const totpForm = document.getElementById('totp-form');
205
234
  const totpBtn = document.getElementById('totp-btn');
206
235
  const totpError = document.getElementById('totp-error');
236
+ const setupForm = document.getElementById('setup-form');
237
+ const setupBtn = document.getElementById('setup-btn');
238
+ const setupError = document.getElementById('setup-error');
239
+ const recoveryBtn = document.getElementById('recovery-btn');
207
240
 
208
241
  function showStep(step) {
209
- document.getElementById('step-credentials').style.display = step === 1 ? '' : 'none';
210
- document.getElementById('step-totp').style.display = step === 2 ? '' : 'none';
211
- if (step === 2) setTimeout(() => document.getElementById('totp-code')?.focus(), 50);
212
- if (step === 1) setTimeout(() => document.getElementById('username')?.focus(), 50);
242
+ document.getElementById('step-credentials').style.display = step === 'credentials' ? '' : 'none';
243
+ document.getElementById('step-totp').style.display = step === 'totp' ? '' : 'none';
244
+ document.getElementById('step-setup').style.display = step === 'setup' ? '' : 'none';
245
+ document.getElementById('step-recovery').style.display = step === 'recovery' ? '' : 'none';
246
+ if (step === 'totp') setTimeout(() => document.getElementById('totp-code')?.focus(), 50);
247
+ if (step === 'setup') setTimeout(() => document.getElementById('setup-code')?.focus(), 50);
248
+ if (step === 'credentials') setTimeout(() => document.getElementById('username')?.focus(), 50);
213
249
  }
214
250
 
215
- function backToCredentials() { showStep(1); }
251
+ function backToCredentials() { showStep('credentials'); }
252
+
253
+ function loadSetup(setup) {
254
+ document.getElementById('setup-qr').src = setup.qrDataUrl || '';
255
+ document.getElementById('setup-manual-key').value = setup.manualKey || '';
256
+ document.getElementById('setup-code').value = '';
257
+ setupError.classList.remove('visible');
258
+ showStep('setup');
259
+ }
260
+
261
+ function showRecoveryCodes(codes) {
262
+ const container = document.getElementById('recovery-codes');
263
+ container.innerHTML = '';
264
+ for (const code of (Array.isArray(codes) ? codes : [])) {
265
+ const pill = document.createElement('div');
266
+ pill.textContent = code;
267
+ pill.style.padding = '10px 12px';
268
+ pill.style.border = '1px solid var(--border)';
269
+ pill.style.borderRadius = '12px';
270
+ pill.style.background = 'var(--bg-secondary)';
271
+ pill.style.fontFamily = 'var(--font-mono)';
272
+ pill.style.fontSize = '12px';
273
+ pill.style.color = 'var(--text)';
274
+ container.appendChild(pill);
275
+ }
276
+ showStep('recovery');
277
+ }
216
278
 
217
279
  form.addEventListener('submit', async (e) => {
218
280
  e.preventDefault();
@@ -232,10 +294,12 @@
232
294
 
233
295
  const body = await res.json().catch(() => ({}));
234
296
 
235
- if (res.ok || body.requiresTwoFactor) {
236
- if (body.requiresTwoFactor) {
297
+ if (res.ok || body.requiresTwoFactor || body.requiresTwoFactorSetup) {
298
+ if (body.requiresTwoFactorSetup) {
299
+ loadSetup(body.setup || {});
300
+ } else if (body.requiresTwoFactor) {
237
301
  // Password accepted — go to 2FA step
238
- showStep(2);
302
+ showStep('totp');
239
303
  } else {
240
304
  window.location.replace('/admin');
241
305
  }
@@ -253,6 +317,37 @@
253
317
  }
254
318
  });
255
319
 
320
+ setupForm.addEventListener('submit', async (e) => {
321
+ e.preventDefault();
322
+ setupError.classList.remove('visible');
323
+ setupBtn.disabled = true;
324
+ setupBtn.textContent = 'Enabling…';
325
+ try {
326
+ const res = await fetch('/admin/api/login/2fa/setup/enable', {
327
+ method: 'POST',
328
+ headers: { 'Content-Type': 'application/json' },
329
+ body: JSON.stringify({
330
+ code: document.getElementById('setup-code').value.trim(),
331
+ }),
332
+ });
333
+ const body = await res.json().catch(() => ({}));
334
+ if (!res.ok) {
335
+ throw new Error(body.error || '2FA setup failed');
336
+ }
337
+ showRecoveryCodes(body.recoveryCodes || []);
338
+ } catch (error) {
339
+ setupError.textContent = error.message || '2FA setup failed';
340
+ setupError.classList.add('visible');
341
+ } finally {
342
+ setupBtn.disabled = false;
343
+ setupBtn.textContent = 'Enable 2FA';
344
+ }
345
+ });
346
+
347
+ recoveryBtn.addEventListener('click', () => {
348
+ window.location.replace('/admin');
349
+ });
350
+
256
351
  totpForm.addEventListener('submit', async (e) => {
257
352
  e.preventDefault();
258
353
  totpError.classList.remove('visible');
@@ -1 +1 @@
1
- 42bd4b8346d90109903a278aa69f343f
1
+ 1f3c8d49376edaea381fffac366d6f2d
@@ -1 +1 @@
1
-
1
+
@@ -1 +1 @@
1
- "DQ0HIWFzc2V0cy9icmFuZGluZy9hcHBfaWNvbl8xMDI0LnBuZwwBDQEHBWFzc2V0ByFhc3NldHMvYnJhbmRpbmcvYXBwX2ljb25fMTAyNC5wbmcHIGFzc2V0cy9icmFuZGluZy9hcHBfaWNvbl8yNTYucG5nDAENAQcFYXNzZXQHIGFzc2V0cy9icmFuZGluZy9hcHBfaWNvbl8yNTYucG5nByBhc3NldHMvYnJhbmRpbmcvYXBwX2ljb25fNTEyLnBuZwwBDQEHBWFzc2V0ByBhc3NldHMvYnJhbmRpbmcvYXBwX2ljb25fNTEyLnBuZwcnYXNzZXRzL2JyYW5kaW5nL2FwcF9pY29uX2xpZ2h0XzEwMjQucG5nDAENAQcFYXNzZXQHJ2Fzc2V0cy9icmFuZGluZy9hcHBfaWNvbl9saWdodF8xMDI0LnBuZwcmYXNzZXRzL2JyYW5kaW5nL2FwcF9pY29uX2xpZ2h0XzI1Ni5wbmcMAQ0BBwVhc3NldAcmYXNzZXRzL2JyYW5kaW5nL2FwcF9pY29uX2xpZ2h0XzI1Ni5wbmcHJmFzc2V0cy9icmFuZGluZy9hcHBfaWNvbl9saWdodF81MTIucG5nDAENAQcFYXNzZXQHJmFzc2V0cy9icmFuZGluZy9hcHBfaWNvbl9saWdodF81MTIucG5nByRhc3NldHMvYnJhbmRpbmcvb25ib2FyZGluZ19pbnRyby5tcDQMAQ0BBwVhc3NldAckYXNzZXRzL2JyYW5kaW5nL29uYm9hcmRpbmdfaW50cm8ubXA0Byxhc3NldHMvYnJhbmRpbmcvdHJheV9pY29uX2xpZ2h0X3RlbXBsYXRlLnBuZwwBDQEHBWFzc2V0Byxhc3NldHMvYnJhbmRpbmcvdHJheV9pY29uX2xpZ2h0X3RlbXBsYXRlLnBuZwcmYXNzZXRzL2JyYW5kaW5nL3RyYXlfaWNvbl90ZW1wbGF0ZS5wbmcMAQ0BBwVhc3NldAcmYXNzZXRzL2JyYW5kaW5nL3RyYXlfaWNvbl90ZW1wbGF0ZS5wbmcHMnBhY2thZ2VzL2N1cGVydGlub19pY29ucy9hc3NldHMvQ3VwZXJ0aW5vSWNvbnMudHRmDAENAQcFYXNzZXQHMnBhY2thZ2VzL2N1cGVydGlub19pY29ucy9hc3NldHMvQ3VwZXJ0aW5vSWNvbnMudHRmBzdwYWNrYWdlcy9yZWNvcmRfd2ViL2Fzc2V0cy9qcy9yZWNvcmQuZml4d2VibWR1cmF0aW9uLmpzDAENAQcFYXNzZXQHN3BhY2thZ2VzL3JlY29yZF93ZWIvYXNzZXRzL2pzL3JlY29yZC5maXh3ZWJtZHVyYXRpb24uanMHL3BhY2thZ2VzL3JlY29yZF93ZWIvYXNzZXRzL2pzL3JlY29yZC53b3JrbGV0LmpzDAENAQcFYXNzZXQHL3BhY2thZ2VzL3JlY29yZF93ZWIvYXNzZXRzL2pzL3JlY29yZC53b3JrbGV0LmpzBxZ3ZWIvaWNvbnMvSWNvbi0xOTIucG5nDAENAQcFYXNzZXQHFndlYi9pY29ucy9JY29uLTE5Mi5wbmc="
1
+ "DQ4HIWFzc2V0cy9icmFuZGluZy9hcHBfaWNvbl8xMDI0LnBuZwwBDQEHBWFzc2V0ByFhc3NldHMvYnJhbmRpbmcvYXBwX2ljb25fMTAyNC5wbmcHIGFzc2V0cy9icmFuZGluZy9hcHBfaWNvbl8yNTYucG5nDAENAQcFYXNzZXQHIGFzc2V0cy9icmFuZGluZy9hcHBfaWNvbl8yNTYucG5nByBhc3NldHMvYnJhbmRpbmcvYXBwX2ljb25fNTEyLnBuZwwBDQEHBWFzc2V0ByBhc3NldHMvYnJhbmRpbmcvYXBwX2ljb25fNTEyLnBuZwcnYXNzZXRzL2JyYW5kaW5nL2FwcF9pY29uX2xpZ2h0XzEwMjQucG5nDAENAQcFYXNzZXQHJ2Fzc2V0cy9icmFuZGluZy9hcHBfaWNvbl9saWdodF8xMDI0LnBuZwcmYXNzZXRzL2JyYW5kaW5nL2FwcF9pY29uX2xpZ2h0XzI1Ni5wbmcMAQ0BBwVhc3NldAcmYXNzZXRzL2JyYW5kaW5nL2FwcF9pY29uX2xpZ2h0XzI1Ni5wbmcHJmFzc2V0cy9icmFuZGluZy9hcHBfaWNvbl9saWdodF81MTIucG5nDAENAQcFYXNzZXQHJmFzc2V0cy9icmFuZGluZy9hcHBfaWNvbl9saWdodF81MTIucG5nByBhc3NldHMvYnJhbmRpbmcvbmVvbWFpbF9sb2dvLnN2ZwwBDQEHBWFzc2V0ByBhc3NldHMvYnJhbmRpbmcvbmVvbWFpbF9sb2dvLnN2ZwckYXNzZXRzL2JyYW5kaW5nL29uYm9hcmRpbmdfaW50cm8ubXA0DAENAQcFYXNzZXQHJGFzc2V0cy9icmFuZGluZy9vbmJvYXJkaW5nX2ludHJvLm1wNAcsYXNzZXRzL2JyYW5kaW5nL3RyYXlfaWNvbl9saWdodF90ZW1wbGF0ZS5wbmcMAQ0BBwVhc3NldAcsYXNzZXRzL2JyYW5kaW5nL3RyYXlfaWNvbl9saWdodF90ZW1wbGF0ZS5wbmcHJmFzc2V0cy9icmFuZGluZy90cmF5X2ljb25fdGVtcGxhdGUucG5nDAENAQcFYXNzZXQHJmFzc2V0cy9icmFuZGluZy90cmF5X2ljb25fdGVtcGxhdGUucG5nBzJwYWNrYWdlcy9jdXBlcnRpbm9faWNvbnMvYXNzZXRzL0N1cGVydGlub0ljb25zLnR0ZgwBDQEHBWFzc2V0BzJwYWNrYWdlcy9jdXBlcnRpbm9faWNvbnMvYXNzZXRzL0N1cGVydGlub0ljb25zLnR0Zgc3cGFja2FnZXMvcmVjb3JkX3dlYi9hc3NldHMvanMvcmVjb3JkLmZpeHdlYm1kdXJhdGlvbi5qcwwBDQEHBWFzc2V0BzdwYWNrYWdlcy9yZWNvcmRfd2ViL2Fzc2V0cy9qcy9yZWNvcmQuZml4d2VibWR1cmF0aW9uLmpzBy9wYWNrYWdlcy9yZWNvcmRfd2ViL2Fzc2V0cy9qcy9yZWNvcmQud29ya2xldC5qcwwBDQEHBWFzc2V0By9wYWNrYWdlcy9yZWNvcmRfd2ViL2Fzc2V0cy9qcy9yZWNvcmQud29ya2xldC5qcwcWd2ViL2ljb25zL0ljb24tMTkyLnBuZwwBDQEHBWFzc2V0BxZ3ZWIvaWNvbnMvSWNvbi0xOTIucG5n"
@@ -2187,6 +2187,8 @@ url_launcher_ios
2187
2187
  url_launcher_linux
2188
2188
  url_launcher_macos
2189
2189
  url_launcher_windows
2190
+ vector_graphics
2191
+ vector_graphics_compiler
2190
2192
  video_player
2191
2193
  video_player_android
2192
2194
  video_player_avfoundation
@@ -5914,6 +5916,7 @@ shared_preferences_web
5914
5916
  shared_preferences_windows
5915
5917
  url_launcher
5916
5918
  url_launcher_platform_interface
5919
+ vector_graphics_codec
5917
5920
  video_player_web
5918
5921
  xdg_directories
5919
5922
 
@@ -6041,6 +6044,28 @@ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
6041
6044
  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
6042
6045
  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
6043
6046
  --------------------------------------------------------------------------------
6047
+ flutter_svg
6048
+
6049
+ Copyright (c) 2018 Dan Field
6050
+
6051
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6052
+ of this software and associated documentation files (the "Software"), to deal
6053
+ in the Software without restriction, including without limitation the rights
6054
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
6055
+ copies of the Software, and to permit persons to whom the Software is
6056
+ furnished to do so, subject to the following conditions:
6057
+
6058
+ The above copyright notice and this permission notice shall be included in all
6059
+ copies or substantial portions of the Software.
6060
+
6061
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
6062
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
6063
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
6064
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
6065
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
6066
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
6067
+ SOFTWARE.
6068
+ --------------------------------------------------------------------------------
6044
6069
  flutter_web_bluetooth
6045
6070
 
6046
6071
  MIT License
@@ -25427,6 +25452,29 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25427
25452
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25428
25453
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25429
25454
  SOFTWARE.
25455
+ --------------------------------------------------------------------------------
25456
+ path_parsing
25457
+
25458
+ Copyright (c) 2018 Dan Field
25459
+
25460
+ Permission is hereby granted, free of charge, to any person obtaining a copy
25461
+ of this software and associated documentation files (the "Software"), to deal
25462
+ in the Software without restriction, including without limitation the rights
25463
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
25464
+ copies of the Software, and to permit persons to whom the Software is
25465
+ furnished to do so, subject to the following conditions:
25466
+
25467
+ The above copyright notice and this permission notice shall be included in all
25468
+ copies or substantial portions of the Software.
25469
+
25470
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25471
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25472
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25473
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25474
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25475
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25476
+ SOFTWARE.
25477
+
25430
25478
  --------------------------------------------------------------------------------
25431
25479
  perfetto
25432
25480
 
@@ -0,0 +1,22 @@
1
+ <svg viewBox="0 0 100 100" width="1024" height="1024" xmlns="http://www.w3.org/2000/svg"><defs>
2
+ <linearGradient id="tile" x1="0%" y1="0%" x2="100%" y2="100%">
3
+ <stop offset="0%" stop-color="#705331"/><stop offset="100%" stop-color="#22564c"/>
4
+ </linearGradient>
5
+ <radialGradient id="shine" cx="26%" cy="14%" r="72%">
6
+ <stop offset="0%" stop-color="#ffffff" stop-opacity="0.22"/><stop offset="100%" stop-color="#ffffff" stop-opacity="0"/>
7
+ </radialGradient>
8
+ <radialGradient id="core" cx="38%" cy="32%" r="75%">
9
+ <stop offset="0%" stop-color="#fffdf6"/><stop offset="48%" stop-color="#eee3cc"/><stop offset="100%" stop-color="#b5a888"/>
10
+ </radialGradient>
11
+ <radialGradient id="node" cx="36%" cy="30%" r="80%">
12
+ <stop offset="0%" stop-color="#f8dca8"/><stop offset="52%" stop-color="#d3a85f"/><stop offset="100%" stop-color="#9b6f2f"/>
13
+ </radialGradient>
14
+ <linearGradient id="tube" x1="0%" y1="0%" x2="0%" y2="100%">
15
+ <stop offset="0%" stop-color="#fffaf0"/><stop offset="52%" stop-color="#efe7d6"/><stop offset="100%" stop-color="#cabf9f"/>
16
+ </linearGradient></defs>
17
+ <rect width="100" height="100" rx="22.5" fill="url(#tile)"/>
18
+ <rect width="100" height="100" rx="22.5" fill="url(#shine)"/>
19
+ <circle cx="50" cy="50" r="17" fill="none" stroke="url(#tube)" stroke-width="5" stroke-dasharray="78 29" stroke-linecap="round" stroke-opacity=".92"/>
20
+ <circle cx="50" cy="50" r="30" fill="none" stroke="url(#tube)" stroke-width="5" stroke-dasharray="150 39" stroke-linecap="round" stroke-opacity=".8"/>
21
+ <path d="M42 46 L50 50 L58 46 L58 56 L42 56 Z" fill="url(#core)"/>
22
+ <circle cx="50" cy="20" r="5.6" fill="url(#node)"/></svg>
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"a10d8ac38de835021c8d2f920dbf50a920ccc0
37
37
 
38
38
  _flutter.loader.load({
39
39
  serviceWorkerSettings: {
40
- serviceWorkerVersion: "2745407135" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
40
+ serviceWorkerVersion: "3722046936" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
41
41
  }
42
42
  });