import 'dart:async'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:lbjconsole/models/train_record.dart'; class NotificationService { static const String channelId = 'lbj_messages'; static const String channelName = 'LBJ Messages'; static const String channelDescription = 'Receive LBJ messages'; final FlutterLocalNotificationsPlugin _notificationsPlugin = FlutterLocalNotificationsPlugin(); int _notificationId = 1000; bool _notificationsEnabled = true; final StreamController _settingsController = StreamController.broadcast(); Stream get settingsStream => _settingsController.stream; Future initialize() async { const AndroidInitializationSettings initializationSettingsAndroid = AndroidInitializationSettings('@mipmap/ic_launcher'); final InitializationSettings initializationSettings = InitializationSettings( android: initializationSettingsAndroid, ); await _notificationsPlugin.initialize( initializationSettings, onDidReceiveNotificationResponse: (details) {}, ); await _createNotificationChannel(); _notificationsEnabled = await isNotificationEnabled(); _settingsController.add(_notificationsEnabled); } Future _createNotificationChannel() async { const AndroidNotificationChannel channel = AndroidNotificationChannel( channelId, channelName, description: channelDescription, importance: Importance.high, enableVibration: true, playSound: true, ); await _notificationsPlugin .resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin>() ?.createNotificationChannel(channel); } Future showTrainNotification(TrainRecord record) async { if (!_notificationsEnabled) return; if (!_isValidValue(record.train) || !_isValidValue(record.route) || !_isValidValue(record.directionText)) { return; } final String title = '列车信息'; final String body = _buildNotificationContent(record); final AndroidNotificationDetails androidPlatformChannelSpecifics = AndroidNotificationDetails( channelId, channelName, channelDescription: channelDescription, importance: Importance.high, priority: Priority.high, ticker: 'ticker', styleInformation: BigTextStyleInformation(body), ); final NotificationDetails platformChannelSpecifics = NotificationDetails(android: androidPlatformChannelSpecifics); await _notificationsPlugin.show( _notificationId++, title, body, platformChannelSpecifics, payload: 'train_${record.train}', ); } String _buildNotificationContent(TrainRecord record) { final buffer = StringBuffer(); buffer.write(record.fullTrainNumber); if (_isValidValue(record.route)) { buffer.write(' ${record.route}'); } if (_isValidValue(record.directionText)) { buffer.write(' ${record.directionText}'); } if (_isValidValue(record.positionInfo)) { buffer.write(' ${record.positionInfo}'); } buffer.writeln(); if (_isValidValue(record.locoType) && _isValidValue(record.loco)) { final shortLoco = record.loco.length > 5 ? record.loco.substring(record.loco.length - 5) : record.loco; buffer.write('${record.locoType}-$shortLoco'); } else if (_isValidValue(record.locoType)) { buffer.write(record.locoType); } else if (_isValidValue(record.loco)) { buffer.write(record.loco); } if (_isValidValue(record.speed)) { buffer.write(' ${record.speed}km/h'); } return buffer.toString().trim(); } bool _isValidValue(String? value) { if (value == null || value.isEmpty) return false; final trimmed = value.trim(); return trimmed.isNotEmpty && trimmed != 'NUL' && trimmed != 'NA' && trimmed != '*'; } Future enableNotifications(bool enable) async { _notificationsEnabled = enable; _settingsController.add(_notificationsEnabled); } Future isNotificationEnabled() async { return _notificationsEnabled; } Future cancelAllNotifications() async { await _notificationsPlugin.cancelAll(); } void dispose() { _settingsController.close(); } }