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
23 changes: 21 additions & 2 deletions open_wearable/lib/models/fota_post_update_verification.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ class ArmedFotaPostUpdateVerification {
final String verificationId;
final String wearableName;
final String? sideLabel;
final DateTime deadline;

const ArmedFotaPostUpdateVerification({
required this.verificationId,
required this.wearableName,
this.sideLabel,
required this.deadline,
});
}

Expand Down Expand Up @@ -81,11 +83,17 @@ class FotaPostUpdateVerificationCoordinator {
bool isVerificationPending(String verificationId) =>
_pendingById.containsKey(verificationId);

void cancel(String verificationId) {
_verificationTimers.remove(verificationId)?.cancel();
if (_pendingById.remove(verificationId) != null) _publishPendingIds();
}

Future<ArmedFotaPostUpdateVerification?> armFromUpdateRequest({
required FirmwareUpdateRequest request,
Wearable? selectedWearable,
String? preResolvedWearableName,
String? preResolvedSideLabel,
Wearable? connectionBeforeReset,
}) async {
_cleanupExpired();

Expand Down Expand Up @@ -128,6 +136,7 @@ class FotaPostUpdateVerificationCoordinator {
expectedSideLabel: expectedSideLabel,
expectedFirmwareVersion: expectedFirmwareVersion,
armedAt: DateTime.now(),
connectionBeforeReset: connectionBeforeReset,
);
_pendingById[verificationId] = pending;
_verificationTimers[verificationId] = Timer(_maxPendingAge, () {
Expand All @@ -139,6 +148,7 @@ class FotaPostUpdateVerificationCoordinator {
verificationId: verificationId,
wearableName: displayName ?? 'OpenEarable',
sideLabel: expectedSideLabel,
deadline: pending.armedAt.add(_maxPendingAge),
);
}

Expand All @@ -163,7 +173,12 @@ class FotaPostUpdateVerificationCoordinator {
connectedSideLabel: connectedSideLabel,
);

if (pending == null || !_verifyingIds.add(pending.verificationId)) {
// The app also checks its existing connections when verification is armed.
// A Wearable belongs to one connection; only a new connection can verify
// the reboot, including when reinstalling the same firmware version.
if (pending == null ||
identical(pending.connectionBeforeReset, wearable) ||
!_verifyingIds.add(pending.verificationId)) {
return null;
}
try {
Expand All @@ -186,7 +201,9 @@ class FotaPostUpdateVerificationCoordinator {
expectedFirmwareVersion != null &&
detectedFirmwareVersion != null &&
_firmwareVersionsMatch(
expectedFirmwareVersion, detectedFirmwareVersion,);
expectedFirmwareVersion,
detectedFirmwareVersion,
);
final displayName = pending.displayWearableName ?? 'OpenEarable';
final result = FotaPostUpdateVerificationResult(
verificationId: pending.verificationId,
Expand Down Expand Up @@ -582,6 +599,7 @@ class _PendingPostUpdateVerification {
final String? expectedSideLabel;
final String? expectedFirmwareVersion;
final DateTime armedAt;
final Wearable? connectionBeforeReset;

const _PendingPostUpdateVerification({
required this.verificationId,
Expand All @@ -591,5 +609,6 @@ class _PendingPostUpdateVerification {
required this.expectedSideLabel,
required this.expectedFirmwareVersion,
required this.armedAt,
this.connectionBeforeReset,
});
}
58 changes: 31 additions & 27 deletions open_wearable/lib/widgets/fota/fota_verification_banner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ class FotaVerificationBanner extends StatefulWidget {
final String wearableName;
final String? sideLabel;
final VoidCallback onDismiss;
final bool showUploadCompleted;

const FotaVerificationBanner({
super.key,
required this.deadline,
required this.wearableName,
this.sideLabel,
required this.onDismiss,
this.showUploadCompleted = true,
});

@override
Expand Down Expand Up @@ -93,33 +95,34 @@ class _FotaVerificationBannerState extends State<FotaVerificationBanner> {
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
AppBanner(
backgroundColor: successBackground,
foregroundColor: successForeground,
leadingIcon: Icons.verified_rounded,
content: Text.rich(
TextSpan(
style: successTextStyle,
children: [
const TextSpan(
text: 'Firmware upload completed successfully for ',
),
TextSpan(text: widget.wearableName),
if (widget.sideLabel != null) const TextSpan(text: ' '),
if (widget.sideLabel != null)
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: _FotaSideBadge(
sideLabel: widget.sideLabel!,
accentColor: successForeground,
),
if (widget.showUploadCompleted)
AppBanner(
backgroundColor: successBackground,
foregroundColor: successForeground,
leadingIcon: Icons.verified_rounded,
content: Text.rich(
TextSpan(
style: successTextStyle,
children: [
const TextSpan(
text: 'Firmware upload completed successfully for ',
),
const TextSpan(text: '.'),
],
TextSpan(text: widget.wearableName),
if (widget.sideLabel != null) const TextSpan(text: ' '),
if (widget.sideLabel != null)
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: _FotaSideBadge(
sideLabel: widget.sideLabel!,
accentColor: successForeground,
),
),
const TextSpan(text: '.'),
],
),
),
),
),
const SizedBox(height: 8),
if (widget.showUploadCompleted) const SizedBox(height: 8),
AppBanner(
backgroundColor: warningBackground,
foregroundColor: warningForeground,
Expand Down Expand Up @@ -186,6 +189,7 @@ void showFotaVerificationBanner(
required String wearableName,
String? sideLabel,
Duration duration = const Duration(minutes: 3),
DateTime? deadline,
}) {
final controller = Provider.of<AppBannerController>(context, listen: false);
_pruneMissingFotaVerificationBannerKeys(controller);
Expand All @@ -200,9 +204,9 @@ void showFotaVerificationBanner(
}
}
_activeFotaVerificationBannerKeys.remove(verificationId);
final deadline = _fotaVerificationDeadlinesById.putIfAbsent(
final verificationDeadline = _fotaVerificationDeadlinesById.putIfAbsent(
verificationId,
() => DateTime.now().add(duration),
() => deadline ?? DateTime.now().add(duration),
);

controller.showBanner(
Expand All @@ -214,7 +218,7 @@ void showFotaVerificationBanner(
key: bannerKey,
content: FotaVerificationBanner(
key: ValueKey('fota_verification_$verificationId'),
deadline: deadline,
deadline: verificationDeadline,
wearableName: wearableName,
sideLabel: sideLabel,
onDismiss: () => _dismissFotaVerificationBanner(
Expand Down
Loading
Loading