Compare commits
2 Commits
v0.5.2-flu
...
v0.6.1-flu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6073ea615e | ||
|
|
5533df92b5 |
@@ -3,15 +3,16 @@
|
|||||||
LBJ Console 是一款应用程序,用于通过 BLE 从 [SX1276_Receive_LBJ](https://github.com/undef-i/SX1276_Receive_LBJ) 设备接收并显示列车预警消息,功能包括:
|
LBJ Console 是一款应用程序,用于通过 BLE 从 [SX1276_Receive_LBJ](https://github.com/undef-i/SX1276_Receive_LBJ) 设备接收并显示列车预警消息,功能包括:
|
||||||
|
|
||||||
- 接收列车预警消息,支持可选的手机推送通知。
|
- 接收列车预警消息,支持可选的手机推送通知。
|
||||||
|
- 监控指定列车的轨迹,在地图上显示。
|
||||||
- 在地图上显示预警消息的 GPS 信息。
|
- 在地图上显示预警消息的 GPS 信息。
|
||||||
- 基于内置数据文件显示机车配属,机车类型和车次类型。
|
- 基于内置数据文件显示机车配属,机车类型和车次类型。
|
||||||
|
|
||||||
[android](https://github.com/undef-i/LBJ_Console/tree/android) 分支包含项目早期基于 Android 平台的实现代码,已实现基本功能,现已停止开发。
|
[android](https://github.com/undef-i/LBJ_Console/tree/android) 分支包含项目早期基于 Android 平台的实现代码,已实现基本功能,现已停止开发。
|
||||||
|
|
||||||
|
|
||||||
## 数据文件
|
## 数据文件
|
||||||
|
|
||||||
LBJ Console 依赖以下数据文件,位于 `assets` 目录,用于支持机车配属和车次信息的展示:
|
LBJ Console 依赖以下数据文件,位于 `assets` 目录,用于支持机车配属和车次信息的展示:
|
||||||
|
|
||||||
- `loco_info.csv`:包含机车配属信息,格式为 `机车型号,机车编号起始值,机车编号结束值,所属铁路局及机务段,备注`。
|
- `loco_info.csv`:包含机车配属信息,格式为 `机车型号,机车编号起始值,机车编号结束值,所属铁路局及机务段,备注`。
|
||||||
- `loco_type_info.csv`:包含机车类型编码信息,格式为 `机车类型编码前缀,机车类型`。
|
- `loco_type_info.csv`:包含机车类型编码信息,格式为 `机车类型编码前缀,机车类型`。
|
||||||
- `train_info.csv`:包含车次类型信息,格式为 `正则表达式,车次类型`。
|
- `train_info.csv`:包含车次类型信息,格式为 `正则表达式,车次类型`。
|
||||||
|
|||||||
@@ -221,28 +221,43 @@ class HistoryScreenState extends State<HistoryScreen> {
|
|||||||
if (mounted) {
|
if (mounted) {
|
||||||
if (_isAtTop) {
|
if (_isAtTop) {
|
||||||
setState(() {
|
setState(() {
|
||||||
bool isMerge = false;
|
List<TrainRecord> allRecords = [];
|
||||||
Object? mergeResult;
|
Set<String> selectedRecordIds = {};
|
||||||
if (_displayItems.isNotEmpty) {
|
|
||||||
final firstItem = _displayItems.first;
|
for (final item in _displayItems) {
|
||||||
List<TrainRecord> tempRecords = [newRecord];
|
if (item is MergedTrainRecord) {
|
||||||
if (firstItem is MergedTrainRecord) {
|
allRecords.addAll(item.records);
|
||||||
tempRecords.addAll(firstItem.records);
|
if (_selectedRecords.contains(item.records.first.uniqueId)) {
|
||||||
} else if (firstItem is TrainRecord) {
|
selectedRecordIds.addAll(item.records.map((r) => r.uniqueId));
|
||||||
tempRecords.add(firstItem);
|
}
|
||||||
}
|
} else if (item is TrainRecord) {
|
||||||
final mergeCheckResult =
|
allRecords.add(item);
|
||||||
MergeService.getMixedList(tempRecords, _mergeSettings);
|
if (_selectedRecords.contains(item.uniqueId)) {
|
||||||
if (mergeCheckResult.length == 1 &&
|
selectedRecordIds.add(item.uniqueId);
|
||||||
mergeCheckResult.first is MergedTrainRecord) {
|
}
|
||||||
isMerge = true;
|
|
||||||
mergeResult = mergeCheckResult.first;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isMerge) {
|
|
||||||
_displayItems[0] = mergeResult!;
|
allRecords.insert(0, newRecord);
|
||||||
} else {
|
|
||||||
_displayItems.insert(0, newRecord);
|
final mergedItems =
|
||||||
|
MergeService.getMixedList(allRecords, _mergeSettings);
|
||||||
|
|
||||||
|
_displayItems.clear();
|
||||||
|
_displayItems.addAll(mergedItems);
|
||||||
|
|
||||||
|
_selectedRecords.clear();
|
||||||
|
for (final item in _displayItems) {
|
||||||
|
if (item is MergedTrainRecord) {
|
||||||
|
if (item.records
|
||||||
|
.any((r) => selectedRecordIds.contains(r.uniqueId))) {
|
||||||
|
_selectedRecords.addAll(item.records.map((r) => r.uniqueId));
|
||||||
|
}
|
||||||
|
} else if (item is TrainRecord) {
|
||||||
|
if (selectedRecordIds.contains(item.uniqueId)) {
|
||||||
|
_selectedRecords.add(item.uniqueId);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (_scrollController.hasClients) {
|
if (_scrollController.hasClients) {
|
||||||
@@ -723,8 +738,6 @@ class HistoryScreenState extends State<HistoryScreen> {
|
|||||||
Widget _buildRecordCard(TrainRecord record,
|
Widget _buildRecordCard(TrainRecord record,
|
||||||
{bool isSubCard = false, Key? key}) {
|
{bool isSubCard = false, Key? key}) {
|
||||||
final isSelected = _selectedRecords.contains(record.uniqueId);
|
final isSelected = _selectedRecords.contains(record.uniqueId);
|
||||||
final isExpanded =
|
|
||||||
!isSubCard && (_expandedStates[record.uniqueId] ?? false);
|
|
||||||
|
|
||||||
return Card(
|
return Card(
|
||||||
key: key,
|
key: key,
|
||||||
@@ -752,6 +765,11 @@ class HistoryScreenState extends State<HistoryScreen> {
|
|||||||
}
|
}
|
||||||
widget.onSelectionChanged();
|
widget.onSelectionChanged();
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
setState(() {
|
||||||
|
_expandedStates[record.uniqueId] =
|
||||||
|
!(_expandedStates[record.uniqueId] ?? false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLongPress: () {
|
onLongPress: () {
|
||||||
@@ -771,7 +789,8 @@ class HistoryScreenState extends State<HistoryScreen> {
|
|||||||
_buildRecordHeader(record),
|
_buildRecordHeader(record),
|
||||||
_buildPositionAndSpeed(record),
|
_buildPositionAndSpeed(record),
|
||||||
_buildLocoInfo(record),
|
_buildLocoInfo(record),
|
||||||
if (isExpanded) _buildExpandedContent(record),
|
if (_expandedStates[record.uniqueId] ?? false)
|
||||||
|
_buildExpandedContent(record),
|
||||||
]))));
|
]))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -803,6 +822,7 @@ class HistoryScreenState extends State<HistoryScreen> {
|
|||||||
final hasLocoInfo =
|
final hasLocoInfo =
|
||||||
formattedLocoInfo.isNotEmpty && formattedLocoInfo != "<NUL>";
|
formattedLocoInfo.isNotEmpty && formattedLocoInfo != "<NUL>";
|
||||||
final shouldShowTrainRow = hasTrainNumber || hasDirection || hasLocoInfo;
|
final shouldShowTrainRow = hasTrainNumber || hasDirection || hasLocoInfo;
|
||||||
|
final hasPosition = _parsePosition(record.positionInfo) != null;
|
||||||
|
|
||||||
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
|
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
|
||||||
@@ -856,7 +876,8 @@ class HistoryScreenState extends State<HistoryScreen> {
|
|||||||
])),
|
])),
|
||||||
if (hasLocoInfo)
|
if (hasLocoInfo)
|
||||||
Text(formattedLocoInfo,
|
Text(formattedLocoInfo,
|
||||||
style: const TextStyle(fontSize: 14, color: Colors.white70))
|
style:
|
||||||
|
const TextStyle(fontSize: 14, color: Colors.white70)),
|
||||||
]),
|
]),
|
||||||
const SizedBox(height: 2)
|
const SizedBox(height: 2)
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import 'package:lbjconsole/models/train_record.dart';
|
|||||||
import 'package:lbjconsole/screens/history_screen.dart';
|
import 'package:lbjconsole/screens/history_screen.dart';
|
||||||
import 'package:lbjconsole/screens/map_screen.dart';
|
import 'package:lbjconsole/screens/map_screen.dart';
|
||||||
import 'package:lbjconsole/screens/map_webview_screen.dart';
|
import 'package:lbjconsole/screens/map_webview_screen.dart';
|
||||||
|
import 'package:lbjconsole/screens/realtime_screen.dart';
|
||||||
import 'package:lbjconsole/screens/settings_screen.dart';
|
import 'package:lbjconsole/screens/settings_screen.dart';
|
||||||
import 'package:lbjconsole/services/ble_service.dart';
|
import 'package:lbjconsole/services/ble_service.dart';
|
||||||
import 'package:lbjconsole/services/database_service.dart';
|
import 'package:lbjconsole/services/database_service.dart';
|
||||||
@@ -183,10 +184,13 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
|
|||||||
StreamSubscription? _connectionSubscription;
|
StreamSubscription? _connectionSubscription;
|
||||||
StreamSubscription? _dataSubscription;
|
StreamSubscription? _dataSubscription;
|
||||||
StreamSubscription? _lastReceivedTimeSubscription;
|
StreamSubscription? _lastReceivedTimeSubscription;
|
||||||
|
StreamSubscription? _settingsSubscription;
|
||||||
DateTime? _lastReceivedTime;
|
DateTime? _lastReceivedTime;
|
||||||
bool _isHistoryEditMode = false;
|
bool _isHistoryEditMode = false;
|
||||||
final GlobalKey<HistoryScreenState> _historyScreenKey =
|
final GlobalKey<HistoryScreenState> _historyScreenKey =
|
||||||
GlobalKey<HistoryScreenState>();
|
GlobalKey<HistoryScreenState>();
|
||||||
|
final GlobalKey<RealtimeScreenState> _realtimeScreenKey =
|
||||||
|
GlobalKey<RealtimeScreenState>();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -197,6 +201,7 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
|
|||||||
_initializeServices();
|
_initializeServices();
|
||||||
_checkAndStartBackgroundService();
|
_checkAndStartBackgroundService();
|
||||||
_setupLastReceivedTimeListener();
|
_setupLastReceivedTimeListener();
|
||||||
|
_setupSettingsListener();
|
||||||
_loadMapType();
|
_loadMapType();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,7 +214,6 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Future<void> _checkAndStartBackgroundService() async {
|
Future<void> _checkAndStartBackgroundService() async {
|
||||||
final settings = await DatabaseService.instance.getAllSettings() ?? {};
|
final settings = await DatabaseService.instance.getAllSettings() ?? {};
|
||||||
final backgroundServiceEnabled =
|
final backgroundServiceEnabled =
|
||||||
@@ -231,11 +235,21 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _setupSettingsListener() {
|
||||||
|
_settingsSubscription =
|
||||||
|
DatabaseService.instance.onSettingsChanged((settings) {
|
||||||
|
if (mounted && _currentIndex == 1) {
|
||||||
|
_realtimeScreenKey.currentState?.loadRecords(scrollToTop: false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_connectionSubscription?.cancel();
|
_connectionSubscription?.cancel();
|
||||||
_dataSubscription?.cancel();
|
_dataSubscription?.cancel();
|
||||||
_lastReceivedTimeSubscription?.cancel();
|
_lastReceivedTimeSubscription?.cancel();
|
||||||
|
_settingsSubscription?.cancel();
|
||||||
WidgetsBinding.instance.removeObserver(this);
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
@@ -256,6 +270,9 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
|
|||||||
if (_historyScreenKey.currentState != null) {
|
if (_historyScreenKey.currentState != null) {
|
||||||
_historyScreenKey.currentState!.addNewRecord(record);
|
_historyScreenKey.currentState!.addNewRecord(record);
|
||||||
}
|
}
|
||||||
|
if (_realtimeScreenKey.currentState != null) {
|
||||||
|
_realtimeScreenKey.currentState!.addNewRecord(record);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,7 +319,7 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
|
|||||||
backgroundColor: AppTheme.primaryBlack,
|
backgroundColor: AppTheme.primaryBlack,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
title: Text(
|
title: Text(
|
||||||
['列车记录', '位置地图', '设置'][_currentIndex],
|
['列车记录', '数据监控', '位置地图', '设置'][_currentIndex],
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold),
|
color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
@@ -394,6 +411,9 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
|
|||||||
onEditModeChanged: _handleHistoryEditModeChanged,
|
onEditModeChanged: _handleHistoryEditModeChanged,
|
||||||
onSelectionChanged: _handleSelectionChanged,
|
onSelectionChanged: _handleSelectionChanged,
|
||||||
),
|
),
|
||||||
|
RealtimeScreen(
|
||||||
|
key: _realtimeScreenKey,
|
||||||
|
),
|
||||||
_mapType == 'map' ? const MapScreen() : const MapWebViewScreen(),
|
_mapType == 'map' ? const MapScreen() : const MapWebViewScreen(),
|
||||||
SettingsScreen(
|
SettingsScreen(
|
||||||
onSettingsChanged: () {
|
onSettingsChanged: () {
|
||||||
@@ -411,14 +431,16 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
|
|||||||
),
|
),
|
||||||
bottomNavigationBar: NavigationBar(
|
bottomNavigationBar: NavigationBar(
|
||||||
backgroundColor: AppTheme.secondaryBlack,
|
backgroundColor: AppTheme.secondaryBlack,
|
||||||
indicatorColor: AppTheme.accentBlue.withOpacity(0.2),
|
indicatorColor: AppTheme.accentBlue.withValues(alpha: 0.2),
|
||||||
selectedIndex: _currentIndex,
|
selectedIndex: _currentIndex,
|
||||||
onDestinationSelected: (index) {
|
onDestinationSelected: (index) {
|
||||||
if (_currentIndex == 2 && index == 0) {
|
if (index == 0) {
|
||||||
_historyScreenKey.currentState?.reloadRecords();
|
_historyScreenKey.currentState?.reloadRecords();
|
||||||
}
|
}
|
||||||
// 如果从设置页面切换到地图页面,重新加载地图类型
|
if (index == 1) {
|
||||||
if (_currentIndex == 2 && index == 1) {
|
_realtimeScreenKey.currentState?.loadRecords(scrollToTop: false);
|
||||||
|
}
|
||||||
|
if (_currentIndex == 3 && index == 2) {
|
||||||
_loadMapType();
|
_loadMapType();
|
||||||
}
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -429,6 +451,7 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
|
|||||||
destinations: const [
|
destinations: const [
|
||||||
NavigationDestination(
|
NavigationDestination(
|
||||||
icon: Icon(Icons.directions_railway), label: '列车记录'),
|
icon: Icon(Icons.directions_railway), label: '列车记录'),
|
||||||
|
NavigationDestination(icon: Icon(Icons.speed), label: '数据监控'),
|
||||||
NavigationDestination(icon: Icon(Icons.location_on), label: '位置地图'),
|
NavigationDestination(icon: Icon(Icons.location_on), label: '位置地图'),
|
||||||
NavigationDestination(icon: Icon(Icons.settings), label: '设置'),
|
NavigationDestination(icon: Icon(Icons.settings), label: '设置'),
|
||||||
],
|
],
|
||||||
|
|||||||
1286
lib/screens/realtime_screen.dart
Normal file
1286
lib/screens/realtime_screen.dart
Normal file
File diff suppressed because it is too large
Load Diff
@@ -232,16 +232,28 @@ class DatabaseService {
|
|||||||
|
|
||||||
Future<int> deleteRecord(String uniqueId) async {
|
Future<int> deleteRecord(String uniqueId) async {
|
||||||
final db = await database;
|
final db = await database;
|
||||||
return await db.delete(
|
final result = await db.delete(
|
||||||
trainRecordsTable,
|
trainRecordsTable,
|
||||||
where: 'uniqueId = ?',
|
where: 'uniqueId = ?',
|
||||||
whereArgs: [uniqueId],
|
whereArgs: [uniqueId],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (result > 0) {
|
||||||
|
_notifyRecordDeleted([uniqueId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> deleteAllRecords() async {
|
Future<int> deleteAllRecords() async {
|
||||||
final db = await database;
|
final db = await database;
|
||||||
return await db.delete(trainRecordsTable);
|
final result = await db.delete(trainRecordsTable);
|
||||||
|
|
||||||
|
if (result > 0) {
|
||||||
|
_notifyRecordDeleted([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> getRecordCount() async {
|
Future<int> getRecordCount() async {
|
||||||
@@ -279,20 +291,31 @@ class DatabaseService {
|
|||||||
|
|
||||||
Future<int> updateSettings(Map<String, dynamic> settings) async {
|
Future<int> updateSettings(Map<String, dynamic> settings) async {
|
||||||
final db = await database;
|
final db = await database;
|
||||||
return await db.update(
|
final result = await db.update(
|
||||||
appSettingsTable,
|
appSettingsTable,
|
||||||
settings,
|
settings,
|
||||||
where: 'id = 1',
|
where: 'id = 1',
|
||||||
);
|
);
|
||||||
|
if (result > 0) {
|
||||||
|
_notifySettingsChanged(settings);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> setSetting(String key, dynamic value) async {
|
Future<int> setSetting(String key, dynamic value) async {
|
||||||
final db = await database;
|
final db = await database;
|
||||||
return await db.update(
|
final result = await db.update(
|
||||||
appSettingsTable,
|
appSettingsTable,
|
||||||
{key: value},
|
{key: value},
|
||||||
where: 'id = 1',
|
where: 'id = 1',
|
||||||
);
|
);
|
||||||
|
if (result > 0) {
|
||||||
|
final currentSettings = await getAllSettings();
|
||||||
|
if (currentSettings != null) {
|
||||||
|
_notifySettingsChanged(currentSettings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<String>> getSearchOrderList() async {
|
Future<List<String>> getSearchOrderList() async {
|
||||||
@@ -349,6 +372,42 @@ class DatabaseService {
|
|||||||
whereArgs: [id],
|
whereArgs: [id],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
_notifyRecordDeleted(uniqueIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<Function(List<String>)> _recordDeleteListeners = [];
|
||||||
|
|
||||||
|
final List<Function(Map<String, dynamic>)> _settingsListeners = [];
|
||||||
|
|
||||||
|
StreamSubscription<void> onRecordDeleted(Function(List<String>) listener) {
|
||||||
|
_recordDeleteListeners.add(listener);
|
||||||
|
return Stream.value(null).listen((_) {})
|
||||||
|
..onData((_) {})
|
||||||
|
..onDone(() {
|
||||||
|
_recordDeleteListeners.remove(listener);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _notifyRecordDeleted(List<String> deletedIds) {
|
||||||
|
for (final listener in _recordDeleteListeners) {
|
||||||
|
listener(deletedIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamSubscription<void> onSettingsChanged(
|
||||||
|
Function(Map<String, dynamic>) listener) {
|
||||||
|
_settingsListeners.add(listener);
|
||||||
|
return Stream.value(null).listen((_) {})
|
||||||
|
..onData((_) {})
|
||||||
|
..onDone(() {
|
||||||
|
_settingsListeners.remove(listener);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _notifySettingsChanged(Map<String, dynamic> settings) {
|
||||||
|
for (final listener in _settingsListeners) {
|
||||||
|
listener(settings);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
@@ -404,6 +463,11 @@ class DatabaseService {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final currentSettings = await getAllSettings();
|
||||||
|
if (currentSettings != null) {
|
||||||
|
_notifySettingsChanged(currentSettings);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -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.5.2-flutter+52
|
version: 0.6.1-flutter+61
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.5.4
|
sdk: ^3.5.4
|
||||||
|
|||||||
Reference in New Issue
Block a user