feat: integrate RTL-TCP server support
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'dart:async';
|
||||
import 'dart:developer' as developer;
|
||||
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
|
||||
import 'package:lbjconsole/models/merged_record.dart';
|
||||
import 'package:lbjconsole/models/train_record.dart';
|
||||
@@ -13,15 +14,25 @@ import 'package:lbjconsole/services/ble_service.dart';
|
||||
import 'package:lbjconsole/services/database_service.dart';
|
||||
import 'package:lbjconsole/services/notification_service.dart';
|
||||
import 'package:lbjconsole/services/background_service.dart';
|
||||
import 'package:lbjconsole/services/rtl_tcp_service.dart';
|
||||
import 'package:lbjconsole/themes/app_theme.dart';
|
||||
import 'dart:convert';
|
||||
|
||||
class _ConnectionStatusWidget extends StatefulWidget {
|
||||
final BLEService bleService;
|
||||
final RtlTcpService rtlTcpService;
|
||||
final DateTime? lastReceivedTime;
|
||||
final DateTime? rtlTcpLastReceivedTime;
|
||||
final bool rtlTcpEnabled;
|
||||
final bool rtlTcpConnected;
|
||||
|
||||
const _ConnectionStatusWidget({
|
||||
required this.bleService,
|
||||
required this.rtlTcpService,
|
||||
required this.lastReceivedTime,
|
||||
required this.rtlTcpLastReceivedTime,
|
||||
required this.rtlTcpEnabled,
|
||||
required this.rtlTcpConnected,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -58,19 +69,32 @@ class _ConnectionStatusWidgetState extends State<_ConnectionStatusWidget> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isRtlTcpMode = widget.rtlTcpEnabled;
|
||||
final rtlTcpConnected = widget.rtlTcpConnected;
|
||||
|
||||
final isConnected = isRtlTcpMode ? rtlTcpConnected : _isConnected;
|
||||
final statusColor = isRtlTcpMode
|
||||
? (rtlTcpConnected ? Colors.green : Colors.red)
|
||||
: (_isConnected ? Colors.green : Colors.red);
|
||||
final statusText = isRtlTcpMode
|
||||
? (rtlTcpConnected ? '已连接' : '未连接')
|
||||
: _deviceStatus;
|
||||
|
||||
final lastReceivedTime = isRtlTcpMode ? widget.rtlTcpLastReceivedTime : widget.lastReceivedTime;
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (widget.lastReceivedTime == null || !_isConnected) ...[
|
||||
Text(_deviceStatus,
|
||||
if (lastReceivedTime == null || !isConnected) ...[
|
||||
Text(statusText,
|
||||
style: const TextStyle(color: Colors.white70, fontSize: 12)),
|
||||
],
|
||||
_LastReceivedTimeWidget(
|
||||
lastReceivedTime: widget.lastReceivedTime,
|
||||
isConnected: _isConnected,
|
||||
lastReceivedTime: lastReceivedTime,
|
||||
isConnected: isConnected,
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -79,7 +103,7 @@ class _ConnectionStatusWidgetState extends State<_ConnectionStatusWidget> {
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(
|
||||
color: _isConnected ? Colors.green : Colors.red,
|
||||
color: statusColor,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
@@ -179,14 +203,23 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
|
||||
String _mapType = 'webview';
|
||||
|
||||
late final BLEService _bleService;
|
||||
late final RtlTcpService _rtlTcpService;
|
||||
final NotificationService _notificationService = NotificationService();
|
||||
final DatabaseService _databaseService = DatabaseService.instance;
|
||||
|
||||
StreamSubscription? _connectionSubscription;
|
||||
StreamSubscription? _rtlTcpConnectionSubscription;
|
||||
StreamSubscription? _dataSubscription;
|
||||
StreamSubscription? _rtlTcpDataSubscription;
|
||||
StreamSubscription? _lastReceivedTimeSubscription;
|
||||
StreamSubscription? _rtlTcpLastReceivedTimeSubscription;
|
||||
StreamSubscription? _settingsSubscription;
|
||||
DateTime? _lastReceivedTime;
|
||||
DateTime? _rtlTcpLastReceivedTime;
|
||||
bool _isHistoryEditMode = false;
|
||||
bool _rtlTcpEnabled = false;
|
||||
bool _rtlTcpConnected = false;
|
||||
bool _isConnected = false;
|
||||
final GlobalKey<HistoryScreenState> _historyScreenKey =
|
||||
GlobalKey<HistoryScreenState>();
|
||||
final GlobalKey<RealtimeScreenState> _realtimeScreenKey =
|
||||
@@ -197,23 +230,47 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
_bleService = BLEService();
|
||||
_rtlTcpService = RtlTcpService();
|
||||
_bleService.initialize();
|
||||
_loadRtlTcpSettings();
|
||||
_initializeServices();
|
||||
_checkAndStartBackgroundService();
|
||||
_setupConnectionListener();
|
||||
_setupLastReceivedTimeListener();
|
||||
_setupSettingsListener();
|
||||
_loadMapType();
|
||||
}
|
||||
|
||||
Future<void> _loadMapType() async {
|
||||
final settings = await DatabaseService.instance.getAllSettings() ?? {};
|
||||
final settings = await DatabaseService.instance.getAllSettings();
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_mapType = settings['mapType']?.toString() ?? 'webview';
|
||||
_mapType = settings?['mapType']?.toString() ?? 'webview';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _loadRtlTcpSettings() async {
|
||||
developer.log('rtl_tcp: load_settings');
|
||||
final settings = await _databaseService.getAllSettings();
|
||||
developer.log('rtl_tcp: settings_loaded: enabled=${(settings?['rtlTcpEnabled'] ?? 0) == 1}, host=${settings?['rtlTcpHost']?.toString() ?? '127.0.0.1'}, port=${settings?['rtlTcpPort']?.toString() ?? '14423'}');
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_rtlTcpEnabled = (settings?['rtlTcpEnabled'] ?? 0) == 1;
|
||||
_rtlTcpConnected = _rtlTcpService.isConnected;
|
||||
});
|
||||
|
||||
if (_rtlTcpEnabled && !_rtlTcpConnected) {
|
||||
final host = settings?['rtlTcpHost']?.toString() ?? '127.0.0.1';
|
||||
final port = settings?['rtlTcpPort']?.toString() ?? '14423';
|
||||
developer.log('rtl_tcp: auto_connect');
|
||||
_connectToRtlTcp(host, port);
|
||||
} else {
|
||||
developer.log('rtl_tcp: skip_connect: enabled=$_rtlTcpEnabled, connected=$_rtlTcpConnected');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _checkAndStartBackgroundService() async {
|
||||
final settings = await DatabaseService.instance.getAllSettings() ?? {};
|
||||
final backgroundServiceEnabled =
|
||||
@@ -233,22 +290,92 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
_rtlTcpLastReceivedTimeSubscription =
|
||||
_rtlTcpService.lastReceivedTimeStream.listen((time) {
|
||||
if (mounted) {
|
||||
if (_rtlTcpEnabled) {
|
||||
setState(() {
|
||||
_rtlTcpLastReceivedTime = time;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _setupSettingsListener() {
|
||||
developer.log('rtl_tcp: setup_listener');
|
||||
_settingsSubscription =
|
||||
DatabaseService.instance.onSettingsChanged((settings) {
|
||||
if (mounted && _currentIndex == 1) {
|
||||
_realtimeScreenKey.currentState?.loadRecords(scrollToTop: false);
|
||||
developer.log('rtl_tcp: settings_changed: enabled=${(settings?['rtlTcpEnabled'] ?? 0) == 1}, host=${settings?['rtlTcpHost']?.toString() ?? '127.0.0.1'}, port=${settings?['rtlTcpPort']?.toString() ?? '14423'}');
|
||||
if (mounted) {
|
||||
final rtlTcpEnabled = (settings?['rtlTcpEnabled'] ?? 0) == 1;
|
||||
if (rtlTcpEnabled != _rtlTcpEnabled) {
|
||||
setState(() {
|
||||
_rtlTcpEnabled = rtlTcpEnabled;
|
||||
});
|
||||
|
||||
if (rtlTcpEnabled) {
|
||||
final host = settings?['rtlTcpHost']?.toString() ?? '127.0.0.1';
|
||||
final port = settings?['rtlTcpPort']?.toString() ?? '14423';
|
||||
_connectToRtlTcp(host, port);
|
||||
} else {
|
||||
_rtlTcpConnectionSubscription?.cancel();
|
||||
_rtlTcpDataSubscription?.cancel();
|
||||
_rtlTcpLastReceivedTimeSubscription?.cancel();
|
||||
_rtlTcpService.disconnect();
|
||||
setState(() {
|
||||
_rtlTcpConnected = false;
|
||||
_rtlTcpLastReceivedTime = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (_currentIndex == 1) {
|
||||
_realtimeScreenKey.currentState?.loadRecords(scrollToTop: false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _setupConnectionListener() {
|
||||
_connectionSubscription = _bleService.connectionStream.listen((connected) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isConnected = connected;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
_rtlTcpConnectionSubscription = _rtlTcpService.connectionStream.listen((connected) {
|
||||
if (mounted) {
|
||||
if (_rtlTcpEnabled) {
|
||||
setState(() {
|
||||
_rtlTcpConnected = connected;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _connectToRtlTcp(String host, String port) async {
|
||||
developer.log('rtl_tcp: connect: $host:$port');
|
||||
try {
|
||||
await _rtlTcpService.connect(host: host, port: port);
|
||||
developer.log('rtl_tcp: connect_req_sent');
|
||||
} catch (e) {
|
||||
developer.log('rtl_tcp: connect_fail: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_connectionSubscription?.cancel();
|
||||
_rtlTcpConnectionSubscription?.cancel();
|
||||
_dataSubscription?.cancel();
|
||||
_rtlTcpDataSubscription?.cancel();
|
||||
_lastReceivedTimeSubscription?.cancel();
|
||||
_rtlTcpLastReceivedTimeSubscription?.cancel();
|
||||
_settingsSubscription?.cancel();
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
super.dispose();
|
||||
@@ -274,6 +401,18 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
|
||||
_realtimeScreenKey.currentState!.addNewRecord(record);
|
||||
}
|
||||
});
|
||||
|
||||
_rtlTcpDataSubscription = _rtlTcpService.dataStream.listen((record) {
|
||||
developer.log('rtl_tcp: recv_data: train=${record.train}');
|
||||
developer.log('rtl_tcp: recv_json: ${jsonEncode(record.toJson())}');
|
||||
_notificationService.showTrainNotification(record);
|
||||
if (_historyScreenKey.currentState != null) {
|
||||
_historyScreenKey.currentState!.addNewRecord(record);
|
||||
}
|
||||
if (_realtimeScreenKey.currentState != null) {
|
||||
_realtimeScreenKey.currentState!.addNewRecord(record);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _showConnectionDialog() {
|
||||
@@ -282,7 +421,7 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
builder: (context) =>
|
||||
_PixelPerfectBluetoothDialog(bleService: _bleService),
|
||||
_PixelPerfectBluetoothDialog(bleService: _bleService, rtlTcpEnabled: _rtlTcpEnabled),
|
||||
).then((_) {
|
||||
_bleService.setAutoConnectBlocked(false);
|
||||
if (!_bleService.isManualDisconnect) {
|
||||
@@ -325,19 +464,26 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
|
||||
),
|
||||
centerTitle: false,
|
||||
actions: [
|
||||
Row(
|
||||
children: [
|
||||
_ConnectionStatusWidget(
|
||||
bleService: _bleService,
|
||||
lastReceivedTime: _lastReceivedTime,
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.bluetooth, color: Colors.white),
|
||||
onPressed: _showConnectionDialog,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
Row(
|
||||
children: [
|
||||
_ConnectionStatusWidget(
|
||||
bleService: _bleService,
|
||||
rtlTcpService: _rtlTcpService,
|
||||
lastReceivedTime: _lastReceivedTime,
|
||||
rtlTcpLastReceivedTime: _rtlTcpLastReceivedTime,
|
||||
rtlTcpEnabled: _rtlTcpEnabled,
|
||||
rtlTcpConnected: _rtlTcpConnected,
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
_rtlTcpEnabled ? Icons.wifi : Icons.bluetooth,
|
||||
color: Colors.white,
|
||||
),
|
||||
onPressed: _showConnectionDialog,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -418,6 +564,7 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
|
||||
SettingsScreen(
|
||||
onSettingsChanged: () {
|
||||
_loadMapType();
|
||||
_loadRtlTcpSettings();
|
||||
},
|
||||
),
|
||||
];
|
||||
@@ -464,7 +611,8 @@ enum _ScanState { initial, scanning, finished }
|
||||
|
||||
class _PixelPerfectBluetoothDialog extends StatefulWidget {
|
||||
final BLEService bleService;
|
||||
const _PixelPerfectBluetoothDialog({required this.bleService});
|
||||
final bool rtlTcpEnabled;
|
||||
const _PixelPerfectBluetoothDialog({required this.bleService, required this.rtlTcpEnabled});
|
||||
@override
|
||||
State<_PixelPerfectBluetoothDialog> createState() =>
|
||||
_PixelPerfectBluetoothDialogState();
|
||||
@@ -477,13 +625,28 @@ class _PixelPerfectBluetoothDialogState
|
||||
StreamSubscription? _connectionSubscription;
|
||||
StreamSubscription? _lastReceivedTimeSubscription;
|
||||
DateTime? _lastReceivedTime;
|
||||
StreamSubscription? _rtlTcpConnectionSubscription;
|
||||
bool _rtlTcpConnected = false;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_connectionSubscription = widget.bleService.connectionStream.listen((_) {
|
||||
if (mounted) setState(() {});
|
||||
});
|
||||
if (!widget.bleService.isConnected) {
|
||||
|
||||
_rtlTcpConnectionSubscription = widget.bleService.rtlTcpService?.connectionStream.listen((connected) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_rtlTcpConnected = connected;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (widget.rtlTcpEnabled && widget.bleService.rtlTcpService != null) {
|
||||
_rtlTcpConnected = widget.bleService.rtlTcpService!.isConnected;
|
||||
}
|
||||
|
||||
if (!widget.bleService.isConnected && !widget.rtlTcpEnabled) {
|
||||
_startScan();
|
||||
}
|
||||
}
|
||||
@@ -491,6 +654,7 @@ class _PixelPerfectBluetoothDialogState
|
||||
@override
|
||||
void dispose() {
|
||||
_connectionSubscription?.cancel();
|
||||
_rtlTcpConnectionSubscription?.cancel();
|
||||
_lastReceivedTimeSubscription?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
@@ -536,13 +700,15 @@ class _PixelPerfectBluetoothDialogState
|
||||
Widget build(BuildContext context) {
|
||||
final isConnected = widget.bleService.isConnected;
|
||||
return AlertDialog(
|
||||
title: const Text('蓝牙设备'),
|
||||
title: Text(widget.rtlTcpEnabled ? 'RTL-TCP 模式' : '蓝牙设备'),
|
||||
content: SizedBox(
|
||||
width: double.maxFinite,
|
||||
child: SingleChildScrollView(
|
||||
child: isConnected
|
||||
? _buildConnectedView(context, widget.bleService.connectedDevice)
|
||||
: _buildDisconnectedView(context),
|
||||
child: widget.rtlTcpEnabled
|
||||
? _buildRtlTcpView(context)
|
||||
: (isConnected
|
||||
? _buildConnectedView(context, widget.bleService.connectedDevice)
|
||||
: _buildDisconnectedView(context)),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
@@ -605,6 +771,31 @@ class _PixelPerfectBluetoothDialogState
|
||||
]);
|
||||
}
|
||||
|
||||
Widget _buildRtlTcpView(BuildContext context) {
|
||||
final isConnected = _rtlTcpConnected;
|
||||
final currentAddress = widget.bleService.rtlTcpService?.currentAddress ?? '未配置';
|
||||
|
||||
return Column(mainAxisSize: MainAxisSize.min, children: [
|
||||
Icon(Icons.wifi, size: 48, color: isConnected ? Colors.green : Colors.red),
|
||||
const SizedBox(height: 16),
|
||||
Text(isConnected ? '已连接' : '未连接',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium
|
||||
?.copyWith(fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 8),
|
||||
Text('$currentAddress',
|
||||
style: TextStyle(color: isConnected ? Colors.green : Colors.grey)),
|
||||
const SizedBox(height: 16),
|
||||
if (_lastReceivedTime != null && isConnected) ...[
|
||||
_LastReceivedTimeWidget(
|
||||
lastReceivedTime: _lastReceivedTime,
|
||||
isConnected: isConnected,
|
||||
),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
Widget _buildDeviceListView() {
|
||||
return SizedBox(
|
||||
height: 200,
|
||||
|
||||
@@ -28,6 +28,8 @@ class SettingsScreen extends StatefulWidget {
|
||||
class _SettingsScreenState extends State<SettingsScreen> {
|
||||
late DatabaseService _databaseService;
|
||||
late TextEditingController _deviceNameController;
|
||||
late TextEditingController _rtlTcpHostController;
|
||||
late TextEditingController _rtlTcpPortController;
|
||||
|
||||
String _deviceName = '';
|
||||
bool _backgroundServiceEnabled = false;
|
||||
@@ -39,19 +41,143 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
GroupBy _groupBy = GroupBy.trainAndLoco;
|
||||
TimeWindow _timeWindow = TimeWindow.unlimited;
|
||||
String _mapType = 'map';
|
||||
bool _rtlTcpEnabled = false;
|
||||
String _rtlTcpHost = '127.0.0.1';
|
||||
String _rtlTcpPort = '14423';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_databaseService = DatabaseService.instance;
|
||||
_deviceNameController = TextEditingController();
|
||||
_rtlTcpHostController = TextEditingController();
|
||||
_rtlTcpPortController = TextEditingController();
|
||||
_loadSettings();
|
||||
_loadRecordCount();
|
||||
}
|
||||
|
||||
Widget _buildRtlTcpSettings() {
|
||||
return Card(
|
||||
color: AppTheme.tertiaryBlack,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16.0),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.wifi,
|
||||
color: Theme.of(context).colorScheme.primary),
|
||||
const SizedBox(width: 12),
|
||||
Text('RTL-TCP 接收', style: AppTheme.titleMedium),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('启用RTL-TCP接收', style: AppTheme.bodyLarge),
|
||||
],
|
||||
),
|
||||
Switch(
|
||||
value: _rtlTcpEnabled,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_rtlTcpEnabled = value;
|
||||
});
|
||||
_saveSettings();
|
||||
},
|
||||
activeColor: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
],
|
||||
),
|
||||
Visibility(
|
||||
visible: _rtlTcpEnabled,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
decoration: InputDecoration(
|
||||
labelText: '服务器地址',
|
||||
hintText: '输入RTL-TCP服务器地址',
|
||||
labelStyle: const TextStyle(color: Colors.white70),
|
||||
hintStyle: const TextStyle(color: Colors.white54),
|
||||
border: OutlineInputBorder(
|
||||
borderSide: const BorderSide(color: Colors.white54),
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: const BorderSide(color: Colors.white54),
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide:
|
||||
BorderSide(color: Theme.of(context).colorScheme.primary),
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
controller: _rtlTcpHostController,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_rtlTcpHost = value;
|
||||
});
|
||||
_saveSettings();
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
decoration: InputDecoration(
|
||||
labelText: '服务器端口',
|
||||
hintText: '输入RTL-TCP服务器端口',
|
||||
labelStyle: const TextStyle(color: Colors.white70),
|
||||
hintStyle: const TextStyle(color: Colors.white54),
|
||||
border: OutlineInputBorder(
|
||||
borderSide: const BorderSide(color: Colors.white54),
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: const BorderSide(color: Colors.white54),
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide:
|
||||
BorderSide(color: Theme.of(context).colorScheme.primary),
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
controller: _rtlTcpPortController,
|
||||
keyboardType: TextInputType.number,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_rtlTcpPort = value;
|
||||
});
|
||||
_saveSettings();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_deviceNameController.dispose();
|
||||
_rtlTcpHostController.dispose();
|
||||
_rtlTcpPortController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -71,6 +197,11 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
_groupBy = settings.groupBy;
|
||||
_timeWindow = settings.timeWindow;
|
||||
_mapType = settingsMap['mapType']?.toString() ?? 'webview';
|
||||
_rtlTcpEnabled = (settingsMap['rtlTcpEnabled'] ?? 0) == 1;
|
||||
_rtlTcpHost = settingsMap['rtlTcpHost']?.toString() ?? '127.0.0.1';
|
||||
_rtlTcpPort = settingsMap['rtlTcpPort']?.toString() ?? '14423';
|
||||
_rtlTcpHostController.text = _rtlTcpHost;
|
||||
_rtlTcpPortController.text = _rtlTcpPort;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -95,6 +226,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
'groupBy': _groupBy.name,
|
||||
'timeWindow': _timeWindow.name,
|
||||
'mapType': _mapType,
|
||||
'rtlTcpEnabled': _rtlTcpEnabled ? 1 : 0,
|
||||
'rtlTcpHost': _rtlTcpHost,
|
||||
'rtlTcpPort': _rtlTcpPort,
|
||||
});
|
||||
widget.onSettingsChanged?.call();
|
||||
}
|
||||
@@ -110,6 +244,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
const SizedBox(height: 20),
|
||||
_buildAppSettings(),
|
||||
const SizedBox(height: 20),
|
||||
_buildRtlTcpSettings(),
|
||||
const SizedBox(height: 20),
|
||||
_buildMergeSettings(),
|
||||
const SizedBox(height: 20),
|
||||
_buildDataManagement(),
|
||||
@@ -204,7 +340,6 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('后台保活服务', style: AppTheme.bodyLarge),
|
||||
Text('保持应用在后台运行', style: AppTheme.caption),
|
||||
],
|
||||
),
|
||||
Switch(
|
||||
@@ -232,8 +367,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('LBJ消息通知', style: AppTheme.bodyLarge),
|
||||
Text('接收LBJ消息通知', style: AppTheme.caption),
|
||||
Text('通知服务', style: AppTheme.bodyLarge),
|
||||
],
|
||||
),
|
||||
Switch(
|
||||
@@ -255,8 +389,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('地图显示方式', style: AppTheme.bodyLarge),
|
||||
Text('选择地图组件类型', style: AppTheme.caption),
|
||||
Text('地图组件类型', style: AppTheme.bodyLarge),
|
||||
],
|
||||
),
|
||||
DropdownButton<String>(
|
||||
@@ -293,7 +426,6 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('隐藏只有时间有效的记录', style: AppTheme.bodyLarge),
|
||||
Text('不显示只有时间信息的记录', style: AppTheme.caption),
|
||||
],
|
||||
),
|
||||
Switch(
|
||||
@@ -342,7 +474,6 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('启用记录合并', style: AppTheme.bodyLarge),
|
||||
Text('合并相同内容的LBJ记录', style: AppTheme.caption),
|
||||
],
|
||||
),
|
||||
Switch(
|
||||
@@ -452,7 +583,6 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('隐藏不可分组记录', style: AppTheme.bodyLarge),
|
||||
Text('不显示无法分组的记录', style: AppTheme.caption),
|
||||
],
|
||||
),
|
||||
Switch(
|
||||
|
||||
Reference in New Issue
Block a user