Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion open_wearable/lib/models/fota_post_update_verification.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,19 @@ class FotaPostUpdateVerificationCoordinator {
static const Duration _maxPendingAge = Duration(minutes: 20);

final Map<String, _PendingPostUpdateVerification> _pendingById = {};
final StreamController<Set<String>> _pendingIdsController =
StreamController<Set<String>>.broadcast();
int _nextVerificationId = 0;

/// Emits the current set of active verification ids whenever it changes.
Stream<Set<String>> get pendingVerificationIds =>
_pendingIdsController.stream;

/// Returns whether the given verification is still awaiting reconnect
/// confirmation.
bool isVerificationPending(String verificationId) =>
_pendingById.containsKey(verificationId);

Future<ArmedFotaPostUpdateVerification?> armFromUpdateRequest({
required FirmwareUpdateRequest request,
Wearable? selectedWearable,
Expand Down Expand Up @@ -111,6 +122,7 @@ class FotaPostUpdateVerificationCoordinator {
expectedFirmwareVersion: expectedFirmwareVersion,
armedAt: DateTime.now(),
);
_publishPendingIds();

return ArmedFotaPostUpdateVerification(
verificationId: verificationId,
Expand Down Expand Up @@ -156,6 +168,7 @@ class FotaPostUpdateVerificationCoordinator {
);

_pendingById.remove(pending.verificationId);
_publishPendingIds();

final displayName = pending.displayWearableName ??
_displayName(wearable.name) ??
Expand Down Expand Up @@ -380,9 +393,19 @@ class FotaPostUpdateVerificationCoordinator {
}

final now = DateTime.now();
final removed = <String>[];
_pendingById.removeWhere(
(_, pending) => now.difference(pending.armedAt) > _maxPendingAge,
(_, pending) {
final isExpired = now.difference(pending.armedAt) > _maxPendingAge;
if (isExpired) {
removed.add(pending.verificationId);
}
return isExpired;
},
);
if (removed.isNotEmpty) {
_publishPendingIds();
}
}

void _removeConflictingPending({
Expand All @@ -394,9 +417,11 @@ class FotaPostUpdateVerificationCoordinator {
return;
}

final removed = <String>[];
_pendingById.removeWhere((_, pending) {
if (expectedDeviceId != null &&
pending.expectedDeviceId == expectedDeviceId) {
removed.add(pending.verificationId);
return true;
}

Expand All @@ -406,11 +431,21 @@ class FotaPostUpdateVerificationCoordinator {
expectedName != null &&
sameName &&
sameSide) {
removed.add(pending.verificationId);
return true;
}

return false;
});
if (removed.isNotEmpty) {
_publishPendingIds();
}
}

/// Publishes a defensive copy so listeners can react to verification
/// completion without mutating coordinator state.
void _publishPendingIds() {
_pendingIdsController.add(Set<String>.unmodifiable(_pendingById.keys));
}

String? _extractExpectedFirmwareVersion(SelectedFirmware? firmware) {
Expand Down
16 changes: 14 additions & 2 deletions open_wearable/lib/widgets/fota/fota_verification_banner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -235,11 +235,23 @@ void dismissFotaVerificationBannerById(
_pruneMissingFotaVerificationBannerKeys(controller);

final key = _activeFotaVerificationBannerKeys.remove(verificationId);
if (key == null) {
if (key != null) {
controller.hideBannerByKey(key);
_fotaVerificationDeadlinesById.remove(verificationId);
return;
}

controller.hideBannerByKey(key);
final fallbackPrefix = 'fota_verification_banner_${verificationId}_';
final fallbackKey = controller.activeBanners
.map((banner) => banner.key)
.whereType<ValueKey<String>>()
.firstWhere(
(candidate) => candidate.value.startsWith(fallbackPrefix),
orElse: () => const ValueKey<String>(''),
);
if (fallbackKey.value.isNotEmpty) {
controller.hideBannerByKey(fallbackKey);
}
_fotaVerificationDeadlinesById.remove(verificationId);
}

Expand Down
25 changes: 24 additions & 1 deletion open_wearable/lib/widgets/fota/stepper_view/update_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,10 @@ class _UpdateStepViewState extends State<UpdateStepView> {
bool _lastReportedRunning = false;
bool _startRequested = false;
bool _verificationBannerShown = false;
bool _isVerificationPending = false;
bool _loopWarningHandled = false;
String? _lastResetValidateStage;
StreamSubscription<Set<String>>? _verificationPendingSubscription;
int _resetValidateLoopTransitions = 0;

@override
Expand All @@ -63,6 +65,7 @@ class _UpdateStepViewState extends State<UpdateStepView> {

@override
void dispose() {
_verificationPendingSubscription?.cancel();
if (_lastReportedRunning) {
widget.onUpdateRunningChanged?.call(false);
}
Expand Down Expand Up @@ -114,6 +117,7 @@ class _UpdateStepViewState extends State<UpdateStepView> {
if (!mounted || armedVerification == null) {
return;
}
_bindVerificationLifecycle(armedVerification.verificationId);
showFotaVerificationBanner(
this.context,
verificationId: armedVerification.verificationId,
Expand Down Expand Up @@ -142,6 +146,25 @@ class _UpdateStepViewState extends State<UpdateStepView> {
return true;
}

/// Subscribes the page-level success panel to the coordinator entry created
/// for this update so it disappears immediately after reconnect validation.
void _bindVerificationLifecycle(String verificationId) {
_verificationPendingSubscription?.cancel();
_isVerificationPending = FotaPostUpdateVerificationCoordinator.instance
.isVerificationPending(verificationId);
_verificationPendingSubscription = FotaPostUpdateVerificationCoordinator
.instance.pendingVerificationIds
.listen((pendingIds) {
final isPending = pendingIds.contains(verificationId);
if (!mounted || _isVerificationPending == isPending) {
return;
}
setState(() {
_isVerificationPending = isPending;
});
});
}

/// Shows a one-time warning when the update appears to restart image uploads
/// more often than the selected firmware package should require.
Future<void> _maybeShowLoopWarning({
Expand Down Expand Up @@ -417,7 +440,7 @@ class _UpdateStepViewState extends State<UpdateStepView> {
_abortButton(context),
const SizedBox(height: 10),
],
if (showSuccessMessage) ...[
if (showSuccessMessage && _isVerificationPending) ...[
_successPanel(context),
const SizedBox(height: 10),
],
Expand Down
16 changes: 8 additions & 8 deletions open_wearable/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,10 @@ packages:
dependency: transitive
description:
name: characters
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
url: "https://pub.dev"
source: hosted
version: "1.4.0"
version: "1.4.1"
clock:
dependency: transitive
description:
Expand Down Expand Up @@ -500,18 +500,18 @@ packages:
dependency: transitive
description:
name: matcher
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
url: "https://pub.dev"
source: hosted
version: "0.12.17"
version: "0.12.19"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
url: "https://pub.dev"
source: hosted
version: "0.11.1"
version: "0.13.0"
mcumgr_flutter:
dependency: "direct main"
description:
Expand Down Expand Up @@ -945,10 +945,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
url: "https://pub.dev"
source: hosted
version: "0.7.7"
version: "0.7.10"
tuple:
dependency: transitive
description:
Expand Down
Loading