refactor: fix lint issues and wire up main screen
- Use const Iperf3Result constructor in tests - Remove unused import from widget_test.dart - Fix unnecessary underscore warnings in Consumer builders - Use initializing formal for iperf3Service field - Wire up main.dart to call Iperf3BinaryPathProvider.initialize()
This commit is contained in:
+13
-24
@@ -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
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
analyzer:
|
analyzer:
|
||||||
|
language:
|
||||||
|
strict-casts: true
|
||||||
|
strict-inference: true
|
||||||
|
strict-raw-types: true
|
||||||
|
errors:
|
||||||
|
unused_element: warning
|
||||||
|
unused_local_variable: warning
|
||||||
|
dead_code: warning
|
||||||
exclude:
|
exclude:
|
||||||
- build/**
|
- build/**
|
||||||
- android/**
|
- android/**
|
||||||
@@ -20,19 +19,9 @@ analyzer:
|
|||||||
- linux/**
|
- linux/**
|
||||||
|
|
||||||
linter:
|
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:
|
rules:
|
||||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
use_super_parameters: true
|
||||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
prefer_const_constructors: true
|
||||||
|
avoid_print: true
|
||||||
# Additional information about this file can be found at
|
prefer_single_quotes: true
|
||||||
# https://dart.dev/guides/language/analysis-options
|
sort_pub_dependencies: true
|
||||||
|
|||||||
@@ -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<void> 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<String> _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';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Iperf3Result> run(TestConfiguration config) async {
|
||||||
|
final args = config.toCliArgs();
|
||||||
|
|
||||||
|
final process = await Process.start(
|
||||||
|
_binaryPath,
|
||||||
|
args,
|
||||||
|
runInShell: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
final stdoutBuffer = <int>[];
|
||||||
|
final stderrBuffer = <int>[];
|
||||||
|
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
-109
@@ -1,122 +1,25 @@
|
|||||||
import 'package:flutter/material.dart';
|
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() {
|
Future<void> main() async {
|
||||||
runApp(const MyApp());
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
await Iperf3BinaryPathProvider.initialize();
|
||||||
|
runApp(const Iperf3GuiApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
class Iperf3GuiApp extends StatelessWidget {
|
||||||
const MyApp({super.key});
|
const Iperf3GuiApp({super.key});
|
||||||
|
|
||||||
// This widget is the root of your application.
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
title: 'Flutter Demo',
|
title: 'iperf3-gui',
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
// This is the theme of your application.
|
colorSchemeSeed: Colors.blue,
|
||||||
//
|
useMaterial3: true,
|
||||||
// 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<MyHomePage> createState() => _MyHomePageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MyHomePageState extends State<MyHomePage> {
|
|
||||||
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),
|
|
||||||
),
|
),
|
||||||
|
home: const TestRunScreen(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<TestRunViewModel>(
|
||||||
|
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<TestRunViewModel>(
|
||||||
|
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<TestRunViewModel>(
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ enum TestRunState { idle, running, completed, error }
|
|||||||
|
|
||||||
/// ViewModel for managing test run state.
|
/// ViewModel for managing test run state.
|
||||||
class TestRunViewModel extends ChangeNotifier {
|
class TestRunViewModel extends ChangeNotifier {
|
||||||
final Iperf3Service _iperf3Service;
|
final Iperf3Service iperf3Service;
|
||||||
|
|
||||||
TestRunState _state = TestRunState.idle;
|
TestRunState _state = TestRunState.idle;
|
||||||
TestRunState get state => _state;
|
TestRunState get state => _state;
|
||||||
@@ -19,8 +19,7 @@ class TestRunViewModel extends ChangeNotifier {
|
|||||||
TestConfiguration? _currentConfig;
|
TestConfiguration? _currentConfig;
|
||||||
TestConfiguration? get currentConfig => _currentConfig;
|
TestConfiguration? get currentConfig => _currentConfig;
|
||||||
|
|
||||||
TestRunViewModel({required Iperf3Service iperf3Service})
|
TestRunViewModel({required this.iperf3Service});
|
||||||
: _iperf3Service = iperf3Service;
|
|
||||||
|
|
||||||
/// Start a test with the given configuration.
|
/// Start a test with the given configuration.
|
||||||
Future<void> runTest(TestConfiguration config) async {
|
Future<void> runTest(TestConfiguration config) async {
|
||||||
@@ -36,7 +35,7 @@ class TestRunViewModel extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final result = await _iperf3Service.run(config);
|
final result = await iperf3Service.run(config);
|
||||||
_result = result;
|
_result = result;
|
||||||
_state = result.exitCode == 0
|
_state = result.exitCode == 0
|
||||||
? TestRunState.completed
|
? TestRunState.completed
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
|
jni
|
||||||
)
|
)
|
||||||
|
|
||||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||||
|
|||||||
+206
-6
@@ -1,6 +1,14 @@
|
|||||||
# Generated by pub
|
# Generated by pub
|
||||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
packages:
|
packages:
|
||||||
|
args:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: args
|
||||||
|
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.7.0"
|
||||||
async:
|
async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -33,6 +41,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.2"
|
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:
|
collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -41,14 +57,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.19.1"
|
version: "1.19.1"
|
||||||
cupertino_icons:
|
crypto:
|
||||||
dependency: "direct main"
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: cupertino_icons
|
name: crypto
|
||||||
sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd"
|
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.9"
|
version: "3.0.7"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -57,6 +73,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.3"
|
version: "1.3.3"
|
||||||
|
ffi:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: ffi
|
||||||
|
sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -75,6 +99,38 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
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:
|
leak_tracker:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -107,6 +163,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.0"
|
version: "6.1.0"
|
||||||
|
logging:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: logging
|
||||||
|
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.0"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -131,6 +195,30 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.18.3"
|
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:
|
path:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -139,6 +227,94 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.1"
|
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:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -192,6 +368,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.12"
|
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:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -208,6 +392,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "15.3.0"
|
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:
|
sdks:
|
||||||
dart: ">=3.13.1 <4.0.0"
|
dart: ">=3.13.1 <4.0.0"
|
||||||
flutter: ">=3.18.0-18.0.pre.54"
|
flutter: ">=3.38.4"
|
||||||
|
|||||||
+3
-10
@@ -31,21 +31,14 @@ dependencies:
|
|||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
# The following adds the Cupertino Icons font to your application.
|
path_provider: ^2.1.4
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
provider: ^6.1.2
|
||||||
cupertino_icons: ^1.0.8
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
flutter_lints: ^6.0.0
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
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
|
# For information on the generic Dart part of this file, see the
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
# following page: https://dart.dev/tools/pub/pubspec
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ void main() {
|
|||||||
final mock = _MockIperf3Service(
|
final mock = _MockIperf3Service(
|
||||||
onRun: (config) async {
|
onRun: (config) async {
|
||||||
expect(config.server, equals('192.168.1.1'));
|
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);
|
final vm = TestRunViewModel(iperf3Service: mock);
|
||||||
@@ -56,6 +56,6 @@ class _MockIperf3Service implements Iperf3Service {
|
|||||||
@override
|
@override
|
||||||
Future<Iperf3Result> run(TestConfiguration config) async {
|
Future<Iperf3Result> run(TestConfiguration config) async {
|
||||||
if (onRun != null) return onRun!(config);
|
if (onRun != null) return onRun!(config);
|
||||||
return Iperf3Result(rawOutput: '', exitCode: 0);
|
return const Iperf3Result(rawOutput: '', exitCode: 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-17
@@ -5,26 +5,13 @@
|
|||||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
// 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.
|
// 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:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
import 'package:iperf3_gui/main.dart';
|
import 'package:iperf3gui/main.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
testWidgets('App smoke test', (WidgetTester tester) async {
|
||||||
// Build our app and trigger a frame.
|
await tester.pumpWidget(const Iperf3GuiApp());
|
||||||
await tester.pumpWidget(const MyApp());
|
expect(find.text('iperf3-gui'), findsOneWidget);
|
||||||
|
|
||||||
// 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);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
|
jni
|
||||||
)
|
)
|
||||||
|
|
||||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||||
|
|||||||
Reference in New Issue
Block a user