Skip to content

Commit fabf716

Browse files
committed
Add speed test box
1 parent c3c2a85 commit fabf716

15 files changed

+466
-142
lines changed

Windows setup.exe

-9.53 MB
Binary file not shown.

lib/bloc.dart

+52-3
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ import 'dart:io';
44

55
import 'package:connectivity_plus/connectivity_plus.dart';
66
import 'package:flutter/material.dart';
7+
import 'package:flutter_speedtest/flutter_speedtest.dart';
78
import 'package:http/http.dart' as http;
89
import 'package:http/io_client.dart';
910
import 'package:ir_net/data/leak_item.dart';
1011
import 'package:ir_net/data/shared_preferences.dart';
1112
import 'package:ir_net/utils/cmd.dart';
13+
import 'package:ir_net/utils/http.dart';
1214
import 'package:ir_net/utils/system_tray.dart';
1315
import 'package:latlng/latlng.dart';
1416
import 'package:live_event/live_event.dart';
@@ -21,6 +23,10 @@ class AppBloc with AppSystemTray {
2123
final _clearLeakInput = LiveEvent();
2224
final _leakChecklist = BehaviorSubject<List<LeakItem>>();
2325
final _localNetwork = BehaviorSubject<LocalNetworksResult>();
26+
final _ping = BehaviorSubject<double?>();
27+
final _downloadSpeed = BehaviorSubject<double?>();
28+
final _uploadSpeed = BehaviorSubject<double?>();
29+
final _speedTestStatus = BehaviorSubject<String>();
2430

2531
bool _isPingingGoogle = false;
2632
bool _foundALeakedSite = false;
@@ -32,6 +38,17 @@ class AppBloc with AppSystemTray {
3238
Stream get clearLeakInput => _clearLeakInput.stream;
3339
Stream<List<LeakItem>> get leakChecklist => _leakChecklist.stream;
3440
Stream<LocalNetworksResult> get localNetwork => _localNetwork.stream;
41+
Stream<double?> get ping => _ping.stream;
42+
Stream<double?> get downloadSpeed => _downloadSpeed.stream;
43+
Stream<double?> get uploadSpeed => _uploadSpeed.stream;
44+
Stream<String> get speedTestStatus => _speedTestStatus.stream;
45+
46+
final speedtest = FlutterSpeedtest(
47+
baseUrl: 'http://speedtest.jaosing.com:8080',
48+
pathDownload: '/download',
49+
pathUpload: '/upload',
50+
pathResponseTime: '/ping',
51+
);
3552

3653
void onLeakInputChanged(String value) {
3754
_leakInput = value;
@@ -110,7 +127,7 @@ class AppBloc with AppSystemTray {
110127
item.status = LeakStatus.failed;
111128
}
112129
debugPrint('leak detection for $url => ${response.bodyBytes.length ~/ 1024} Kilobytes');
113-
} on Exception catch(ex) {
130+
} on Exception catch (ex) {
114131
item.status = LeakStatus.failed;
115132
_checkNetworkRefuseException(ex);
116133
}
@@ -131,12 +148,17 @@ class AppBloc with AppSystemTray {
131148
await _checkProxySettings();
132149
_checkIpLocation();
133150
_verifyLeakedSites();
151+
_checkPing();
134152
} else {
135153
setSystemTrayStatusToOffline();
136154
}
137155
});
138156
}
139157

