From 64401a6ce983659ea40e328ca64201131818e23f Mon Sep 17 00:00:00 2001 From: Nedifinita Date: Thu, 25 Sep 2025 22:47:12 +0800 Subject: [PATCH] feat: add the function to hide records that are only valid for time. --- lib/screens/history_screen.dart | 128 ++++++++++++++++++++++++++++- lib/screens/main_screen.dart | 2 +- lib/screens/settings_screen.dart | 26 ++++++ lib/services/database_service.dart | 12 ++- pubspec.yaml | 2 +- 5 files changed, 164 insertions(+), 6 deletions(-) diff --git a/lib/screens/history_screen.dart b/lib/screens/history_screen.dart index f7acb1d..1d58bf5 100644 --- a/lib/screens/history_screen.dart +++ b/lib/screens/history_screen.dart @@ -60,6 +60,10 @@ class HistoryScreenState extends State { }); } + Future reloadRecords() async { + await loadRecords(scrollToTop: false); + } + @override void initState() { super.initState(); @@ -93,7 +97,71 @@ class HistoryScreenState extends State { final allRecords = await DatabaseService.instance.getAllRecords(); final settingsMap = await DatabaseService.instance.getAllSettings() ?? {}; _mergeSettings = MergeSettings.fromMap(settingsMap); - final items = MergeService.getMixedList(allRecords, _mergeSettings); + + List filteredRecords = allRecords; + if ((settingsMap['hideTimeOnlyRecords'] ?? 0) == 1) { + int hiddenCount = 0; + int shownCount = 0; + + filteredRecords = allRecords.where((record) { + bool isFieldMeaningful(String field) { + if (field.isEmpty) return false; + String cleaned = field.replaceAll('', '').trim(); + if (cleaned.isEmpty) return false; + if (cleaned.runes + .every((r) => r == '*'.runes.first || r == ' '.runes.first)) + return false; + return true; + } + + final hasTrainNumber = isFieldMeaningful(record.fullTrainNumber) && + !record.fullTrainNumber.contains("-----"); + + final hasDirection = record.direction == 1 || record.direction == 3; + + final hasLocoInfo = isFieldMeaningful(record.locoType) || + isFieldMeaningful(record.loco); + + final hasRoute = isFieldMeaningful(record.route); + + final hasPosition = isFieldMeaningful(record.position); + + final hasSpeed = + isFieldMeaningful(record.speed) && record.speed != "NUL"; + + final hasPositionInfo = isFieldMeaningful(record.positionInfo); + + final hasTrainType = + isFieldMeaningful(record.trainType) && record.trainType != "未知"; + + final hasLbjClass = + isFieldMeaningful(record.lbjClass) && record.lbjClass != "NA"; + + final hasTrain = isFieldMeaningful(record.train) && + !record.train.contains("-----"); + + final shouldShow = hasTrainNumber || + hasDirection || + hasLocoInfo || + hasRoute || + hasPosition || + hasSpeed || + hasPositionInfo || + hasTrainType || + hasLbjClass || + hasTrain; + + if (!shouldShow) { + hiddenCount++; + } else { + shownCount++; + } + + return shouldShow; + }).toList(); + } + + final items = MergeService.getMixedList(filteredRecords, _mergeSettings); if (mounted) { final hasDataChanged = _hasDataChanged(items); @@ -124,6 +192,58 @@ class HistoryScreenState extends State { final settingsMap = await DatabaseService.instance.getAllSettings() ?? {}; _mergeSettings = MergeSettings.fromMap(settingsMap); + if ((settingsMap['hideTimeOnlyRecords'] ?? 0) == 1) { + bool isFieldMeaningful(String field) { + if (field.isEmpty) return false; + String cleaned = field.replaceAll('', '').trim(); + if (cleaned.isEmpty) return false; + if (cleaned.runes + .every((r) => r == '*'.runes.first || r == ' '.runes.first)) + return false; + return true; + } + + final hasTrainNumber = isFieldMeaningful(newRecord.fullTrainNumber) && + !newRecord.fullTrainNumber.contains("-----"); + + final hasDirection = + newRecord.direction == 1 || newRecord.direction == 3; + + final hasLocoInfo = isFieldMeaningful(newRecord.locoType) || + isFieldMeaningful(newRecord.loco); + + final hasRoute = isFieldMeaningful(newRecord.route); + + final hasPosition = isFieldMeaningful(newRecord.position); + + final hasSpeed = + isFieldMeaningful(newRecord.speed) && newRecord.speed != "NUL"; + + final hasPositionInfo = isFieldMeaningful(newRecord.positionInfo); + + final hasTrainType = isFieldMeaningful(newRecord.trainType) && + newRecord.trainType != "未知"; + + final hasLbjClass = + isFieldMeaningful(newRecord.lbjClass) && newRecord.lbjClass != "NA"; + + final hasTrain = isFieldMeaningful(newRecord.train) && + !newRecord.train.contains("-----"); + + if (!hasTrainNumber && + !hasDirection && + !hasLocoInfo && + !hasRoute && + !hasPosition && + !hasSpeed && + !hasPositionInfo && + !hasTrainType && + !hasLbjClass && + !hasTrain) { + return; + } + } + final isNewRecord = !_displayItems.any((item) { if (item is TrainRecord) { return item.uniqueId == newRecord.uniqueId; @@ -624,7 +744,8 @@ class HistoryScreenState extends State { final hasTrainNumber = record.fullTrainNumber.isNotEmpty; final hasDirection = record.direction == 1 || record.direction == 3; - final hasLocoInfo = formattedLocoInfo.isNotEmpty && formattedLocoInfo != ""; + final hasLocoInfo = + formattedLocoInfo.isNotEmpty && formattedLocoInfo != ""; final shouldShowTrainRow = hasTrainNumber || hasDirection || hasLocoInfo; return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -661,7 +782,8 @@ class HistoryScreenState extends State { fontWeight: FontWeight.bold, color: Colors.white), overflow: TextOverflow.ellipsis)), - if (hasTrainNumber && hasDirection) const SizedBox(width: 6), + if (hasTrainNumber && hasDirection) + const SizedBox(width: 6), if (hasDirection) Container( width: 20, diff --git a/lib/screens/main_screen.dart b/lib/screens/main_screen.dart index 6fc6646..5f4a606 100644 --- a/lib/screens/main_screen.dart +++ b/lib/screens/main_screen.dart @@ -397,7 +397,7 @@ class _MainScreenState extends State with WidgetsBindingObserver { selectedIndex: _currentIndex, onDestinationSelected: (index) { if (_currentIndex == 2 && index == 0) { - _historyScreenKey.currentState?.loadRecords(); + _historyScreenKey.currentState?.reloadRecords(); } setState(() { if (_isHistoryEditMode) _isHistoryEditMode = false; diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index 7cb4866..e542207 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -32,6 +32,7 @@ class _SettingsScreenState extends State { bool _notificationsEnabled = true; int _recordCount = 0; bool _mergeRecordsEnabled = false; + bool _hideTimeOnlyRecords = false; GroupBy _groupBy = GroupBy.trainAndLoco; TimeWindow _timeWindow = TimeWindow.unlimited; @@ -61,6 +62,7 @@ class _SettingsScreenState extends State { (settingsMap['backgroundServiceEnabled'] ?? 0) == 1; _notificationsEnabled = (settingsMap['notificationEnabled'] ?? 1) == 1; _mergeRecordsEnabled = settings.enabled; + _hideTimeOnlyRecords = (settingsMap['hideTimeOnlyRecords'] ?? 0) == 1; _groupBy = settings.groupBy; _timeWindow = settings.timeWindow; }); @@ -82,6 +84,7 @@ class _SettingsScreenState extends State { 'backgroundServiceEnabled': _backgroundServiceEnabled ? 1 : 0, 'notificationEnabled': _notificationsEnabled ? 1 : 0, 'mergeRecordsEnabled': _mergeRecordsEnabled ? 1 : 0, + 'hideTimeOnlyRecords': _hideTimeOnlyRecords ? 1 : 0, 'groupBy': _groupBy.name, 'timeWindow': _timeWindow.name, }); @@ -236,6 +239,29 @@ class _SettingsScreenState extends State { ), ], ), + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('隐藏只有时间有效的记录', style: AppTheme.bodyLarge), + Text('不显示只有时间信息的记录', style: AppTheme.caption), + ], + ), + Switch( + value: _hideTimeOnlyRecords, + onChanged: (value) { + setState(() { + _hideTimeOnlyRecords = value; + }); + _saveSettings(); + }, + activeColor: Theme.of(context).colorScheme.primary, + ), + ], + ), ], ), ), diff --git a/lib/services/database_service.dart b/lib/services/database_service.dart index e2c18c9..2bce1c7 100644 --- a/lib/services/database_service.dart +++ b/lib/services/database_service.dart @@ -13,7 +13,7 @@ class DatabaseService { DatabaseService._internal(); static const String _databaseName = 'train_database'; - static const _databaseVersion = 1; + static const _databaseVersion = 2; static const String trainRecordsTable = 'train_records'; static const String appSettingsTable = 'app_settings'; @@ -34,9 +34,17 @@ class DatabaseService { path, version: _databaseVersion, onCreate: _onCreate, + onUpgrade: _onUpgrade, ); } + Future _onUpgrade(Database db, int oldVersion, int newVersion) async { + if (oldVersion < 2) { + await db.execute( + 'ALTER TABLE $appSettingsTable ADD COLUMN hideTimeOnlyRecords INTEGER NOT NULL DEFAULT 0'); + } + } + Future _onCreate(Database db, int version) async { await db.execute(''' CREATE TABLE IF NOT EXISTS $trainRecordsTable ( @@ -79,6 +87,7 @@ class DatabaseService { backgroundServiceEnabled INTEGER NOT NULL DEFAULT 0, notificationEnabled INTEGER NOT NULL DEFAULT 0, mergeRecordsEnabled INTEGER NOT NULL DEFAULT 0, + hideTimeOnlyRecords INTEGER NOT NULL DEFAULT 0, groupBy TEXT NOT NULL DEFAULT 'trainAndLoco', timeWindow TEXT NOT NULL DEFAULT 'unlimited' ) @@ -102,6 +111,7 @@ class DatabaseService { 'backgroundServiceEnabled': 0, 'notificationEnabled': 0, 'mergeRecordsEnabled': 0, + 'hideTimeOnlyRecords': 0, 'groupBy': 'trainAndLoco', 'timeWindow': 'unlimited', }); diff --git a/pubspec.yaml b/pubspec.yaml index cdf6915..db6b9bd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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 # 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. -version: 0.2.0-flutter+20 # versionName: 0.2.0-flutter, versionCode: 3 +version: 0.3.0-flutter+30 environment: sdk: ^3.5.4