@_nazmiforreal/flutter-ota 0.1.28 → 0.1.30
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/flutter-ota-linux-x64 +0 -0
- package/bin/flutter-ota.js +0 -0
- package/dart-src/packages/cli-tools/.dart_tool/package_config.json +496 -0
- package/dart-src/packages/cli-tools/.dart_tool/package_graph.json +754 -0
- package/dart-src/packages/cli-tools/.dart_tool/version +1 -0
- package/dart-src/packages/cli-tools/lib/src/commands/doctor.dart +22 -1
- package/dart-src/packages/cli-tools/lib/src/commands/init.dart +17 -8
- package/dart-src/packages/cli-tools/pubspec.lock +636 -0
- package/dart-src/packages/cli-tools/pubspec.yaml +7 -7
- package/dart-src/packages/cli-tools/test/mocks/mock_pocketbase_client.dart +2 -1
- package/dart-src/root/analysis_options.yaml +3 -0
- package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/FlutterPatcherPlugin.kt +1 -0
- package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/PatchManager.kt +24 -5
- package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/PatcherConfig.kt +20 -0
- package/dart-src/root/lib/flutter_ota_kit.dart +31 -11
- package/dart-src/root/lib/src/aws_update_source.dart +1 -0
- package/dart-src/root/lib/src/cloudflare_update_source.dart +1 -0
- package/dart-src/root/lib/src/ota_progress_overlay.dart +243 -296
- package/dart-src/root/lib/src/patcher_channel.dart +14 -1
- package/dart-src/root/lib/src/pkg/plugin_core/src/request_update_bundle_state.dart +2 -1
- package/dart-src/root/lib/src/pkg/postgres/src/postgres_types.dart +2 -1
- package/dart-src/root/lib/src/pocketbase_update_source.dart +1 -0
- package/dart-src/root/lib/src/postgres_update_source.dart +1 -0
- package/dart-src/root/lib/src/shared_update_check.dart +30 -2
- package/dart-src/root/lib/src/supabase_update_source.dart +1 -0
- package/dart-src/root/pubspec.yaml +14 -14
- package/package.json +1 -1
|
@@ -169,7 +169,7 @@ const Map<String, Color> _logColors = {
|
|
|
169
169
|
/// - Title bar (traffic lights + status pill)
|
|
170
170
|
/// - META (channel / version / bundle / size)
|
|
171
171
|
/// - STEPS (per-phase status list)
|
|
172
|
-
/// - PROGRESS (
|
|
172
|
+
/// - PROGRESS (single-line determinate bar + percent)
|
|
173
173
|
/// - LOG (scrolling colored feed)
|
|
174
174
|
/// - Footer (deploy message, or error hint + retry)
|
|
175
175
|
class OtaProgressOverlay extends StatefulWidget {
|
|
@@ -275,36 +275,35 @@ class _OtaProgressOverlayState extends State<OtaProgressOverlay> {
|
|
|
275
275
|
|
|
276
276
|
// ── Title bar ─────────────────────────────────────────────────────────
|
|
277
277
|
Widget _titleBar(OtaOverlayState s) => Container(
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
278
|
+
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 11),
|
|
279
|
+
decoration: const BoxDecoration(
|
|
280
|
+
color: _C.head,
|
|
281
|
+
borderRadius: BorderRadius.vertical(top: Radius.circular(12)),
|
|
282
|
+
border: Border(bottom: BorderSide(color: _C.border)),
|
|
283
|
+
),
|
|
284
|
+
child: Row(
|
|
285
|
+
children: [
|
|
286
|
+
_dot(_C.red),
|
|
287
|
+
const SizedBox(width: 6),
|
|
288
|
+
_dot(_C.yellow),
|
|
289
|
+
const SizedBox(width: 6),
|
|
290
|
+
_dot(_C.green),
|
|
291
|
+
const SizedBox(width: 12),
|
|
292
|
+
const Flexible(
|
|
293
|
+
child: Text(
|
|
294
|
+
'flutter-ota · forced update',
|
|
295
|
+
overflow: TextOverflow.ellipsis,
|
|
296
|
+
style: TextStyle(color: _C.text, fontFamily: _mono, fontSize: 12),
|
|
297
|
+
),
|
|
283
298
|
),
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
_dot(_C.yellow),
|
|
289
|
-
const SizedBox(width: 6),
|
|
290
|
-
_dot(_C.green),
|
|
291
|
-
const SizedBox(width: 12),
|
|
292
|
-
const Flexible(
|
|
293
|
-
child: Text(
|
|
294
|
-
'flutter-ota · forced update',
|
|
295
|
-
overflow: TextOverflow.ellipsis,
|
|
296
|
-
style: TextStyle(
|
|
297
|
-
color: _C.text, fontFamily: _mono, fontSize: 12),
|
|
298
|
-
),
|
|
299
|
-
),
|
|
300
|
-
const SizedBox(width: 8),
|
|
301
|
-
_pill(
|
|
302
|
-
s.hasError ? 'FAILED' : 'RUNNING',
|
|
303
|
-
s.hasError ? _C.red : _C.green,
|
|
304
|
-
),
|
|
305
|
-
],
|
|
299
|
+
const SizedBox(width: 8),
|
|
300
|
+
_pill(
|
|
301
|
+
s.hasError ? 'FAILED' : 'RUNNING',
|
|
302
|
+
s.hasError ? _C.red : _C.green,
|
|
306
303
|
),
|
|
307
|
-
|
|
304
|
+
],
|
|
305
|
+
),
|
|
306
|
+
);
|
|
308
307
|
|
|
309
308
|
// ── META ──────────────────────────────────────────────────────────────
|
|
310
309
|
Widget _metaBlock(OtaOverlayState s) {
|
|
@@ -330,12 +329,15 @@ class _OtaProgressOverlayState extends State<OtaProgressOverlay> {
|
|
|
330
329
|
style: const TextStyle(fontFamily: _mono, fontSize: 13),
|
|
331
330
|
children: [
|
|
332
331
|
TextSpan(
|
|
333
|
-
|
|
334
|
-
|
|
332
|
+
text: '${r[0].padRight(9)} ',
|
|
333
|
+
style: const TextStyle(color: _C.dim),
|
|
334
|
+
),
|
|
335
335
|
TextSpan(
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
336
|
+
text: r[1],
|
|
337
|
+
style: TextStyle(
|
|
338
|
+
color: r[0] == 'version' ? _C.blue : _C.text,
|
|
339
|
+
),
|
|
340
|
+
),
|
|
339
341
|
],
|
|
340
342
|
),
|
|
341
343
|
),
|
|
@@ -367,31 +369,32 @@ class _OtaProgressOverlayState extends State<OtaProgressOverlay> {
|
|
|
367
369
|
final Color color = failed
|
|
368
370
|
? _C.red
|
|
369
371
|
: done
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
372
|
+
? _C.green
|
|
373
|
+
: active
|
|
374
|
+
? _C.blue
|
|
375
|
+
: _C.faint;
|
|
374
376
|
|
|
375
377
|
final String glyph = failed
|
|
376
378
|
? '✗'
|
|
377
379
|
: done
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
380
|
+
? '✓'
|
|
381
|
+
: active
|
|
382
|
+
? spinner
|
|
383
|
+
: '·';
|
|
382
384
|
|
|
383
385
|
// Only the downloading step shows a percentage. Verify / install /
|
|
384
|
-
//
|
|
385
|
-
//
|
|
386
|
+
// Only the download step shows a percentage. Other active phases show
|
|
387
|
+
// a steady "working…" (no spinning glyph in the status column — the
|
|
388
|
+
// row's leading glyph already spins), so nothing jitters.
|
|
386
389
|
final String status;
|
|
387
390
|
if (failed) {
|
|
388
391
|
status = 'error';
|
|
389
392
|
} else if (done) {
|
|
390
393
|
status = 'done';
|
|
391
394
|
} else if (active) {
|
|
392
|
-
final showPct =
|
|
393
|
-
s.fraction != null;
|
|
394
|
-
status = showPct ? _pct(s.fraction) : 'working
|
|
395
|
+
final showPct =
|
|
396
|
+
s.phase == PatchApplyPhase.downloading && s.fraction != null;
|
|
397
|
+
status = showPct ? _pct(s.fraction) : 'working…';
|
|
395
398
|
} else {
|
|
396
399
|
status = 'wait';
|
|
397
400
|
}
|
|
@@ -402,9 +405,14 @@ class _OtaProgressOverlayState extends State<OtaProgressOverlay> {
|
|
|
402
405
|
children: [
|
|
403
406
|
SizedBox(
|
|
404
407
|
width: 18,
|
|
405
|
-
child: Text(
|
|
406
|
-
|
|
407
|
-
|
|
408
|
+
child: Text(
|
|
409
|
+
glyph,
|
|
410
|
+
style: TextStyle(
|
|
411
|
+
color: color,
|
|
412
|
+
fontFamily: _mono,
|
|
413
|
+
fontSize: 14,
|
|
414
|
+
),
|
|
415
|
+
),
|
|
408
416
|
),
|
|
409
417
|
const SizedBox(width: 8),
|
|
410
418
|
Expanded(
|
|
@@ -437,68 +445,72 @@ class _OtaProgressOverlayState extends State<OtaProgressOverlay> {
|
|
|
437
445
|
|
|
438
446
|
// ── PROGRESS ──────────────────────────────────────────────────────────
|
|
439
447
|
Widget _progressBlock(OtaOverlayState s) {
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
448
|
+
// Single honest fraction for the bar:
|
|
449
|
+
// - preparing (no phase / no fraction yet) → empty
|
|
450
|
+
// - downloading with a known size → real fraction
|
|
451
|
+
// - verifying / finalizing / restarting → full (download is already
|
|
452
|
+
// done, so the bar rests at 100%)
|
|
453
|
+
// - error → freeze wherever it was
|
|
454
|
+
final double frac;
|
|
455
|
+
if (s.hasError) {
|
|
456
|
+
frac = (s.fraction ?? 0).clamp(0.0, 1.0);
|
|
457
|
+
} else if (s.restarting) {
|
|
458
|
+
frac = 1.0;
|
|
459
|
+
} else if (s.phase == PatchApplyPhase.downloading) {
|
|
460
|
+
frac = (s.fraction ?? 0).clamp(0.0, 1.0);
|
|
461
|
+
} else if (s.phase == PatchApplyPhase.verifying ||
|
|
462
|
+
s.phase == PatchApplyPhase.finalizing) {
|
|
463
|
+
frac = 1.0;
|
|
464
|
+
} else {
|
|
465
|
+
frac = 0.0; // preparing
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// The readout lives on its own line above the bar (right aligned) so the
|
|
469
|
+
// bar itself spans the full panel width and can visibly reach the end.
|
|
470
|
+
// It used to sit in a fixed column beside the bar, which (a) reserved
|
|
471
|
+
// 54px the bar could never grow into and (b) hid the percent as soon as
|
|
472
|
+
// the download phase ended, making the bar look stuck below 100%.
|
|
473
|
+
final String pctLabel;
|
|
474
|
+
if (s.hasError) {
|
|
475
|
+
pctLabel = '';
|
|
476
|
+
} else if (s.restarting ||
|
|
477
|
+
s.phase == PatchApplyPhase.verifying ||
|
|
478
|
+
s.phase == PatchApplyPhase.finalizing) {
|
|
479
|
+
pctLabel = _pct(1.0);
|
|
480
|
+
} else if (s.phase == PatchApplyPhase.downloading && s.fraction != null) {
|
|
481
|
+
pctLabel = _pct(s.fraction);
|
|
482
|
+
} else {
|
|
483
|
+
pctLabel = '';
|
|
449
484
|
}
|
|
450
|
-
final phase = s.hasError
|
|
451
|
-
? 'halted'
|
|
452
|
-
: (s.restarting ? 'restarting' : (s.phase?.name ?? 'preparing'));
|
|
453
|
-
|
|
454
|
-
// A determinate fraction is only meaningful while downloading with a known
|
|
455
|
-
// Content-Length. Verifying / finalizing / unknown-size downloads render an
|
|
456
|
-
// indeterminate (sliding) bar so nothing ever looks stuck at 0%.
|
|
457
|
-
final bool determinate = !s.hasError &&
|
|
458
|
-
s.phase == PatchApplyPhase.downloading &&
|
|
459
|
-
s.fraction != null;
|
|
460
|
-
final double? frac = s.hasError
|
|
461
|
-
? (s.fraction ?? 0)
|
|
462
|
-
: (determinate ? s.fraction : null);
|
|
463
485
|
|
|
464
486
|
return _panelBox(
|
|
465
487
|
child: Column(
|
|
466
|
-
|
|
488
|
+
key: const Key('ota-progress-panel'),
|
|
489
|
+
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
467
490
|
children: [
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
width: 44,
|
|
481
|
-
child: Text(
|
|
482
|
-
determinate ? _pct(s.fraction) : '',
|
|
483
|
-
textAlign: TextAlign.right,
|
|
484
|
-
style: const TextStyle(
|
|
485
|
-
fontFamily: _mono,
|
|
486
|
-
fontSize: 13,
|
|
487
|
-
color: _C.text,
|
|
488
|
-
fontWeight: FontWeight.w700,
|
|
489
|
-
fontFeatures: [FontFeature.tabularFigures()],
|
|
490
|
-
),
|
|
491
|
+
SizedBox(
|
|
492
|
+
height: 16,
|
|
493
|
+
child: Align(
|
|
494
|
+
alignment: Alignment.centerRight,
|
|
495
|
+
child: Text(
|
|
496
|
+
pctLabel,
|
|
497
|
+
style: const TextStyle(
|
|
498
|
+
fontFamily: _mono,
|
|
499
|
+
fontSize: 13,
|
|
500
|
+
color: _C.text,
|
|
501
|
+
fontWeight: FontWeight.w700,
|
|
502
|
+
fontFeatures: [FontFeature.tabularFigures()],
|
|
491
503
|
),
|
|
492
504
|
),
|
|
493
|
-
|
|
505
|
+
),
|
|
494
506
|
),
|
|
495
|
-
const SizedBox(height:
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
507
|
+
const SizedBox(height: 4),
|
|
508
|
+
SizedBox(
|
|
509
|
+
key: const Key('ota-progress-bar'),
|
|
510
|
+
child: _ProgressBar(
|
|
511
|
+
fraction: frac,
|
|
512
|
+
color: s.hasError ? _C.red : _C.blue,
|
|
513
|
+
),
|
|
502
514
|
),
|
|
503
515
|
],
|
|
504
516
|
),
|
|
@@ -536,7 +548,10 @@ class _OtaProgressOverlayState extends State<OtaProgressOverlay> {
|
|
|
536
548
|
child: Text(
|
|
537
549
|
s.errorHint ?? 'Close the app and reopen to retry.',
|
|
538
550
|
style: const TextStyle(
|
|
539
|
-
|
|
551
|
+
color: _C.dim,
|
|
552
|
+
fontFamily: _mono,
|
|
553
|
+
fontSize: 12,
|
|
554
|
+
),
|
|
540
555
|
),
|
|
541
556
|
),
|
|
542
557
|
if (showRetry)
|
|
@@ -544,25 +559,35 @@ class _OtaProgressOverlayState extends State<OtaProgressOverlay> {
|
|
|
544
559
|
onTap: widget.onRetry,
|
|
545
560
|
child: Container(
|
|
546
561
|
padding: const EdgeInsets.symmetric(
|
|
547
|
-
|
|
562
|
+
horizontal: 14,
|
|
563
|
+
vertical: 7,
|
|
564
|
+
),
|
|
548
565
|
decoration: BoxDecoration(
|
|
549
566
|
borderRadius: BorderRadius.circular(7),
|
|
550
567
|
border: Border.all(color: _C.border),
|
|
551
568
|
),
|
|
552
|
-
child: const Text(
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
569
|
+
child: const Text(
|
|
570
|
+
'[ retry ]',
|
|
571
|
+
style: TextStyle(
|
|
572
|
+
color: _C.blue,
|
|
573
|
+
fontFamily: _mono,
|
|
574
|
+
fontSize: 13,
|
|
575
|
+
),
|
|
576
|
+
),
|
|
557
577
|
),
|
|
558
578
|
),
|
|
559
579
|
],
|
|
560
580
|
)
|
|
561
581
|
: Row(
|
|
562
582
|
children: [
|
|
563
|
-
const Text(
|
|
564
|
-
|
|
565
|
-
|
|
583
|
+
const Text(
|
|
584
|
+
'›',
|
|
585
|
+
style: TextStyle(
|
|
586
|
+
color: _C.green,
|
|
587
|
+
fontFamily: _mono,
|
|
588
|
+
fontSize: 13,
|
|
589
|
+
),
|
|
590
|
+
),
|
|
566
591
|
const SizedBox(width: 8),
|
|
567
592
|
Expanded(
|
|
568
593
|
child: Text(
|
|
@@ -570,7 +595,10 @@ class _OtaProgressOverlayState extends State<OtaProgressOverlay> {
|
|
|
570
595
|
? s.message!
|
|
571
596
|
: 'Applying update, please keep the app open…',
|
|
572
597
|
style: const TextStyle(
|
|
573
|
-
|
|
598
|
+
color: _C.dim,
|
|
599
|
+
fontFamily: _mono,
|
|
600
|
+
fontSize: 12,
|
|
601
|
+
),
|
|
574
602
|
),
|
|
575
603
|
),
|
|
576
604
|
],
|
|
@@ -580,83 +608,68 @@ class _OtaProgressOverlayState extends State<OtaProgressOverlay> {
|
|
|
580
608
|
|
|
581
609
|
// ── helpers ─────────────────────────────────────────────────────────
|
|
582
610
|
Widget _sectionLabel(String t) => Row(
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
)
|
|
611
|
+
children: [
|
|
612
|
+
Text(
|
|
613
|
+
t,
|
|
614
|
+
style: const TextStyle(
|
|
615
|
+
color: _C.dim,
|
|
616
|
+
fontFamily: _mono,
|
|
617
|
+
fontSize: 11,
|
|
618
|
+
fontWeight: FontWeight.w700,
|
|
619
|
+
letterSpacing: 1.5,
|
|
620
|
+
),
|
|
621
|
+
),
|
|
622
|
+
const SizedBox(width: 10),
|
|
623
|
+
const Expanded(child: Divider(color: _C.sep, height: 1)),
|
|
624
|
+
],
|
|
625
|
+
);
|
|
596
626
|
|
|
597
627
|
Widget _panelBox({required Widget child}) => Container(
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
Widget _stat(String k, String v) => Expanded(
|
|
609
|
-
child: Column(
|
|
610
|
-
crossAxisAlignment: CrossAxisAlignment.start,
|
|
611
|
-
children: [
|
|
612
|
-
Text(k,
|
|
613
|
-
style: const TextStyle(
|
|
614
|
-
color: _C.faint, fontFamily: _mono, fontSize: 10)),
|
|
615
|
-
const SizedBox(height: 2),
|
|
616
|
-
Text(v,
|
|
617
|
-
overflow: TextOverflow.ellipsis,
|
|
618
|
-
style: const TextStyle(
|
|
619
|
-
color: _C.text, fontFamily: _mono, fontSize: 12)),
|
|
620
|
-
],
|
|
621
|
-
),
|
|
622
|
-
);
|
|
628
|
+
width: double.infinity,
|
|
629
|
+
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
|
|
630
|
+
decoration: BoxDecoration(
|
|
631
|
+
color: _C.inner,
|
|
632
|
+
borderRadius: BorderRadius.circular(8),
|
|
633
|
+
border: Border.all(color: _C.sep),
|
|
634
|
+
),
|
|
635
|
+
child: child,
|
|
636
|
+
);
|
|
623
637
|
|
|
624
638
|
Widget _dot(Color c) => Container(
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
639
|
+
width: 11,
|
|
640
|
+
height: 11,
|
|
641
|
+
decoration: BoxDecoration(color: c, shape: BoxShape.circle),
|
|
642
|
+
);
|
|
629
643
|
|
|
630
644
|
Widget _pill(String t, Color c) => Container(
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
+
padding: const EdgeInsets.symmetric(horizontal: 9, vertical: 3),
|
|
646
|
+
decoration: BoxDecoration(
|
|
647
|
+
color: c.withValues(alpha: 0.15),
|
|
648
|
+
borderRadius: BorderRadius.circular(6),
|
|
649
|
+
border: Border.all(color: c.withValues(alpha: 0.5)),
|
|
650
|
+
),
|
|
651
|
+
child: Text(
|
|
652
|
+
t,
|
|
653
|
+
style: TextStyle(
|
|
654
|
+
color: c,
|
|
655
|
+
fontFamily: _mono,
|
|
656
|
+
fontSize: 10,
|
|
657
|
+
fontWeight: FontWeight.w700,
|
|
658
|
+
letterSpacing: 0.5,
|
|
659
|
+
),
|
|
660
|
+
),
|
|
661
|
+
);
|
|
645
662
|
}
|
|
646
663
|
|
|
647
|
-
/// A single-line progress bar that never wraps.
|
|
648
|
-
///
|
|
649
|
-
/// - When [fraction] is non-null it's a determinate bar that animates smoothly
|
|
650
|
-
/// between values (no snapping / jumping).
|
|
651
|
-
/// - When [fraction] is null it's an indeterminate sliding bar, so
|
|
652
|
-
/// verify/install/finalize (which have no meaningful percentage) never look
|
|
653
|
-
/// frozen at 0%.
|
|
664
|
+
/// A single-line determinate progress bar that never wraps.
|
|
654
665
|
///
|
|
655
666
|
/// It's a real painted widget with a fixed height inside an [Expanded], so it
|
|
656
667
|
/// always stays on one line regardless of width — unlike the old
|
|
657
|
-
/// character-string bar, which wrapped when it didn't fit.
|
|
668
|
+
/// character-string bar, which wrapped when it didn't fit. It eases toward its
|
|
669
|
+
/// target (forward-only) instead of snapping, and has no indeterminate sliding
|
|
670
|
+
/// mode — once a download completes the bar simply rests at 100%.
|
|
658
671
|
class _ProgressBar extends StatefulWidget {
|
|
659
|
-
final double
|
|
672
|
+
final double fraction;
|
|
660
673
|
final Color color;
|
|
661
674
|
|
|
662
675
|
const _ProgressBar({required this.fraction, required this.color});
|
|
@@ -665,40 +678,22 @@ class _ProgressBar extends StatefulWidget {
|
|
|
665
678
|
State<_ProgressBar> createState() => _ProgressBarState();
|
|
666
679
|
}
|
|
667
680
|
|
|
668
|
-
class _ProgressBarState extends State<_ProgressBar>
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
vsync: this,
|
|
672
|
-
duration: const Duration(milliseconds: 1200),
|
|
673
|
-
);
|
|
674
|
-
|
|
675
|
-
// Smoothed determinate value so the bar eases toward the target instead of
|
|
676
|
-
// snapping. Monotonic — it never visually goes backwards.
|
|
681
|
+
class _ProgressBarState extends State<_ProgressBar> {
|
|
682
|
+
// Smoothed value so the bar eases toward the target. Forward-only — it never
|
|
683
|
+
// visually goes backwards.
|
|
677
684
|
double _shown = 0;
|
|
678
685
|
|
|
679
686
|
@override
|
|
680
687
|
void initState() {
|
|
681
688
|
super.initState();
|
|
682
|
-
_shown = widget.fraction
|
|
683
|
-
if (widget.fraction == null) _indeterminate.repeat();
|
|
689
|
+
_shown = widget.fraction.clamp(0.0, 1.0);
|
|
684
690
|
}
|
|
685
691
|
|
|
686
692
|
@override
|
|
687
693
|
void didUpdateWidget(covariant _ProgressBar old) {
|
|
688
694
|
super.didUpdateWidget(old);
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
} else {
|
|
692
|
-
if (_indeterminate.isAnimating) _indeterminate.stop();
|
|
693
|
-
final target = widget.fraction!.clamp(0.0, 1.0);
|
|
694
|
-
if (target >= _shown) _shown = target; // forward-only
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
@override
|
|
699
|
-
void dispose() {
|
|
700
|
-
_indeterminate.dispose();
|
|
701
|
-
super.dispose();
|
|
695
|
+
final target = widget.fraction.clamp(0.0, 1.0);
|
|
696
|
+
if (target >= _shown) _shown = target;
|
|
702
697
|
}
|
|
703
698
|
|
|
704
699
|
static const _height = 8.0;
|
|
@@ -712,60 +707,24 @@ class _ProgressBarState extends State<_ProgressBar>
|
|
|
712
707
|
height: _height,
|
|
713
708
|
child: ColoredBox(
|
|
714
709
|
color: _track,
|
|
715
|
-
child:
|
|
716
|
-
|
|
717
|
-
:
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
tween: Tween(end: _shown),
|
|
729
|
-
duration: const Duration(milliseconds: 400),
|
|
730
|
-
curve: Curves.easeOut,
|
|
731
|
-
builder: (context, v, _) => Align(
|
|
732
|
-
alignment: Alignment.centerLeft,
|
|
733
|
-
child: Container(
|
|
734
|
-
width: (c.maxWidth * v.clamp(0.0, 1.0)),
|
|
735
|
-
decoration: BoxDecoration(
|
|
736
|
-
color: widget.color,
|
|
737
|
-
borderRadius: BorderRadius.circular(99),
|
|
738
|
-
),
|
|
739
|
-
),
|
|
740
|
-
),
|
|
741
|
-
),
|
|
742
|
-
);
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
Widget _indeterminateBar() {
|
|
746
|
-
return LayoutBuilder(
|
|
747
|
-
builder: (context, c) => AnimatedBuilder(
|
|
748
|
-
animation: _indeterminate,
|
|
749
|
-
builder: (context, _) {
|
|
750
|
-
const segment = 0.35; // 35% wide moving segment
|
|
751
|
-
final t = _indeterminate.value; // 0..1
|
|
752
|
-
final left = (t * (1 + segment)) - segment; // slides in from the left
|
|
753
|
-
return Align(
|
|
754
|
-
alignment: Alignment.centerLeft,
|
|
755
|
-
child: Padding(
|
|
756
|
-
padding: EdgeInsets.only(
|
|
757
|
-
left: (left.clamp(0.0, 1.0)) * c.maxWidth,
|
|
758
|
-
),
|
|
759
|
-
child: Container(
|
|
760
|
-
width: segment * c.maxWidth,
|
|
761
|
-
decoration: BoxDecoration(
|
|
762
|
-
color: widget.color,
|
|
763
|
-
borderRadius: BorderRadius.circular(99),
|
|
710
|
+
child: LayoutBuilder(
|
|
711
|
+
builder: (context, c) => TweenAnimationBuilder<double>(
|
|
712
|
+
tween: Tween(end: _shown),
|
|
713
|
+
duration: const Duration(milliseconds: 300),
|
|
714
|
+
curve: Curves.easeOut,
|
|
715
|
+
builder: (context, v, _) => Align(
|
|
716
|
+
alignment: Alignment.centerLeft,
|
|
717
|
+
child: Container(
|
|
718
|
+
width: c.maxWidth * v.clamp(0.0, 1.0),
|
|
719
|
+
decoration: BoxDecoration(
|
|
720
|
+
color: widget.color,
|
|
721
|
+
borderRadius: BorderRadius.circular(99),
|
|
722
|
+
),
|
|
764
723
|
),
|
|
765
724
|
),
|
|
766
725
|
),
|
|
767
|
-
)
|
|
768
|
-
|
|
726
|
+
),
|
|
727
|
+
),
|
|
769
728
|
),
|
|
770
729
|
);
|
|
771
730
|
}
|
|
@@ -830,19 +789,20 @@ class _TerminalLogState extends State<_TerminalLog> {
|
|
|
830
789
|
style: const TextStyle(fontFamily: _mono, fontSize: 12),
|
|
831
790
|
children: [
|
|
832
791
|
TextSpan(
|
|
833
|
-
|
|
834
|
-
|
|
792
|
+
text: '${match.group(1)!.padRight(8)} ',
|
|
793
|
+
style: TextStyle(color: color),
|
|
794
|
+
),
|
|
835
795
|
TextSpan(
|
|
836
|
-
|
|
837
|
-
|
|
796
|
+
text: match.group(3),
|
|
797
|
+
style: const TextStyle(color: _C.text),
|
|
798
|
+
),
|
|
838
799
|
],
|
|
839
800
|
),
|
|
840
801
|
);
|
|
841
802
|
} else {
|
|
842
803
|
content = Text(
|
|
843
804
|
line.text,
|
|
844
|
-
style: TextStyle(
|
|
845
|
-
fontFamily: _mono, fontSize: 12, color: color),
|
|
805
|
+
style: TextStyle(fontFamily: _mono, fontSize: 12, color: color),
|
|
846
806
|
);
|
|
847
807
|
}
|
|
848
808
|
|
|
@@ -869,9 +829,13 @@ class OtaOverlayManager {
|
|
|
869
829
|
bool _disposed = false;
|
|
870
830
|
DateTime? _startTime;
|
|
871
831
|
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
832
|
+
/// Completes once the active overlay entry has been removed. [begin] resets
|
|
833
|
+
/// it for every session; `FlutterPatcher.applyUpdate` awaits it so the
|
|
834
|
+
/// forced-update restart can never kill a still-visible install screen.
|
|
835
|
+
Completer<void>? _dismissed;
|
|
836
|
+
|
|
837
|
+
Future<void> get _dismissedFuture =>
|
|
838
|
+
_dismissed?.future ?? Future<void>.value();
|
|
875
839
|
|
|
876
840
|
/// Last 10%-bucket we emitted a "download NN%" log line for, to throttle the
|
|
877
841
|
/// feed. -1 = none yet.
|
|
@@ -911,9 +875,8 @@ class OtaOverlayManager {
|
|
|
911
875
|
if (overlay == null) return null;
|
|
912
876
|
|
|
913
877
|
_startTime = DateTime.now();
|
|
914
|
-
_lastBytes = 0;
|
|
915
|
-
_lastBytesAt = null;
|
|
916
878
|
_lastLoggedPctBucket = -1;
|
|
879
|
+
_dismissed = Completer<void>();
|
|
917
880
|
|
|
918
881
|
final logs = <LogLine>[
|
|
919
882
|
if (channel != null) LogLine('gray', 'channel $channel'),
|
|
@@ -984,30 +947,6 @@ class OtaOverlayManager {
|
|
|
984
947
|
final phaseStep = _phaseToStep(progress.phase);
|
|
985
948
|
final newStep = phaseStep < s.activeStep ? s.activeStep : phaseStep;
|
|
986
949
|
|
|
987
|
-
// Compute a smoothed download speed from byte deltas. A single sample can
|
|
988
|
-
// spike wildly (buffered reads), so we blend with the previous value.
|
|
989
|
-
int? bytesPerSec = s.bytesPerSec;
|
|
990
|
-
if (progress.phase == PatchApplyPhase.downloading &&
|
|
991
|
-
progress.bytesReceived > 0) {
|
|
992
|
-
final now = DateTime.now();
|
|
993
|
-
if (_lastBytesAt != null && progress.bytesReceived > _lastBytes) {
|
|
994
|
-
final dt = now.difference(_lastBytesAt!).inMilliseconds / 1000.0;
|
|
995
|
-
if (dt > 0.05) {
|
|
996
|
-
final sample =
|
|
997
|
-
((progress.bytesReceived - _lastBytes) / dt).round();
|
|
998
|
-
// Exponential moving average → the speed/ETA read-out stops jumping.
|
|
999
|
-
bytesPerSec = bytesPerSec == null || bytesPerSec == 0
|
|
1000
|
-
? sample
|
|
1001
|
-
: (bytesPerSec * 0.6 + sample * 0.4).round();
|
|
1002
|
-
_lastBytes = progress.bytesReceived;
|
|
1003
|
-
_lastBytesAt = now;
|
|
1004
|
-
}
|
|
1005
|
-
} else {
|
|
1006
|
-
_lastBytes = progress.bytesReceived;
|
|
1007
|
-
_lastBytesAt = now;
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
950
|
// Log phase transitions once, with staggered detail lines.
|
|
1012
951
|
if (s.phase != progress.phase) {
|
|
1013
952
|
switch (progress.phase) {
|
|
@@ -1048,7 +987,6 @@ class OtaOverlayManager {
|
|
|
1048
987
|
fraction: progress.fraction,
|
|
1049
988
|
bytesReceived: progress.bytesReceived,
|
|
1050
989
|
totalBytes: progress.totalBytes > 0 ? progress.totalBytes : null,
|
|
1051
|
-
bytesPerSec: bytesPerSec,
|
|
1052
990
|
activeStep: newStep,
|
|
1053
991
|
);
|
|
1054
992
|
}
|
|
@@ -1062,7 +1000,12 @@ class OtaOverlayManager {
|
|
|
1062
1000
|
}
|
|
1063
1001
|
|
|
1064
1002
|
void _end({bool hasError = false, String? errorText}) {
|
|
1065
|
-
if (_disposed || _entry == null)
|
|
1003
|
+
if (_disposed || _entry == null) {
|
|
1004
|
+
if (_dismissed != null && !_dismissed!.isCompleted) {
|
|
1005
|
+
_dismissed!.complete();
|
|
1006
|
+
}
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1066
1009
|
if (hasError) {
|
|
1067
1010
|
_log('red', 'error ${errorText ?? 'unknown'}');
|
|
1068
1011
|
_log('yellow', 'action rolling back to base apk');
|
|
@@ -1096,6 +1039,9 @@ class OtaOverlayManager {
|
|
|
1096
1039
|
if (_disposed) return;
|
|
1097
1040
|
entry?.remove();
|
|
1098
1041
|
if (identical(_entry, entry)) _entry = null;
|
|
1042
|
+
if (_dismissed != null && !_dismissed!.isCompleted) {
|
|
1043
|
+
_dismissed!.complete();
|
|
1044
|
+
}
|
|
1099
1045
|
});
|
|
1100
1046
|
}
|
|
1101
1047
|
|
|
@@ -1150,10 +1096,7 @@ class OtaOverlayManager {
|
|
|
1150
1096
|
),
|
|
1151
1097
|
Text(
|
|
1152
1098
|
'Restored: $previousVersion',
|
|
1153
|
-
style: TextStyle(
|
|
1154
|
-
color: Colors.grey[400],
|
|
1155
|
-
fontSize: 12,
|
|
1156
|
-
),
|
|
1099
|
+
style: TextStyle(color: Colors.grey[400], fontSize: 12),
|
|
1157
1100
|
),
|
|
1158
1101
|
],
|
|
1159
1102
|
),
|
|
@@ -1183,4 +1126,8 @@ class OtaOverlayHandle {
|
|
|
1183
1126
|
|
|
1184
1127
|
void end({bool hasError = false, String? errorText}) =>
|
|
1185
1128
|
_manager._end(hasError: hasError, errorText: errorText);
|
|
1129
|
+
|
|
1130
|
+
/// Completes when the overlay has fully unmounted (dwell elapsed + removed),
|
|
1131
|
+
/// or immediately if no overlay session is active.
|
|
1132
|
+
Future<void> get dismissed => _manager._dismissedFuture;
|
|
1186
1133
|
}
|