0dd202291c
The root cause of the SIGBUS (alignment fault) crash was casting jmethodID values to function pointers and invoking them directly. jmethodID is an opaque JNI type that must only be used via CallVoidMethod/CallObjectMethod etc. Calling it as a function pointer dereferences garbage memory, causing SIGBUS at address 0xb1. Changes: - Replace function pointer typedefs (output_callback_t etc.) with a struct storing JNIEnv + callback global ref + jmethodIDs - All callbacks now go through CallVoidMethod(env, ref, mid, ...) - Remove getTestProgress() (never used, unused start_time workaround) - Fix Kotlin arg cast: List<String> -> toTypedArray() (ClassCastException) - Update UI default server to speedtest.uztelecom.uz
144 lines
4.5 KiB
Dart
144 lines
4.5 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:iperf3gui/data/services/iperf3_service.dart';
|
|
import 'package:iperf3gui/domain/models/test_configuration.dart';
|
|
import 'package:iperf3gui/ui/features/test_run/test_run_view_model.dart';
|
|
|
|
/// Main test run screen.
|
|
///
|
|
/// iperf3 is now compiled as a native shared library (libiperf3jni.so)
|
|
/// and accessed via JNI. No binary extraction or chmod needed.
|
|
class TestRunScreen extends StatelessWidget {
|
|
const TestRunScreen({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return ChangeNotifierProvider(
|
|
create: (_) => TestRunViewModel(
|
|
iperf3Service: Iperf3Service(),
|
|
),
|
|
child: const _TestRunContent(),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _TestRunContent extends StatefulWidget {
|
|
const _TestRunContent();
|
|
|
|
@override
|
|
State<_TestRunContent> createState() => _TestRunContentState();
|
|
}
|
|
|
|
class _TestRunContentState extends State<_TestRunContent> {
|
|
final _serverController = TextEditingController(text: 'speedtest.uztelecom.uz');
|
|
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,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|