diff --git a/open_wearable/lib/widgets/fota/stepper_view/update_view.dart b/open_wearable/lib/widgets/fota/stepper_view/update_view.dart index bd9d3bbf3..fdd46a1f3 100644 --- a/open_wearable/lib/widgets/fota/stepper_view/update_view.dart +++ b/open_wearable/lib/widgets/fota/stepper_view/update_view.dart @@ -134,6 +134,12 @@ class _UpdateStepViewState extends State { _verificationResult != null && (_isVerificationReset(state) || _isNativeSuccess(state)); + bool _canAbortUpdate(UpdateState state) => + state is UpdateFirmwareStateHistory && + !state.isComplete && + !_isVerificationReset(state) && + !_isVerificationPending; + bool _isUpdateInProgress(UpdateState state) { if (state is UpdateInitial || _hasVerificationResult(state)) return false; if (state is UpdateFirmwareStateHistory) { @@ -397,7 +403,8 @@ class _UpdateStepViewState extends State { ), ); - if (!mounted || shouldAbort != true) { + // The earphone may have started rebooting while confirmation was open. + if (!mounted || shouldAbort != true || !_canAbortUpdate(updateBloc.state)) { return; } @@ -510,14 +517,14 @@ class _UpdateStepViewState extends State { _currentStatePanel(context, state), const SizedBox(height: 10), ], - if (!state.isComplete && !verificationFinished) ...[ - _abortButton(context), - const SizedBox(height: 10), - ], if (_isVerificationPending) ...[ _successPanel(context), const SizedBox(height: 10), ], + if (_canAbortUpdate(state)) ...[ + _abortButton(context), + const SizedBox(height: 10), + ], if (verificationFinished) ...[ _completedStep( context, diff --git a/open_wearable/test/models/fota_post_update_verification_test.dart b/open_wearable/test/models/fota_post_update_verification_test.dart index 2c115528a..08308d5dd 100644 --- a/open_wearable/test/models/fota_post_update_verification_test.dart +++ b/open_wearable/test/models/fota_post_update_verification_test.dart @@ -293,6 +293,15 @@ void main() { expect(armedIds, isNull, reason: 'Recovery resets before upload must not verify',); expect(find.textContaining('Verification in progress'), findsNothing); + expect(find.text('Abort Update'), findsOneWidget); + if (outcome == 'verified') { + bloc.show(UpdateFirmwareStateHistory(UpdateFirmware('Test'), [ + UpdateProgressFirmware('Upload', 100, 1), + ]),); + await tester.pump(); + await tester.tap(find.text('Abort Update')); + await tester.pump(const Duration(milliseconds: 300)); + } bloc.show( UpdateFirmwareStateHistory(UpdateFirmware('Reset'), [ UpdateProgressFirmware('Upload', 100, 1), @@ -301,6 +310,11 @@ void main() { ); await tester.pump(); await tester.pump(); + if (outcome == 'verified') { + // A confirmation opened before reboot must no longer abort it. + await tester.tap(find.text('Abort Update').last); + await tester.pump(const Duration(milliseconds: 300)); + } expect( armedIds, isNotNull, @@ -309,6 +323,7 @@ void main() { final id = armedIds!.single; expect(find.text('Reset and verify'), findsOneWidget); expect(find.textContaining('Verification in progress'), findsOneWidget); + expect(find.text('Abort Update'), findsNothing); expect(find.textContaining(RegExp(r'0[23]:[0-5][0-9]')), findsOneWidget); expect(banners.activeBanners, hasLength(1)); expect(running.last, isTrue); @@ -439,6 +454,7 @@ void main() { ); await tester.pump(); expect(find.text('Upload firmware'), findsNothing); + expect(find.text('Abort Update'), findsOneWidget); expect( find.text( 'Uploading ${image == 0 ? 'application' : 'network'} core ${50 + image}%',