158+
void _checkPing() async {
159+
_ping.value = await HttpUtils.measureHttpPing();
160+
}
161+
140162
void _runIpCheckInfinitely() async {
141163
while (true) {
142164
await _checkProxySettings();
@@ -154,14 +176,15 @@ class AppBloc with AppSystemTray {
154176
_isPingingGoogle = false;
155177
_checkNetworkConnectivity();
156178
return;
157-
} on SocketException catch(ex) {
179+
} on SocketException catch (ex) {
158180
_checkNetworkRefuseException(ex);
159181
}
160182
_isPingingGoogle = false;
161183
}
162184

163185
void _checkNetworkRefuseException(Exception ex) {
164-
if (ex is SocketException && ex.message.startsWith('The remote computer refused the network connection.')) {
186+
if (ex is SocketException &&
187+
ex.message.startsWith('The remote computer refused the network connection.')) {
165188
_proxyServer = null;
166189
}
167190
}
@@ -222,6 +245,31 @@ class AppBloc with AppSystemTray {
222245
}
223246
}
224247

248+
void onConnectionTestClick() async {
249+
_downloadSpeed.value = 0;
250+
_uploadSpeed.value = 0;
251+
_speedTestStatus.value = 'Running';
252+
speedtest.getDataspeedtest(
253+
downloadOnProgress: ((percent, transferRate) {
254+
_downloadSpeed.value = transferRate;
255+
}),
256+
uploadOnProgress: ((percent, transferRate) {
257+
_uploadSpeed.value = transferRate;
258+
}),
259+
progressResponse: ((responseTime, jitter) {
260+
// nothing to do
261+
}),
262+
onError: ((errorMessage) {
263+
_downloadSpeed.value = 0;
264+
_uploadSpeed.value = 0;
265+
_speedTestStatus.value = 'Error';
266+
}),
267+
onDone: () {
268+
_speedTestStatus.value = 'Done';
269+
},
270+
);
271+
}
272+
225273
void onExitClick() {
226274
destroySystemTray();
227275
exit(0);
@@ -238,6 +286,7 @@ class AppBloc with AppSystemTray {
238286

239287
void _handleRefresh() async {
240288
await _checkProxySettings();
289+
_checkPing();
241290
_pingGoogle();
242291
_checkIpLocation();
243292
_verifyLeakedSites();

lib/home.dart

+9-52
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import 'package:flutter/material.dart';
2-
import 'package:ir_net/data/shared_preferences.dart';
2+
import 'package:ir_net/views/connection.dart';
33
import 'package:ir_net/views/ip_stat.dart';
44
import 'package:ir_net/views/leak.dart';
5-
import 'package:launch_at_startup/launch_at_startup.dart';
5+
import 'package:ir_net/views/options.dart';
66

77
import 'main.dart';
88

@@ -23,27 +23,22 @@ class _HomePageState extends State<HomePage> {
2323
crossAxisAlignment: CrossAxisAlignment.center,
2424
mainAxisAlignment: MainAxisAlignment.center,
2525
children: [
26-
Row(
26+
const Row(
2727
mainAxisAlignment: MainAxisAlignment.center,
2828
crossAxisAlignment: CrossAxisAlignment.start,
29-
children: const [
29+
children: [
3030
LeakView(),
3131
SizedBox(width: 64),
3232
IpStatView(),
3333
],
3434
),
3535
const SizedBox(height: 16),
36-
Column(
37-
crossAxisAlignment: CrossAxisAlignment.start,
36+
const Row(
37+
mainAxisAlignment: MainAxisAlignment.center,
3838
children: [
39-
SizedBox(
40-
width: 400,
41-
child: showLeakInSysTray(),
42-
),
43-
SizedBox(
44-
width: 300,
45-
child: launchAtStartup(),
46-
),
39+
AppOptions(),
40+
SizedBox(width: 64),
41+
Connection()
4742
],
4843
),
4944
const SizedBox(height: 24),
@@ -62,44 +57,6 @@ class _HomePageState extends State<HomePage> {
6257
);
6358
}
6459

65-
Widget launchAtStartup() {
66-
return FutureBuilder<bool>(
67-
future: LaunchAtStartup.instance.isEnabled(),
68-
builder: (context, snapshot) {
69-
final value = snapshot.data ?? false;
70-
return CheckboxListTile(
71-
title: const Text('Launch on windows startup?'),
72-
value: value,
73-
onChanged: (enabled) {
74-
if (enabled == true) {
75-
LaunchAtStartup.instance.enable();
76-
} else {
77-
LaunchAtStartup.instance.disable();
78-
}
79-
setState(() {});
80-
},
81-
);
82-
},
83-
);
84-
}
85-
86-
Widget showLeakInSysTray() {
87-
return FutureBuilder<bool>(
88-
future: AppSharedPreferences.showLeakInSysTray,
89-
builder: (context, snapshot) {
90-
final value = snapshot.data ?? false;
91-
return CheckboxListTile(
92-
title: const Text('Show leak detection on system tray icon?'),
93-
value: value,
94-
onChanged: (enabled) async {
95-
await AppSharedPreferences.setShowLeakInSysTray(enabled ?? false);
96-
setState(() {});
97-
},
98-
);
99-
},
100-
);
101-
}
102-
10360
Widget exitButton() {
10461
return ElevatedButton(
10562
style: ButtonStyle(minimumSize: MaterialStateProperty.all(const Size(80, 56))),

lib/main.dart

+12
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:ir_net/data/shared_preferences.dart';
66
import 'package:launch_at_startup/launch_at_startup.dart';
77
import 'package:package_info_plus/package_info_plus.dart';
88
import 'package:win_toast/win_toast.dart';
9+
import 'package:window_manager/window_manager.dart';
910
import 'package:windows_single_instance/windows_single_instance.dart';
1011

1112
import 'app.dart';
@@ -15,6 +16,7 @@ final bloc = AppBloc();
1516

1617
void main() async {
1718
WidgetsFlutterBinding.ensureInitialized();
19+
await initWindowManager();
1820
await initSingleInstance();
1921
await initWinToast();
2022
await initLaunchAtStartup();
@@ -23,6 +25,16 @@ void main() async {
2325
runApp(const App());
2426
}
2527

28+
Future<void> initWindowManager() async {
29+
await windowManager.ensureInitialized();
30+
WindowOptions windowOptions = const WindowOptions(
31+
size: Size(1000, 780),
32+
);
33+
windowManager.waitUntilReadyToShow(windowOptions, () {
34+
windowManager.setTitle("IRNet: freedom does not have a price");
35+
},);
36+
}
37+
2638
Future<void> initSingleInstance() async {
2739
await WindowsSingleInstance.ensureSingleInstance([], "pipeMain");
2840
}

lib/utils/http.dart

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import 'dart:io';
2+
3+
import 'package:dio/dio.dart';
4+
5+
class HttpUtils {
6+
static final Dio _dio = Dio();
7+
8+
static Future<double> measureHttpPing({
9+
String url = "https://www.gstatic.com/generate_204",
10+
Duration timeout = const Duration(seconds: 5),
11+
}) async {
12+
_dio.options = BaseOptions(headers: {
13+
HttpHeaders.connectionHeader: 'keep-alive', // Ensure keep-alive headers
14+
});
15+
try {
16+
await _dio.get(url).timeout(timeout); // just a warm up connection to get a reliable result
17+
final firstTime = DateTime.now().millisecondsSinceEpoch;
18+
final response = await _dio.get(url).timeout(timeout);
19+
final secondTime = DateTime.now().millisecondsSinceEpoch;
20+
if (response.statusCode == 200 || response.statusCode == 204) {
21+
return (secondTime - firstTime).toDouble();
22+
} else {
23+
return 0; // server responded but with an error
24+
}
25+
} on Exception catch (_) {
26+
return 0; // timeout or unreachable
27+
}
28+
}
29+
}

0 commit comments

Comments
 (0)