diff --git a/analysis_options.yaml b/analysis_options.yaml index cedcc10..d5d1f82 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,15 +1,14 @@ -# This file configures the analyzer, which statically analyzes Dart code to -# check for errors, warnings, and lints. -# -# The issues identified by the analyzer are surfaced in the UI of Dart-enabled -# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be -# invoked from the command line by running `flutter analyze`. - -# The following line activates a set of recommended lints for Flutter apps, -# packages, and plugins designed to encourage good coding practices. include: package:flutter_lints/flutter.yaml analyzer: + language: + strict-casts: true + strict-inference: true + strict-raw-types: true + errors: + unused_element: warning + unused_local_variable: warning + dead_code: warning exclude: - build/** - android/** @@ -20,19 +19,9 @@ analyzer: - linux/** linter: - # The lint rules applied to this project can be customized in the - # section below to disable rules from the `package:flutter_lints/flutter.yaml` - # included above or to enable additional rules. A list of all available lints - # and their documentation is published at https://dart.dev/lints. - # - # Instead of disabling a lint rule for the entire project in the - # section below, it can also be suppressed for a single line of code - # or a specific dart file by using the `// ignore: name_of_lint` and - # `// ignore_for_file: name_of_lint` syntax on the line or in the file - # producing the lint. rules: - # avoid_print: false # Uncomment to disable the `avoid_print` rule - # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options + use_super_parameters: true + prefer_const_constructors: true + avoid_print: true + prefer_single_quotes: true + sort_pub_dependencies: true diff --git a/lib/data/services/iperf3_binary_path_provider.dart b/lib/data/services/iperf3_binary_path_provider.dart new file mode 100644 index 0000000..9bb9baf --- /dev/null +++ b/lib/data/services/iperf3_binary_path_provider.dart @@ -0,0 +1,55 @@ +import 'dart:io'; + +import 'package:flutter/services.dart'; +import 'package:path_provider/path_provider.dart'; + +/// Provides the path to the iperf3 binary on the device. +/// +/// Call [initialize] in [main] (before [runApp]) to extract the +/// bundled asset and populate the cache. After initialization, +/// [path] returns a synchronous [String]. +class Iperf3BinaryPathProvider { + static String? _cachedPath; + + /// Extract the bundled iperf3 binary to the app's cache directory. + /// + /// Call this once in [main] before [runApp]. + static Future initialize() async { + if (_cachedPath != null) return; + + final cacheDir = await getApplicationCacheDirectory(); + final binaryDir = Directory('${cacheDir.path}/iperf3'); + if (!await binaryDir.exists()) { + await binaryDir.create(recursive: true); + } + + final abi = await _detectAbi(); + final assetPath = 'assets/iperf3/$abi/iperf3'; + + final bytes = await rootBundle.load(assetPath); + final file = File('${binaryDir.path}/iperf3'); + await file.writeAsBytes(bytes.buffer.asUint8List()); + + // chmod +x the binary + await Process.run('chmod', ['+x', file.path]); + + _cachedPath = file.path; + } + + /// The resolved path to the iperf3 binary. + /// + /// Must call [initialize] first, or this will return a placeholder. + static String get path { + return _cachedPath ?? '/data/local/tmp/iperf3'; + } + + static Future _detectAbi() async { + final result = await Process.run( + 'getprop', + ['ro.product.cpu.abi'], + ); + final abi = result.stdout.toString().trim(); + if (abi.contains('arm64')) return 'arm64-v8a'; + return 'armeabi-v7a'; + } +} diff --git a/lib/data/services/iperf3_service.dart b/lib/data/services/iperf3_service.dart new file mode 100644 index 0000000..6f6f54a --- /dev/null +++ b/lib/data/services/iperf3_service.dart @@ -0,0 +1,47 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:iperf3gui/domain/models/iperf3_result.dart'; +import 'package:iperf3gui/domain/models/test_configuration.dart'; + +/// Service that runs iperf3 via [Process.spawn] and captures output. +class Iperf3Service { + final String _binaryPath; + + // ignore: prefer_initializing_formals + Iperf3Service({required String binaryPath}) : _binaryPath = binaryPath; + + /// Run iperf3 with the given configuration. + /// + /// Returns an [Iperf3Result] with stdout/stderr and exit code. + Future run(TestConfiguration config) async { + final args = config.toCliArgs(); + + final process = await Process.start( + _binaryPath, + args, + runInShell: true, + ); + + final stdoutBuffer = []; + final stderrBuffer = []; + + final stdoutStream = process.stdout.transform(utf8.decoder).listen( + (data) => stdoutBuffer.addAll(utf8.encode(data)), + ); + final stderrStream = process.stderr.transform(utf8.decoder).listen( + (data) => stderrBuffer.addAll(utf8.encode(data)), + ); + + await process.exitCode; + await stdoutStream.cancel(); + await stderrStream.cancel(); + + final rawOutput = utf8.decode(stdoutBuffer) + utf8.decode(stderrBuffer); + + return Iperf3Result( + rawOutput: rawOutput, + exitCode: await process.exitCode, + ); + } +} diff --git a/lib/main.dart b/lib/main.dart index 244a702..3220f39 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,122 +1,25 @@ import 'package:flutter/material.dart'; +import 'package:iperf3gui/data/services/iperf3_binary_path_provider.dart'; +import 'package:iperf3gui/ui/features/test_run/test_run_screen.dart'; -void main() { - runApp(const MyApp()); +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + await Iperf3BinaryPathProvider.initialize(); + runApp(const Iperf3GuiApp()); } -class MyApp extends StatelessWidget { - const MyApp({super.key}); +class Iperf3GuiApp extends StatelessWidget { + const Iperf3GuiApp({super.key}); - // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( - title: 'Flutter Demo', + title: 'iperf3-gui', theme: ThemeData( - // This is the theme of your application. - // - // TRY THIS: Try running your application with "flutter run". You'll see - // the application has a purple toolbar. Then, without quitting the app, - // try changing the seedColor in the colorScheme below to Colors.green - // and then invoke "hot reload" (save your changes or press the "hot - // reload" button in a Flutter-supported IDE, or press "r" if you used - // the command line to start the app). - // - // Notice that the counter didn't reset back to zero; the application - // state is not lost during the reload. To reset the state, use hot - // restart instead. - // - // This works for code too, not just values: Most code changes can be - // tested with just a hot reload. - colorScheme: .fromSeed(seedColor: Colors.deepPurple), - ), - home: const MyHomePage(title: 'Flutter Demo Home Page'), - ); - } -} - -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key, required this.title}); - - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. - - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". - - final String title; - - @override - State createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - int _counter = 0; - - void _incrementCounter() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; - }); - } - - @override - Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. - return Scaffold( - appBar: AppBar( - // TRY THIS: Try changing the color here to a specific color (to - // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar - // change color while the other colors stay the same. - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), - body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. - child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - // - // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint" - // action in the IDE, or press "p" in the console), to see the - // wireframe for each widget. - mainAxisAlignment: .center, - children: [ - const Text('You have pushed the button this many times:'), - Text( - '$_counter', - style: Theme.of(context).textTheme.headlineMedium, - ), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: const Icon(Icons.add), + colorSchemeSeed: Colors.blue, + useMaterial3: true, ), + home: const TestRunScreen(), ); } } diff --git a/lib/ui/features/test_run/test_run_screen.dart b/lib/ui/features/test_run/test_run_screen.dart new file mode 100644 index 0000000..8dcf715 --- /dev/null +++ b/lib/ui/features/test_run/test_run_screen.dart @@ -0,0 +1,142 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:iperf3gui/data/services/iperf3_service.dart'; +import 'package:iperf3gui/data/services/iperf3_binary_path_provider.dart'; +import 'package:iperf3gui/domain/models/test_configuration.dart'; +import 'package:iperf3gui/ui/features/test_run/test_run_view_model.dart'; + +class TestRunScreen extends StatelessWidget { + const TestRunScreen({super.key}); + + @override + Widget build(BuildContext context) { + return ChangeNotifierProvider( + create: (_) => TestRunViewModel( + iperf3Service: Iperf3Service( + binaryPath: Iperf3BinaryPathProvider.path, + ), + ), + child: const _TestRunContent(), + ); + } +} + +class _TestRunContent extends StatefulWidget { + const _TestRunContent(); + + @override + State<_TestRunContent> createState() => _TestRunContentState(); +} + +class _TestRunContentState extends State<_TestRunContent> { + final _serverController = TextEditingController(text: 'iperf.es.net'); + final _portController = TextEditingController(text: '5201'); + + @override + void dispose() { + _serverController.dispose(); + _portController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('iperf3-gui'), + ), + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // Server input + TextField( + controller: _serverController, + decoration: const InputDecoration( + labelText: 'Server', + hintText: 'e.g. iperf.es.net or 192.168.1.1', + ), + ), + const SizedBox(height: 16), + + // Port input + TextField( + controller: _portController, + decoration: const InputDecoration( + labelText: 'Port', + hintText: '5201', + ), + keyboardType: TextInputType.number, + ), + const SizedBox(height: 16), + + // Go button + Consumer( + builder: (context, vm, _) { + return ElevatedButton( + onPressed: vm.state == TestRunState.running + ? null + : () { + final config = TestConfiguration( + server: _serverController.text, + port: int.tryParse(_portController.text) ?? 5201, + ); + vm.runTest(config); + }, + child: vm.state == TestRunState.running + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : const Text('GO'), + ); + }, + ), + const SizedBox(height: 16), + + // Status indicator + Consumer( + builder: (context, vm, _) { + final statusText = switch (vm.state) { + TestRunState.idle => '', + TestRunState.running => 'Running...', + TestRunState.completed => 'Test complete', + TestRunState.error => 'Error', + }; + return Text( + statusText, + style: TextStyle( + color: vm.state == TestRunState.error + ? Colors.red + : Colors.grey, + ), + ); + }, + ), + const SizedBox(height: 16), + + // Raw output text box + Expanded( + child: Consumer( + builder: (context, vm, _) { + final output = vm.result?.rawOutput ?? ''; + return TextField( + controller: TextEditingController(text: output), + decoration: const InputDecoration( + labelText: 'Raw Output', + border: OutlineInputBorder(), + ), + maxLines: null, + readOnly: true, + ); + }, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/ui/features/test_run/test_run_view_model.dart b/lib/ui/features/test_run/test_run_view_model.dart index 402bd61..04f2aa0 100644 --- a/lib/ui/features/test_run/test_run_view_model.dart +++ b/lib/ui/features/test_run/test_run_view_model.dart @@ -8,7 +8,7 @@ enum TestRunState { idle, running, completed, error } /// ViewModel for managing test run state. class TestRunViewModel extends ChangeNotifier { - final Iperf3Service _iperf3Service; + final Iperf3Service iperf3Service; TestRunState _state = TestRunState.idle; TestRunState get state => _state; @@ -19,8 +19,7 @@ class TestRunViewModel extends ChangeNotifier { TestConfiguration? _currentConfig; TestConfiguration? get currentConfig => _currentConfig; - TestRunViewModel({required Iperf3Service iperf3Service}) - : _iperf3Service = iperf3Service; + TestRunViewModel({required this.iperf3Service}); /// Start a test with the given configuration. Future runTest(TestConfiguration config) async { @@ -36,7 +35,7 @@ class TestRunViewModel extends ChangeNotifier { notifyListeners(); try { - final result = await _iperf3Service.run(config); + final result = await iperf3Service.run(config); _result = result; _state = result.exitCode == 0 ? TestRunState.completed diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 2e1de87..be1ee3e 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -6,6 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST ) list(APPEND FLUTTER_FFI_PLUGIN_LIST + jni ) set(PLUGIN_BUNDLED_LIBRARIES) diff --git a/pubspec.lock b/pubspec.lock index d6e40dc..9b71df9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" async: dependency: transitive description: @@ -33,6 +41,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.2" + code_assets: + dependency: transitive + description: + name: code_assets + sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8 + url: "https://pub.dev" + source: hosted + version: "1.2.1" collection: dependency: transitive description: @@ -41,14 +57,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" - cupertino_icons: - dependency: "direct main" + crypto: + dependency: transitive description: - name: cupertino_icons - sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd" + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf url: "https://pub.dev" source: hosted - version: "1.0.9" + version: "3.0.7" fake_async: dependency: transitive description: @@ -57,6 +73,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.dev" + source: hosted + version: "2.2.0" flutter: dependency: "direct main" description: flutter @@ -75,6 +99,38 @@ packages: description: flutter source: sdk version: "0.0.0" + hooks: + dependency: transitive + description: + name: hooks + sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + jni: + dependency: transitive + description: + name: jni + sha256: f038e58b4dc2c9037f50e233175086337e0b305e356d28211bf55f21c504cbd3 + url: "https://pub.dev" + source: hosted + version: "1.0.3" + jni_flutter: + dependency: transitive + description: + name: jni_flutter + sha256: "7b717011ea40d04fd47c2731d3d1d36eb99eba3435c2753d62489e8c3c9991d5" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + jni_util: + dependency: transitive + description: + name: jni_util + sha256: "1ba86da04a5f2bf18fde2edb235587e70c5b0fc5bd4ba955f46b00942c3fc35f" + url: "https://pub.dev" + source: hosted + version: "1.0.0" leak_tracker: dependency: transitive description: @@ -107,6 +163,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" matcher: dependency: transitive description: @@ -131,6 +195,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.3" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + objective_c: + dependency: transitive + description: + name: objective_c + sha256: b7fb95a6d9a4f009edd63dc5ac69f07420b23a16161c6dd8660290b59c602e8e + url: "https://pub.dev" + source: hosted + version: "9.5.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: ffcf4cf3d6c0b74ac43708d9f56625506e8a68aa935abe9d267a7330f320eb5d + url: "https://pub.dev" + source: hosted + version: "3.0.0" path: dependency: transitive description: @@ -139,6 +227,94 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: a7f4874f987173da295a61c181b8ee71dab59b332a486b391babf26a1b884825 + url: "https://pub.dev" + source: hosted + version: "2.1.6" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd" + url: "https://pub.dev" + source: hosted + version: "2.3.1" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" + url: "https://pub.dev" + source: hosted + version: "2.6.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: "58c2005f147315b11e9b4a7bc889cd5203e250cba8e3f012dae259b4972b5c16" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "484838772624c3a4b94f1e44a3e19897fee738f2d5c4ce448443b0417f7c9dda" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + provider: + dependency: "direct main" + description: + name: provider + sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" + url: "https://pub.dev" + source: hosted + version: "6.1.5+1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + record_use: + dependency: transitive + description: + name: record_use + sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed" + url: "https://pub.dev" + source: hosted + version: "0.6.0" sky_engine: dependency: transitive description: flutter @@ -192,6 +368,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.12" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" vector_math: dependency: transitive description: @@ -208,6 +392,22 @@ packages: url: "https://pub.dev" source: hosted version: "15.3.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" sdks: dart: ">=3.13.1 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + flutter: ">=3.38.4" diff --git a/pubspec.yaml b/pubspec.yaml index 92aed3f..f047ce3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -31,21 +31,14 @@ dependencies: flutter: sdk: flutter - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.8 + path_provider: ^2.1.4 + provider: ^6.1.2 dev_dependencies: + flutter_lints: ^6.0.0 flutter_test: sdk: flutter - # The "flutter_lints" package below contains a set of recommended lints to - # encourage good coding practices. The lint set provided by the package is - # activated in the `analysis_options.yaml` file located at the root of your - # package. See that file for information about deactivating specific lint - # rules and activating additional ones. - flutter_lints: ^6.0.0 - # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/test/ui/features/test_run/test_run_view_model_test.dart b/test/ui/features/test_run/test_run_view_model_test.dart index 5062020..46b7532 100644 --- a/test/ui/features/test_run/test_run_view_model_test.dart +++ b/test/ui/features/test_run/test_run_view_model_test.dart @@ -36,7 +36,7 @@ void main() { final mock = _MockIperf3Service( onRun: (config) async { expect(config.server, equals('192.168.1.1')); - return Iperf3Result(rawOutput: 'done', exitCode: 0); + return const Iperf3Result(rawOutput: 'done', exitCode: 0); }, ); final vm = TestRunViewModel(iperf3Service: mock); @@ -56,6 +56,6 @@ class _MockIperf3Service implements Iperf3Service { @override Future run(TestConfiguration config) async { if (onRun != null) return onRun!(config); - return Iperf3Result(rawOutput: '', exitCode: 0); + return const Iperf3Result(rawOutput: '', exitCode: 0); } } diff --git a/test/widget_test.dart b/test/widget_test.dart index f1c2de4..af0aa47 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -5,26 +5,13 @@ // gestures. You can also use WidgetTester to find child widgets in the widget // tree, read text, and verify that the values of widget properties are correct. -import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:iperf3_gui/main.dart'; +import 'package:iperf3gui/main.dart'; void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); + testWidgets('App smoke test', (WidgetTester tester) async { + await tester.pumpWidget(const Iperf3GuiApp()); + expect(find.text('iperf3-gui'), findsOneWidget); }); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index b93c4c3..3ad69c6 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -6,6 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST ) list(APPEND FLUTTER_FFI_PLUGIN_LIST + jni ) set(PLUGIN_BUNDLED_LIBRARIES)