feat: optimize Bluetooth connection status display
This commit is contained in:
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
|
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
|
||||||
<application
|
<application
|
||||||
android:label="lbjconsole"
|
android:label="LBJ Console"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher">
|
android:icon="@mipmap/ic_launcher">
|
||||||
<activity
|
<activity
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleDisplayName</key>
|
<key>CFBundleDisplayName</key>
|
||||||
<string>Lbjconsole</string>
|
<string>LBJ Console</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>$(EXECUTABLE_NAME)</string>
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
<key>CFBundleInfoDictionaryVersion</key>
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
<string>6.0</string>
|
<string>6.0</string>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>lbjconsole</string>
|
<string>LBJ Console</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ class HistoryScreenState extends State<HistoryScreen> {
|
|||||||
final ScrollController _scrollController = ScrollController();
|
final ScrollController _scrollController = ScrollController();
|
||||||
bool _isAtTop = true;
|
bool _isAtTop = true;
|
||||||
MergeSettings _mergeSettings = MergeSettings();
|
MergeSettings _mergeSettings = MergeSettings();
|
||||||
|
double _itemHeightCache = 0.0;
|
||||||
|
|
||||||
final Map<String, double> _mapOptimalZoom = {};
|
final Map<String, double> _mapOptimalZoom = {};
|
||||||
final Map<String, bool> _mapCalculating = {};
|
final Map<String, bool> _mapCalculating = {};
|
||||||
@@ -123,6 +124,9 @@ class HistoryScreenState extends State<HistoryScreen> {
|
|||||||
if (!isNewRecord) return;
|
if (!isNewRecord) return;
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
|
final previousScrollOffset = _scrollController.hasClients ? _scrollController.offset : 0.0;
|
||||||
|
final previousItemCount = _displayItems.length;
|
||||||
|
|
||||||
final allRecords = await DatabaseService.instance.getAllRecords();
|
final allRecords = await DatabaseService.instance.getAllRecords();
|
||||||
final items = MergeService.getMixedList(allRecords, _mergeSettings);
|
final items = MergeService.getMixedList(allRecords, _mergeSettings);
|
||||||
|
|
||||||
@@ -131,14 +135,31 @@ class HistoryScreenState extends State<HistoryScreen> {
|
|||||||
_displayItems.addAll(items);
|
_displayItems.addAll(items);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (_isAtTop && _scrollController.hasClients) {
|
if (_scrollController.hasClients) {
|
||||||
|
if (_isAtTop) {
|
||||||
_scrollController.jumpTo(0.0);
|
_scrollController.jumpTo(0.0);
|
||||||
|
} else {
|
||||||
|
final newItemCount = items.length;
|
||||||
|
final itemDifference = newItemCount - previousItemCount;
|
||||||
|
|
||||||
|
if (itemDifference > 0 && previousScrollOffset > 0) {
|
||||||
|
final itemHeight = _getEstimatedItemHeight();
|
||||||
|
final adjustedOffset = previousScrollOffset + (itemDifference * itemHeight);
|
||||||
|
|
||||||
|
_scrollController.jumpTo(adjustedOffset.clamp(0.0, _scrollController.position.maxScrollExtent));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
print('添加新纪录失败: $e');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
double _getEstimatedItemHeight() {
|
||||||
|
if (_itemHeightCache > 0) {
|
||||||
|
return _itemHeightCache;
|
||||||
|
}
|
||||||
|
return 85.0;
|
||||||
|
}
|
||||||
|
|
||||||
bool _hasDataChanged(List<Object> newItems) {
|
bool _hasDataChanged(List<Object> newItems) {
|
||||||
if (_displayItems.length != newItems.length) return true;
|
if (_displayItems.length != newItems.length) return true;
|
||||||
@@ -489,7 +510,11 @@ class HistoryScreenState extends State<HistoryScreen> {
|
|||||||
final isSelected = _selectedRecords.contains(record.uniqueId);
|
final isSelected = _selectedRecords.contains(record.uniqueId);
|
||||||
final isExpanded =
|
final isExpanded =
|
||||||
!isSubCard && (_expandedStates[record.uniqueId] ?? false);
|
!isSubCard && (_expandedStates[record.uniqueId] ?? false);
|
||||||
return Card(
|
|
||||||
|
final GlobalKey itemKey = GlobalKey();
|
||||||
|
|
||||||
|
final Widget card = Card(
|
||||||
|
key: itemKey,
|
||||||
color: isSelected && _isEditMode
|
color: isSelected && _isEditMode
|
||||||
? const Color(0xFF2E2E2E)
|
? const Color(0xFF2E2E2E)
|
||||||
: const Color(0xFF1E1E1E),
|
: const Color(0xFF1E1E1E),
|
||||||
@@ -545,6 +570,20 @@ class HistoryScreenState extends State<HistoryScreen> {
|
|||||||
_buildLocoInfo(record),
|
_buildLocoInfo(record),
|
||||||
if (isExpanded) _buildExpandedContent(record)
|
if (isExpanded) _buildExpandedContent(record)
|
||||||
]))));
|
]))));
|
||||||
|
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (_itemHeightCache <= 0 && itemKey.currentContext != null) {
|
||||||
|
final RenderBox renderBox = itemKey.currentContext!.findRenderObject() as RenderBox;
|
||||||
|
final double realHeight = renderBox.size.height;
|
||||||
|
if (realHeight > 0) {
|
||||||
|
setState(() {
|
||||||
|
_itemHeightCache = realHeight;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return card;
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildRecordHeader(TrainRecord record, {bool isMerged = false}) {
|
Widget _buildRecordHeader(TrainRecord record, {bool isMerged = false}) {
|
||||||
|
|||||||
@@ -13,6 +13,158 @@ import 'package:lbjconsole/services/notification_service.dart';
|
|||||||
import 'package:lbjconsole/services/background_service.dart';
|
import 'package:lbjconsole/services/background_service.dart';
|
||||||
import 'package:lbjconsole/themes/app_theme.dart';
|
import 'package:lbjconsole/themes/app_theme.dart';
|
||||||
|
|
||||||
|
class _ConnectionStatusWidget extends StatefulWidget {
|
||||||
|
final BLEService bleService;
|
||||||
|
final DateTime? lastReceivedTime;
|
||||||
|
|
||||||
|
const _ConnectionStatusWidget({
|
||||||
|
required this.bleService,
|
||||||
|
required this.lastReceivedTime,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_ConnectionStatusWidget> createState() =>
|
||||||
|
_ConnectionStatusWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ConnectionStatusWidgetState extends State<_ConnectionStatusWidget> {
|
||||||
|
StreamSubscription? _connectionSubscription;
|
||||||
|
String _deviceStatus = "未连接";
|
||||||
|
bool _isConnected = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_connectionSubscription =
|
||||||
|
widget.bleService.connectionStream.listen((connected) {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_isConnected = connected;
|
||||||
|
_deviceStatus = connected ? "已连接" : "未连接";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
_isConnected = widget.bleService.isConnected;
|
||||||
|
_deviceStatus = widget.bleService.deviceStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_connectionSubscription?.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (widget.lastReceivedTime == null || !_isConnected) ...[
|
||||||
|
Text(_deviceStatus,
|
||||||
|
style: const TextStyle(color: Colors.white70, fontSize: 12)),
|
||||||
|
],
|
||||||
|
_LastReceivedTimeWidget(
|
||||||
|
lastReceivedTime: widget.lastReceivedTime,
|
||||||
|
isConnected: _isConnected,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Container(
|
||||||
|
width: 8,
|
||||||
|
height: 8,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: _isConnected ? Colors.green : Colors.red,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LastReceivedTimeWidget extends StatefulWidget {
|
||||||
|
final DateTime? lastReceivedTime;
|
||||||
|
final bool isConnected;
|
||||||
|
|
||||||
|
const _LastReceivedTimeWidget({
|
||||||
|
required this.lastReceivedTime,
|
||||||
|
required this.isConnected,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_LastReceivedTimeWidget> createState() =>
|
||||||
|
_LastReceivedTimeWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LastReceivedTimeWidgetState extends State<_LastReceivedTimeWidget> {
|
||||||
|
Timer? _timer;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_startTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(_LastReceivedTimeWidget oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
if (oldWidget.lastReceivedTime != widget.lastReceivedTime ||
|
||||||
|
oldWidget.isConnected != widget.isConnected) {
|
||||||
|
_startTimer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _startTimer() {
|
||||||
|
_timer?.cancel();
|
||||||
|
if (widget.lastReceivedTime != null && widget.isConnected) {
|
||||||
|
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatTime() {
|
||||||
|
if (widget.lastReceivedTime == null) return '';
|
||||||
|
|
||||||
|
final now = DateTime.now();
|
||||||
|
final difference = now.difference(widget.lastReceivedTime!);
|
||||||
|
|
||||||
|
if (difference.inDays > 0) {
|
||||||
|
return '${difference.inDays}天前';
|
||||||
|
} else if (difference.inHours > 0) {
|
||||||
|
return '${difference.inHours}小时前';
|
||||||
|
} else if (difference.inMinutes > 0) {
|
||||||
|
return '${difference.inMinutes}分钟前';
|
||||||
|
} else {
|
||||||
|
return '${difference.inSeconds}秒前';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_timer?.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (widget.lastReceivedTime == null || !widget.isConnected) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Text(
|
||||||
|
_formatTime(),
|
||||||
|
style: const TextStyle(color: Colors.white70, fontSize: 12),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class MainScreen extends StatefulWidget {
|
class MainScreen extends StatefulWidget {
|
||||||
const MainScreen({super.key});
|
const MainScreen({super.key});
|
||||||
|
|
||||||
@@ -28,7 +180,8 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
|
|||||||
|
|
||||||
StreamSubscription? _connectionSubscription;
|
StreamSubscription? _connectionSubscription;
|
||||||
StreamSubscription? _dataSubscription;
|
StreamSubscription? _dataSubscription;
|
||||||
|
StreamSubscription? _lastReceivedTimeSubscription;
|
||||||
|
DateTime? _lastReceivedTime;
|
||||||
bool _isHistoryEditMode = false;
|
bool _isHistoryEditMode = false;
|
||||||
final GlobalKey<HistoryScreenState> _historyScreenKey =
|
final GlobalKey<HistoryScreenState> _historyScreenKey =
|
||||||
GlobalKey<HistoryScreenState>();
|
GlobalKey<HistoryScreenState>();
|
||||||
@@ -41,21 +194,35 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
|
|||||||
_bleService.initialize();
|
_bleService.initialize();
|
||||||
_initializeServices();
|
_initializeServices();
|
||||||
_checkAndStartBackgroundService();
|
_checkAndStartBackgroundService();
|
||||||
|
_setupLastReceivedTimeListener();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _checkAndStartBackgroundService() async {
|
Future<void> _checkAndStartBackgroundService() async {
|
||||||
final settings = await DatabaseService.instance.getAllSettings() ?? {};
|
final settings = await DatabaseService.instance.getAllSettings() ?? {};
|
||||||
final backgroundServiceEnabled = (settings['backgroundServiceEnabled'] ?? 0) == 1;
|
final backgroundServiceEnabled =
|
||||||
|
(settings['backgroundServiceEnabled'] ?? 0) == 1;
|
||||||
|
|
||||||
if (backgroundServiceEnabled) {
|
if (backgroundServiceEnabled) {
|
||||||
await BackgroundService.startService();
|
await BackgroundService.startService();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _setupLastReceivedTimeListener() {
|
||||||
|
_lastReceivedTimeSubscription =
|
||||||
|
_bleService.lastReceivedTimeStream.listen((time) {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_lastReceivedTime = time;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_connectionSubscription?.cancel();
|
_connectionSubscription?.cancel();
|
||||||
_dataSubscription?.cancel();
|
_dataSubscription?.cancel();
|
||||||
|
_lastReceivedTimeSubscription?.cancel();
|
||||||
WidgetsBinding.instance.removeObserver(this);
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
@@ -70,10 +237,6 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
|
|||||||
Future<void> _initializeServices() async {
|
Future<void> _initializeServices() async {
|
||||||
await _notificationService.initialize();
|
await _notificationService.initialize();
|
||||||
|
|
||||||
_connectionSubscription = _bleService.connectionStream.listen((_) {
|
|
||||||
if (mounted) setState(() {});
|
|
||||||
});
|
|
||||||
|
|
||||||
_dataSubscription = _bleService.dataStream.listen((record) {
|
_dataSubscription = _bleService.dataStream.listen((record) {
|
||||||
_notificationService.showTrainNotification(record);
|
_notificationService.showTrainNotification(record);
|
||||||
if (_historyScreenKey.currentState != null) {
|
if (_historyScreenKey.currentState != null) {
|
||||||
@@ -133,17 +296,10 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
|
|||||||
actions: [
|
actions: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
_ConnectionStatusWidget(
|
||||||
width: 8,
|
bleService: _bleService,
|
||||||
height: 8,
|
lastReceivedTime: _lastReceivedTime,
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: _bleService.isConnected ? Colors.green : Colors.red,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
),
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Text(_bleService.deviceStatus,
|
|
||||||
style: const TextStyle(color: Colors.white70)),
|
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.bluetooth, color: Colors.white),
|
icon: const Icon(Icons.bluetooth, color: Colors.white),
|
||||||
onPressed: _showConnectionDialog,
|
onPressed: _showConnectionDialog,
|
||||||
@@ -274,6 +430,8 @@ class _PixelPerfectBluetoothDialogState
|
|||||||
List<BluetoothDevice> _devices = [];
|
List<BluetoothDevice> _devices = [];
|
||||||
_ScanState _scanState = _ScanState.initial;
|
_ScanState _scanState = _ScanState.initial;
|
||||||
StreamSubscription? _connectionSubscription;
|
StreamSubscription? _connectionSubscription;
|
||||||
|
StreamSubscription? _lastReceivedTimeSubscription;
|
||||||
|
DateTime? _lastReceivedTime;
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -288,6 +446,7 @@ class _PixelPerfectBluetoothDialogState
|
|||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_connectionSubscription?.cancel();
|
_connectionSubscription?.cancel();
|
||||||
|
_lastReceivedTimeSubscription?.cancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,6 +476,17 @@ class _PixelPerfectBluetoothDialogState
|
|||||||
await widget.bleService.disconnect();
|
await widget.bleService.disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _setupLastReceivedTimeListener() {
|
||||||
|
_lastReceivedTimeSubscription =
|
||||||
|
widget.bleService.lastReceivedTimeStream.listen((timestamp) {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_lastReceivedTime = timestamp;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final isConnected = widget.bleService.isConnected;
|
final isConnected = widget.bleService.isConnected;
|
||||||
@@ -353,6 +523,13 @@ class _PixelPerfectBluetoothDialogState
|
|||||||
Text(device?.remoteId.str ?? '',
|
Text(device?.remoteId.str ?? '',
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
textAlign: TextAlign.center),
|
textAlign: TextAlign.center),
|
||||||
|
if (_lastReceivedTime != null) ...[
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
_LastReceivedTimeWidget(
|
||||||
|
lastReceivedTime: _lastReceivedTime,
|
||||||
|
isConnected: widget.bleService.isConnected,
|
||||||
|
),
|
||||||
|
],
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
ElevatedButton.icon(
|
ElevatedButton.icon(
|
||||||
onPressed: _disconnect,
|
onPressed: _disconnect,
|
||||||
|
|||||||
@@ -395,9 +395,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.share, color: Theme.of(context).colorScheme.primary),
|
Icon(Icons.storage, color: Theme.of(context).colorScheme.primary),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Text('数据分享', style: AppTheme.titleMedium),
|
Text('数据管理', style: AppTheme.titleMedium),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|||||||
@@ -27,14 +27,19 @@ class BLEService {
|
|||||||
StreamController<TrainRecord>.broadcast();
|
StreamController<TrainRecord>.broadcast();
|
||||||
final StreamController<bool> _connectionController =
|
final StreamController<bool> _connectionController =
|
||||||
StreamController<bool>.broadcast();
|
StreamController<bool>.broadcast();
|
||||||
|
final StreamController<DateTime?> _lastReceivedTimeController =
|
||||||
|
StreamController<DateTime?>.broadcast();
|
||||||
|
|
||||||
Stream<String> get statusStream => _statusController.stream;
|
Stream<String> get statusStream => _statusController.stream;
|
||||||
Stream<TrainRecord> get dataStream => _dataController.stream;
|
Stream<TrainRecord> get dataStream => _dataController.stream;
|
||||||
Stream<bool> get connectionStream => _connectionController.stream;
|
Stream<bool> get connectionStream => _connectionController.stream;
|
||||||
|
Stream<DateTime?> get lastReceivedTimeStream =>
|
||||||
|
_lastReceivedTimeController.stream;
|
||||||
|
|
||||||
String _deviceStatus = "未连接";
|
String _deviceStatus = "未连接";
|
||||||
String? _lastKnownDeviceAddress;
|
String? _lastKnownDeviceAddress;
|
||||||
String _targetDeviceName = "LBJReceiver";
|
String _targetDeviceName = "LBJReceiver";
|
||||||
|
DateTime? _lastReceivedTime;
|
||||||
|
|
||||||
bool _isConnecting = false;
|
bool _isConnecting = false;
|
||||||
bool _isManualDisconnect = false;
|
bool _isManualDisconnect = false;
|
||||||
@@ -69,8 +74,7 @@ class BLEService {
|
|||||||
if (settings != null) {
|
if (settings != null) {
|
||||||
_targetDeviceName = settings['deviceName'] ?? 'LBJReceiver';
|
_targetDeviceName = settings['deviceName'] ?? 'LBJReceiver';
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ensureConnection() {
|
void ensureConnection() {
|
||||||
@@ -315,6 +319,9 @@ class BLEService {
|
|||||||
'${now.millisecondsSinceEpoch}_${Random().nextInt(9999)}';
|
'${now.millisecondsSinceEpoch}_${Random().nextInt(9999)}';
|
||||||
recordData['receivedTimestamp'] = now.millisecondsSinceEpoch;
|
recordData['receivedTimestamp'] = now.millisecondsSinceEpoch;
|
||||||
|
|
||||||
|
_lastReceivedTime = now;
|
||||||
|
_lastReceivedTimeController.add(_lastReceivedTime);
|
||||||
|
|
||||||
final trainRecord = TrainRecord.fromJson(recordData);
|
final trainRecord = TrainRecord.fromJson(recordData);
|
||||||
_dataController.add(trainRecord);
|
_dataController.add(trainRecord);
|
||||||
DatabaseService.instance.insertRecord(trainRecord);
|
DatabaseService.instance.insertRecord(trainRecord);
|
||||||
@@ -331,6 +338,8 @@ class BLEService {
|
|||||||
_deviceStatus = status;
|
_deviceStatus = status;
|
||||||
_connectedDevice = null;
|
_connectedDevice = null;
|
||||||
_characteristic = null;
|
_characteristic = null;
|
||||||
|
_lastReceivedTime = null;
|
||||||
|
_lastReceivedTimeController.add(null);
|
||||||
}
|
}
|
||||||
_statusController.add(_deviceStatus);
|
_statusController.add(_deviceStatus);
|
||||||
_connectionController.add(connected);
|
_connectionController.add(connected);
|
||||||
@@ -357,5 +366,6 @@ class BLEService {
|
|||||||
_statusController.close();
|
_statusController.close();
|
||||||
_dataController.close();
|
_dataController.close();
|
||||||
_connectionController.close();
|
_connectionController.close();
|
||||||
|
_lastReceivedTimeController.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ class NotificationService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final String title = '列车信息更新';
|
final String title = '列车信息';
|
||||||
final String body = _buildNotificationContent(record);
|
final String body = _buildNotificationContent(record);
|
||||||
|
|
||||||
final AndroidNotificationDetails androidPlatformChannelSpecifics =
|
final AndroidNotificationDetails androidPlatformChannelSpecifics =
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
// 'flutter create' template.
|
// 'flutter create' template.
|
||||||
|
|
||||||
// The application's name. By default this is also the title of the Flutter window.
|
// The application's name. By default this is also the title of the Flutter window.
|
||||||
PRODUCT_NAME = lbjconsole
|
PRODUCT_NAME = LBJ Console
|
||||||
|
|
||||||
// The application's bundle identifier
|
// The application's bundle identifier
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.noxylva.lbjconsole
|
PRODUCT_BUNDLE_IDENTIFIER = org.noxylva.lbjconsole
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
# In Windows, build-name is used as the major, minor, and patch parts
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 0.1.7-flutter
|
version: 0.2.0-flutter+20 # versionName: 0.2.0-flutter, versionCode: 3
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.5.4
|
sdk: ^3.5.4
|
||||||
|
|||||||
Reference in New Issue
Block a user