fix(settings): guard Atmos diagnostics stop

This commit is contained in:
edde746
2026-07-12 08:42:22 +02:00
parent 5f8d263d63
commit f4ec6ccf20
2 changed files with 94 additions and 4 deletions
@@ -29,6 +29,7 @@ class _AtmosDiagnosticsScreenState extends State<AtmosDiagnosticsScreen> {
Timer? _poll;
Map<Object?, Object?> _status = const {};
String? _activeMode;
bool _stopping = false;
@override
void initState() {
@@ -40,7 +41,7 @@ class _AtmosDiagnosticsScreenState extends State<AtmosDiagnosticsScreen> {
@override
void dispose() {
_poll?.cancel();
_channel.invokeMethod('stop').ignore();
if (!_stopping) _channel.invokeMethod('stop').ignore();
super.dispose();
}
@@ -70,9 +71,18 @@ class _AtmosDiagnosticsScreenState extends State<AtmosDiagnosticsScreen> {
}
Future<void> _stop() async {
await _channel.invokeMethod('stop');
setState(() => _activeMode = null);
await _refreshStatus();
if (_stopping) return;
_stopping = true;
try {
await _channel.invokeMethod('stop');
if (!mounted) return;
setState(() => _activeMode = null);
await _refreshStatus();
} on PlatformException catch (e) {
if (mounted) showErrorSnackBar(context, e.message ?? e.code);
} finally {
_stopping = false;
}
}
Widget _testTile({required String mode, required IconData icon, required String title, required String subtitle}) {
@@ -0,0 +1,80 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:plezy/i18n/strings.g.dart';
import 'package:plezy/screens/settings/atmos_diagnostics_screen.dart';
import 'package:plezy/services/settings_service.dart';
import 'package:plezy/theme/mono_theme.dart';
import '../../test_helpers/prefs.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
const channel = MethodChannel('plezy/atmos_probe');
final messenger = TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger;
setUp(() async {
resetSharedPreferencesForTest();
SettingsService.resetForTesting();
await SettingsService.getInstance();
LocaleSettings.setLocaleSync(AppLocale.en);
});
tearDown(() {
messenger.setMockMethodCallHandler(channel, null);
});
Future<void> pumpScreen(WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(theme: monoTheme(dark: true), home: const AtmosDiagnosticsScreen()));
await tester.pump();
await tester.ensureVisible(find.text(t.settings.atmosTestStop));
}
testWidgets('unmounting during stop does not set state or stop twice', (tester) async {
final stopCompleter = Completer<void>();
var stopCalls = 0;
messenger.setMockMethodCallHandler(channel, (call) async {
if (call.method == 'stop') {
stopCalls++;
await stopCompleter.future;
}
return call.method == 'getStatus' ? <Object?, Object?>{} : null;
});
await pumpScreen(tester);
await tester.tap(find.text(t.settings.atmosTestStop));
await tester.pump();
expect(stopCalls, 1);
await tester.pumpWidget(const MaterialApp(home: SizedBox()));
expect(stopCalls, 1);
stopCompleter.complete();
await tester.pump();
expect(tester.takeException(), isNull);
expect(stopCalls, 1);
});
testWidgets('native stop failure is shown without escaping', (tester) async {
var stopCalls = 0;
messenger.setMockMethodCallHandler(channel, (call) async {
if (call.method == 'stop') {
stopCalls++;
throw PlatformException(code: 'stop_failed', message: 'Native stop failed');
}
return call.method == 'getStatus' ? <Object?, Object?>{} : null;
});
await pumpScreen(tester);
await tester.tap(find.text(t.settings.atmosTestStop));
await tester.pump();
expect(stopCalls, 1);
expect(find.text('Native stop failed'), findsOneWidget);
expect(tester.takeException(), isNull);
});
}