mirror of https://github.com/ospab/ostp.git
feat(flutter): add optional live speed/RTT display, matching desktop GUI
Mirrors ostp-gui's "Show Speed" / "Show RTT" client settings toggles (both default on): the home screen now shows live download/upload throughput (computed from byte deltas between 1s polls, same as desktop's poll()) as a subtitle under the existing cumulative Download/Upload totals, and the RTT box is now hideable. Also fixed "Test Ping" to actually query getMetrics instead of just faking a 500ms spinner with no real measurement.
This commit is contained in:
parent
1d1a1ea5af
commit
223f02287a
|
|
@ -32,6 +32,16 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
||||||
String _download = '0 B';
|
String _download = '0 B';
|
||||||
String _upload = '0 B';
|
String _upload = '0 B';
|
||||||
|
|
||||||
|
// Live throughput (bytes/sec, computed from deltas between polls) and RTT
|
||||||
|
// are optional, same as the desktop GUI's "Show Speed" / "Show RTT" toggles
|
||||||
|
// in client settings — default on, persisted in prefs.
|
||||||
|
bool _showSpeed = true;
|
||||||
|
bool _showRtt = true;
|
||||||
|
String _downSpeed = '0 B/s';
|
||||||
|
String _upSpeed = '0 B/s';
|
||||||
|
int _prevBytesRecv = 0;
|
||||||
|
int _prevBytesSent = 0;
|
||||||
|
|
||||||
late AnimationController _pulseController;
|
late AnimationController _pulseController;
|
||||||
late AnimationController _spinController;
|
late AnimationController _spinController;
|
||||||
|
|
||||||
|
|
@ -73,6 +83,8 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
||||||
// happen — the editor enforces exclusivity — but don't crash on stale data).
|
// happen — the editor enforces exclusivity — but don't crash on stale data).
|
||||||
final actives = profiles.where((p) => p.active).toList();
|
final actives = profiles.where((p) => p.active).toList();
|
||||||
_activeProfile = actives.isNotEmpty ? actives.first : null;
|
_activeProfile = actives.isNotEmpty ? actives.first : null;
|
||||||
|
_showSpeed = widget.prefs.getBool('show_speed') ?? true;
|
||||||
|
_showRtt = widget.prefs.getBool('show_rtt') ?? true;
|
||||||
});
|
});
|
||||||
_updateLatestConfigJson();
|
_updateLatestConfigJson();
|
||||||
}
|
}
|
||||||
|
|
@ -402,6 +414,12 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
||||||
setState(() {
|
setState(() {
|
||||||
_download = _formatBytes(bytesRecv);
|
_download = _formatBytes(bytesRecv);
|
||||||
_upload = _formatBytes(bytesSent);
|
_upload = _formatBytes(bytesSent);
|
||||||
|
final dRecv = bytesRecv > _prevBytesRecv ? bytesRecv - _prevBytesRecv : 0;
|
||||||
|
final dSent = bytesSent > _prevBytesSent ? bytesSent - _prevBytesSent : 0;
|
||||||
|
_prevBytesRecv = bytesRecv;
|
||||||
|
_prevBytesSent = bytesSent;
|
||||||
|
_downSpeed = '${_formatBytes(dRecv)}/s';
|
||||||
|
_upSpeed = '${_formatBytes(dSent)}/s';
|
||||||
if (rttMs > 0 && !_isCheckingPing) {
|
if (rttMs > 0 && !_isCheckingPing) {
|
||||||
_pingText = 'Server Ping: $rttMs ms';
|
_pingText = 'Server Ping: $rttMs ms';
|
||||||
if (rttMs < 100) {
|
if (rttMs < 100) {
|
||||||
|
|
@ -438,7 +456,30 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
||||||
_pingColor = Colors.white70;
|
_pingColor = Colors.white70;
|
||||||
});
|
});
|
||||||
|
|
||||||
await Future.delayed(const Duration(milliseconds: 500));
|
try {
|
||||||
|
final metricsJson = await platform.invokeMethod('getMetrics');
|
||||||
|
if (metricsJson != null && metricsJson.isNotEmpty) {
|
||||||
|
final Map<String, dynamic> parsed = jsonDecode(metricsJson);
|
||||||
|
final rttMs = parsed['rtt_ms'] as int? ?? 0;
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
if (rttMs > 0) {
|
||||||
|
_pingText = 'Server Ping: $rttMs ms';
|
||||||
|
_pingColor = rttMs < 100
|
||||||
|
? const Color(0xFF22D3A5)
|
||||||
|
: rttMs < 250
|
||||||
|
? Colors.amberAccent
|
||||||
|
: Colors.redAccent;
|
||||||
|
} else {
|
||||||
|
_pingText = 'Server Ping: -- ms';
|
||||||
|
_pingColor = Colors.white54;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint("Failed to check latency: $e");
|
||||||
|
}
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|
@ -453,6 +494,10 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
||||||
_state = ConnectionStateEnum.disconnected;
|
_state = ConnectionStateEnum.disconnected;
|
||||||
_download = '0 B';
|
_download = '0 B';
|
||||||
_upload = '0 B';
|
_upload = '0 B';
|
||||||
|
_downSpeed = '0 B/s';
|
||||||
|
_upSpeed = '0 B/s';
|
||||||
|
_prevBytesRecv = 0;
|
||||||
|
_prevBytesSent = 0;
|
||||||
_pingText = 'Target Ping: -- ms';
|
_pingText = 'Target Ping: -- ms';
|
||||||
_pingColor = Colors.white54;
|
_pingColor = Colors.white54;
|
||||||
_isCheckingPing = false;
|
_isCheckingPing = false;
|
||||||
|
|
@ -725,6 +770,7 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
if (_showRtt)
|
||||||
AnimatedOpacity(
|
AnimatedOpacity(
|
||||||
opacity: _state == ConnectionStateEnum.connected ? 1.0 : 0.0,
|
opacity: _state == ConnectionStateEnum.connected ? 1.0 : 0.0,
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
|
|
@ -809,15 +855,15 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
children: [
|
children: [
|
||||||
_buildMetricItem(Icons.arrow_downward_rounded, 'Download', _download, theme.colorScheme.secondary),
|
_buildMetricItem(Icons.arrow_downward_rounded, 'Download', _download, theme.colorScheme.secondary, _showSpeed ? _downSpeed : null),
|
||||||
Container(width: 1, height: 40, color: Colors.white.withOpacity(0.15)),
|
Container(width: 1, height: 40, color: Colors.white.withOpacity(0.15)),
|
||||||
_buildMetricItem(Icons.arrow_upward_rounded, 'Upload', _upload, theme.colorScheme.primary),
|
_buildMetricItem(Icons.arrow_upward_rounded, 'Upload', _upload, theme.colorScheme.primary, _showSpeed ? _upSpeed : null),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildMetricItem(IconData icon, String label, String value, Color color) {
|
Widget _buildMetricItem(IconData icon, String label, String value, Color color, [String? speed]) {
|
||||||
return Expanded(
|
return Expanded(
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
|
@ -856,6 +902,19 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (speed != null) ...[
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
speed,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: color,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
bool _debugMode = false;
|
bool _debugMode = false;
|
||||||
bool _muxEnabled = false;
|
bool _muxEnabled = false;
|
||||||
bool _isCheckingUpdates = false;
|
bool _isCheckingUpdates = false;
|
||||||
|
bool _showSpeed = true;
|
||||||
|
bool _showRtt = true;
|
||||||
|
|
||||||
List<OstpProfile> _profiles = [];
|
List<OstpProfile> _profiles = [];
|
||||||
|
|
||||||
|
|
@ -57,6 +59,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
// doesn't map to anything meaningful on Android the way it does on desktop.
|
// doesn't map to anything meaningful on Android the way it does on desktop.
|
||||||
_debugMode = widget.prefs.getBool('debug_mode') ?? false;
|
_debugMode = widget.prefs.getBool('debug_mode') ?? false;
|
||||||
_muxEnabled = widget.prefs.getBool('mux_enabled') ?? false;
|
_muxEnabled = widget.prefs.getBool('mux_enabled') ?? false;
|
||||||
|
_showSpeed = widget.prefs.getBool('show_speed') ?? true;
|
||||||
|
_showRtt = widget.prefs.getBool('show_rtt') ?? true;
|
||||||
_muxSessionsCtrl = TextEditingController(text: widget.prefs.getString('mux_sessions') ?? '2');
|
_muxSessionsCtrl = TextEditingController(text: widget.prefs.getString('mux_sessions') ?? '2');
|
||||||
_profiles = decodeProfiles(widget.prefs.getString('profiles_json'));
|
_profiles = decodeProfiles(widget.prefs.getString('profiles_json'));
|
||||||
}
|
}
|
||||||
|
|
@ -81,6 +85,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
widget.prefs.setString('ex_ips', _ipsCtrl.text.trim());
|
widget.prefs.setString('ex_ips', _ipsCtrl.text.trim());
|
||||||
widget.prefs.setBool('debug_mode', _debugMode);
|
widget.prefs.setBool('debug_mode', _debugMode);
|
||||||
widget.prefs.setBool('mux_enabled', _muxEnabled);
|
widget.prefs.setBool('mux_enabled', _muxEnabled);
|
||||||
|
widget.prefs.setBool('show_speed', _showSpeed);
|
||||||
|
widget.prefs.setBool('show_rtt', _showRtt);
|
||||||
widget.prefs.setString('mux_sessions', _muxSessionsCtrl.text.trim());
|
widget.prefs.setString('mux_sessions', _muxSessionsCtrl.text.trim());
|
||||||
widget.prefs.setString('profiles_json', encodeProfiles(_profiles));
|
widget.prefs.setString('profiles_json', encodeProfiles(_profiles));
|
||||||
}
|
}
|
||||||
|
|
@ -700,6 +706,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
_buildTextField('MUX Sessions', _muxSessionsCtrl, hint: 'e.g. 2, 4, 8'),
|
_buildTextField('MUX Sessions', _muxSessionsCtrl, hint: 'e.g. 2, 4, 8'),
|
||||||
|
|
||||||
_buildToggle('Debug Mode', 'Verbose logging', _debugMode, (v) => _debugMode = v),
|
_buildToggle('Debug Mode', 'Verbose logging', _debugMode, (v) => _debugMode = v),
|
||||||
|
_buildToggle('Show Speed', 'Live download/upload speed on the home screen', _showSpeed, (v) => _showSpeed = v),
|
||||||
|
_buildToggle('Show RTT', 'Live server ping on the home screen', _showRtt, (v) => _showRtt = v),
|
||||||
|
|
||||||
_buildTextField('Local Proxy Bind', _localBindCtrl, hint: '127.0.0.1:1088'),
|
_buildTextField('Local Proxy Bind', _localBindCtrl, hint: '127.0.0.1:1088'),
|
||||||
_buildTextField('Custom DNS Server', _dnsCtrl, hint: '1.1.1.1 (e.g. 8.8.8.8)'),
|
_buildTextField('Custom DNS Server', _dnsCtrl, hint: '1.1.1.1 (e.g. 8.8.8.8)'),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue