215 lines
6.1 KiB
Dart
215 lines
6.1 KiB
Dart
// ============================================================
|
||
// 闲言APP — 传输通知服务
|
||
// 创建时间: 2026-05-10
|
||
// 更新时间: 2026-05-17
|
||
// 作用: 传输完成/失败/接收等本地通知
|
||
// 上次更新: 鸿蒙适配-使用桥接方法隔离OhosInitializationSettings
|
||
// ============================================================
|
||
|
||
import 'dart:io';
|
||
|
||
import 'package:xianyan/core/utils/platform/platform_utils.dart' as pu;
|
||
|
||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||
import 'package:xianyan/core/utils/logger.dart';
|
||
import 'package:xianyan/core/services/notification/notification_init_stub.dart';
|
||
|
||
import '../models/transfer_enums.dart';
|
||
import '../models/transfer_task.dart';
|
||
|
||
class TransferNotificationService {
|
||
TransferNotificationService();
|
||
|
||
final FlutterLocalNotificationsPlugin _plugin =
|
||
FlutterLocalNotificationsPlugin();
|
||
|
||
bool _initialized = false;
|
||
|
||
static const String _channelId = 'file_transfer';
|
||
static const String _channelName = '文件传输';
|
||
static const String _channelDesc = '文件传输进度和完成通知';
|
||
|
||
Future<void> initialize() async {
|
||
if (_initialized) return;
|
||
|
||
if (!pu.isOhos &&
|
||
!Platform.isAndroid &&
|
||
!Platform.isIOS &&
|
||
!Platform.isMacOS) {
|
||
Log.w('Notification: Platform not supported');
|
||
_initialized = true;
|
||
return;
|
||
}
|
||
|
||
const androidSettings = AndroidInitializationSettings(
|
||
'@mipmap/ic_launcher',
|
||
);
|
||
const iosSettings = DarwinInitializationSettings();
|
||
const macOsSettings = DarwinInitializationSettings();
|
||
|
||
// 通过桥接方法构建 InitializationSettings
|
||
// 官方SDK:不含ohos参数;鸿蒙端:动态注入ohos参数
|
||
final settings = buildNotificationInitSettings(
|
||
androidSettings: androidSettings,
|
||
iosSettings: iosSettings,
|
||
macOsSettings: macOsSettings,
|
||
);
|
||
|
||
await _plugin.initialize(
|
||
settings: settings,
|
||
onDidReceiveNotificationResponse: (response) {
|
||
Log.i('Notification: Tapped payload: ${response.payload}');
|
||
},
|
||
);
|
||
|
||
if (pu.isOhos) {
|
||
// 鸿蒙端:通过桥接方法动态请求权限
|
||
await requestOhosNotificationPermission(_plugin);
|
||
} else if (Platform.isAndroid) {
|
||
await _plugin
|
||
.resolvePlatformSpecificImplementation<
|
||
AndroidFlutterLocalNotificationsPlugin
|
||
>()
|
||
?.requestNotificationsPermission();
|
||
}
|
||
|
||
_initialized = true;
|
||
}
|
||
|
||
Future<void> showTransferComplete(TransferTask task) async {
|
||
if (!_initialized) await initialize();
|
||
|
||
final title = task.direction == TransferDirection.send
|
||
? '📤 发送完成'
|
||
: '📥 接收完成';
|
||
final body = '${task.fileName} (${_formatBytes(task.fileSize)})';
|
||
|
||
await _showNotification(
|
||
id: task.id.hashCode,
|
||
title: title,
|
||
body: body,
|
||
payload: 'transfer_complete:${task.id}',
|
||
);
|
||
}
|
||
|
||
Future<void> showTransferFailed(TransferTask task) async {
|
||
if (!_initialized) await initialize();
|
||
|
||
final title = task.direction == TransferDirection.send
|
||
? '❌ 发送失败'
|
||
: '❌ 接收失败';
|
||
final body = '${task.fileName}: ${task.errorMessage ?? "未知错误"}';
|
||
|
||
await _showNotification(
|
||
id: task.id.hashCode,
|
||
title: title,
|
||
body: body,
|
||
payload: 'transfer_failed:${task.id}',
|
||
);
|
||
}
|
||
|
||
Future<void> showIncomingTransfer(String fileName, int fileSize) async {
|
||
if (!_initialized) await initialize();
|
||
|
||
await _showNotification(
|
||
id: DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||
title: '📥 收到文件',
|
||
body: '$fileName (${_formatBytes(fileSize)})',
|
||
payload: 'transfer_incoming',
|
||
);
|
||
}
|
||
|
||
Future<void> showTransferProgress({
|
||
required String taskId,
|
||
required String fileName,
|
||
required double progress,
|
||
}) async {
|
||
if (!_initialized) await initialize();
|
||
if (!Platform.isAndroid && !pu.isOhos) return;
|
||
|
||
final percent = (progress * 100).toStringAsFixed(0);
|
||
|
||
await _showNotification(
|
||
id: taskId.hashCode,
|
||
title: '📡 传输中...',
|
||
body: '$fileName — $percent%',
|
||
ongoing: true,
|
||
progress: (progress * 100).round(),
|
||
maxProgress: 100,
|
||
payload: 'transfer_progress:$taskId',
|
||
);
|
||
}
|
||
|
||
Future<void> cancelNotification(int id) async {
|
||
await _plugin.cancel(id: id);
|
||
}
|
||
|
||
Future<void> _showNotification({
|
||
required int id,
|
||
required String title,
|
||
required String body,
|
||
String? payload,
|
||
bool ongoing = false,
|
||
int? progress,
|
||
int? maxProgress,
|
||
}) async {
|
||
try {
|
||
const androidDetails = AndroidNotificationDetails(
|
||
_channelId,
|
||
_channelName,
|
||
channelDescription: _channelDesc,
|
||
importance: Importance.high,
|
||
priority: Priority.high,
|
||
);
|
||
|
||
const iosDetails = DarwinNotificationDetails(
|
||
presentAlert: true,
|
||
presentBadge: true,
|
||
presentSound: true,
|
||
);
|
||
|
||
AndroidNotificationDetails? androidSpecific;
|
||
if ((Platform.isAndroid || pu.isOhos) &&
|
||
progress != null &&
|
||
maxProgress != null) {
|
||
androidSpecific = AndroidNotificationDetails(
|
||
_channelId,
|
||
_channelName,
|
||
channelDescription: _channelDesc,
|
||
importance: Importance.low,
|
||
priority: Priority.low,
|
||
showProgress: true,
|
||
progress: progress,
|
||
maxProgress: maxProgress,
|
||
ongoing: ongoing,
|
||
onlyAlertOnce: true,
|
||
);
|
||
}
|
||
|
||
final details = NotificationDetails(
|
||
android: androidSpecific ?? androidDetails,
|
||
iOS: iosDetails,
|
||
);
|
||
|
||
await _plugin.show(
|
||
id: id,
|
||
title: title,
|
||
body: body,
|
||
notificationDetails: details,
|
||
payload: payload,
|
||
);
|
||
} catch (e) {
|
||
Log.w('Notification: Failed to show: $e');
|
||
}
|
||
}
|
||
|
||
String _formatBytes(int bytes) {
|
||
if (bytes < 1024) return '$bytes B';
|
||
if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)} KB';
|
||
if (bytes < 1024 * 1024 * 1024) {
|
||
return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB';
|
||
}
|
||
return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(1)} GB';
|
||
}
|
||
}
|