import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:iperf3gui/data/services/iperf3_service.dart'; import 'package:iperf3gui/domain/models/iperf3_result.dart'; import 'package:iperf3gui/domain/models/test_configuration.dart'; /// State of the test run. enum TestRunState { idle, running, completed, error } /// ViewModel for managing test run state. /// /// Subscribes to [Iperf3Service.outputLines] so the transcript grows while the /// test runs rather than appearing all at once at the end. class TestRunViewModel extends ChangeNotifier { final Iperf3Service iperf3Service; TestRunViewModel({required this.iperf3Service}) { _outputSubscription = iperf3Service.outputLines.listen(_onOutputLine); } StreamSubscription? _outputSubscription; TestRunState _state = TestRunState.idle; TestRunState get state => _state; Iperf3Result? _result; Iperf3Result? get result => _result; TestConfiguration? _currentConfig; TestConfiguration? get currentConfig => _currentConfig; final StringBuffer _liveOutput = StringBuffer(); /// Output accumulated so far. Grows line by line during a run, and holds the /// finished transcript once the test completes. String get output => _liveOutput.toString(); /// Whether a test is currently in flight. bool get isRunning => _state == TestRunState.running; /// Whether the most recent finished run ended because the user stopped it. bool get wasStopped => _result?.stopped ?? false; /// Start a test with the given configuration. Future runTest(TestConfiguration config) async { if (_state == TestRunState.running) return; if (!config.isValid) { _liveOutput ..clear() ..writeln('Invalid configuration: check the server address and port.'); _state = TestRunState.error; _result = null; notifyListeners(); return; } _state = TestRunState.running; _currentConfig = config; _result = null; _liveOutput.clear(); notifyListeners(); try { final result = await iperf3Service.run(config); _result = result; // Prefer the streamed transcript — it is what the user watched build up. // Fall back to the service's copy if nothing streamed through. if (_liveOutput.isEmpty && result.rawOutput.isNotEmpty) { _liveOutput.write(result.rawOutput); } _state = result.exitCode == 0 ? TestRunState.completed : TestRunState.error; } catch (e) { _liveOutput.writeln('Test failed: $e'); _state = TestRunState.error; } finally { notifyListeners(); } } /// Request that a running test stop early. Future stopTest() async { if (_state != TestRunState.running) return; await iperf3Service.stop(); } /// Reset to idle state. void reset() { _state = TestRunState.idle; _result = null; _currentConfig = null; _liveOutput.clear(); notifyListeners(); } void _onOutputLine(String line) { _liveOutput.write(line.endsWith('\n') ? line : '$line\n'); notifyListeners(); } @override void dispose() { _outputSubscription?.cancel(); iperf3Service.dispose(); super.dispose(); } }