301 lines
9.8 KiB
Dart
301 lines
9.8 KiB
Dart
import 'dart:async';
|
||
import 'dart:io';
|
||
import 'package:path/path.dart' as path;
|
||
import 'package:image/image.dart' as img;
|
||
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
|
||
import '../../domain/entities/inspiration_image.dart';
|
||
import '../../domain/repositories/share_repository.dart';
|
||
import '../datasources/share/share_intent_datasource.dart';
|
||
import '../../../core/utils/logger.dart';
|
||
import '../../../core/utils/image_utils.dart';
|
||
import '../../../core/utils/path_utils.dart';
|
||
|
||
/// 分享仓库实现类 - 实现分享功能的业务逻辑
|
||
/// 负责处理分享接收、图片保存和相关的业务逻辑
|
||
class ShareRepositoryImpl implements ShareRepository {
|
||
/// 分享数据源 - 处理系统分享接收
|
||
final ShareIntentDataSource _shareDataSource;
|
||
|
||
/// 图片工具类 - 处理图片压缩和格式转换(已使用静态方法)
|
||
// final ImageUtils _imageUtils; // 静态类,不需要实例
|
||
|
||
/// 文件工具类 - 处理文件存储(已使用静态方法)
|
||
// final FileUtils _fileUtils; // 静态类,不需要实例
|
||
|
||
/// 分享数据流订阅 - 管理分享数据流监听
|
||
StreamSubscription<List<SharedMediaFile>>? _shareSubscription;
|
||
|
||
@override
|
||
Stream<List<SharedMediaFile>>? get sharingStream => _shareDataSource.sharingStream;
|
||
|
||
ShareRepositoryImpl({
|
||
required ShareIntentDataSource shareDataSource,
|
||
// required ImageUtils imageUtils, // 静态类,不需要传入
|
||
// required FileUtils fileUtils, // 静态类,不需要传入
|
||
}) : _shareDataSource = shareDataSource;
|
||
|
||
/// 初始化分享接收 - 设置分享监听器
|
||
/// 在应用启动时调用,开始监听系统分享事件
|
||
@override
|
||
Future<void> initializeShareReceiving() async {
|
||
try {
|
||
Logger.info('初始化分享仓库...');
|
||
|
||
// 初始化分享数据源
|
||
await _shareDataSource.initShareReceiving();
|
||
|
||
// 订阅分享数据流
|
||
_shareSubscription = _shareDataSource.sharingStream.listen(
|
||
_handleSharedImages,
|
||
onError: _handleShareError,
|
||
);
|
||
|
||
Logger.info('分享仓库初始化完成');
|
||
} catch (e) {
|
||
Logger.error('分享仓库初始化失败', error: e);
|
||
throw Exception('初始化分享功能失败: $e');
|
||
}
|
||
}
|
||
|
||
/// 处理接收到的分享图片 - 验证并准备保存分享图片
|
||
/// [sharedFiles] 接收到的分享文件列表
|
||
Future<void> _handleSharedImages(List<SharedMediaFile> sharedFiles) async {
|
||
try {
|
||
Logger.info('开始处理分享图片: ${sharedFiles.length} 张');
|
||
|
||
if (sharedFiles.isEmpty) {
|
||
Logger.warning('分享文件列表为空');
|
||
return;
|
||
}
|
||
|
||
// 验证并处理每个分享文件
|
||
final processedImages = <InspirationImage>[];
|
||
|
||
for (final file in sharedFiles) {
|
||
try {
|
||
final image = await _processSharedFile(file);
|
||
if (image != null) {
|
||
processedImages.add(image);
|
||
}
|
||
} catch (e) {
|
||
Logger.error('处理分享文件失败: ${file.path}', error: e);
|
||
// 继续处理其他文件,不中断整个流程
|
||
}
|
||
}
|
||
|
||
if (processedImages.isNotEmpty) {
|
||
Logger.info('分享图片处理完成: ${processedImages.length} 张');
|
||
// 这里可以通知UI层有新的分享图片需要处理
|
||
// 例如通过事件总线或状态管理
|
||
} else {
|
||
Logger.warning('没有成功处理的分享图片');
|
||
}
|
||
|
||
} catch (e) {
|
||
Logger.error('处理分享图片失败', error: e);
|
||
throw Exception('处理分享图片失败: $e');
|
||
}
|
||
}
|
||
|
||
/// 处理单个分享文件 - 处理单个分享图片文件
|
||
/// [sharedFile] 分享文件对象
|
||
/// 返回处理后的灵感图片实体,失败时返回null
|
||
Future<InspirationImage?> _processSharedFile(SharedMediaFile sharedFile) async {
|
||
try {
|
||
Logger.info('处理分享文件: ${sharedFile.path}');
|
||
|
||
// 验证文件路径
|
||
final filePath = sharedFile.path;
|
||
if (filePath.isEmpty || !filePath.startsWith('/')) {
|
||
Logger.error('文件路径无效: $filePath');
|
||
return null;
|
||
}
|
||
|
||
// 验证文件是否存在
|
||
final file = File(filePath);
|
||
if (!await file.exists()) {
|
||
Logger.error('文件不存在: $filePath');
|
||
return null;
|
||
}
|
||
|
||
// 获取文件信息
|
||
final fileStat = await file.stat();
|
||
final fileSize = fileStat.size;
|
||
|
||
if (fileSize == 0) {
|
||
Logger.error('文件大小为0: $filePath');
|
||
return null;
|
||
}
|
||
|
||
// 获取文件扩展名和MIME类型
|
||
final fileExtension = path.extension(filePath).substring(1); // 移除点号
|
||
final mimeType = _getMimeType(fileExtension);
|
||
|
||
Logger.info('文件信息 - 大小: $fileSize bytes, 类型: $mimeType, 扩展名: $fileExtension');
|
||
|
||
// 生成唯一ID和存储路径
|
||
final imageId = PathUtils.generateUniqueId();
|
||
final storagePath = await _getStoragePath();
|
||
final originalFileName = path.basename(filePath);
|
||
|
||
// 创建目标文件路径
|
||
final targetFileName = '$imageId${path.extension(filePath)}';
|
||
final targetFilePath = path.join(storagePath, targetFileName);
|
||
|
||
// 复制文件到应用存储目录
|
||
await file.copy(targetFilePath);
|
||
Logger.info('文件已复制到: $targetFilePath');
|
||
|
||
// 生成缩略图
|
||
String? thumbnailPath;
|
||
try {
|
||
// 生成缩略图文件名
|
||
final thumbnailFileName = PathUtils.generateThumbnailFileName(targetFileName);
|
||
final thumbnailPathDir = PathUtils.getParentDirectory(targetFilePath);
|
||
final thumbnailFullPath = path.join(thumbnailPathDir, thumbnailFileName);
|
||
|
||
thumbnailPath = await ImageUtils.generateThumbnail(
|
||
imagePath: targetFilePath,
|
||
targetPath: thumbnailFullPath,
|
||
maxSize: 500,
|
||
quality: 85,
|
||
);
|
||
Logger.info('缩略图生成完成: $thumbnailPath');
|
||
} catch (e) {
|
||
Logger.error('生成缩略图失败', error: e);
|
||
// 缩略图失败不影响主流程
|
||
}
|
||
|
||
// 获取图片尺寸信息
|
||
int? width;
|
||
int? height;
|
||
try {
|
||
// 读取图片文件获取尺寸信息
|
||
final imageFile = File(targetFilePath);
|
||
final imageBytes = await imageFile.readAsBytes();
|
||
|
||
// 使用image库解码图片获取尺寸
|
||
final decodedImage = img.decodeImage(imageBytes);
|
||
if (decodedImage != null) {
|
||
width = decodedImage.width;
|
||
height = decodedImage.height;
|
||
Logger.info('图片尺寸: ${width}x$height');
|
||
} else {
|
||
Logger.warning('无法解码图片获取尺寸');
|
||
}
|
||
} catch (e) {
|
||
Logger.error('获取图片尺寸失败', error: e);
|
||
// 尺寸获取失败不影响主流程
|
||
}
|
||
|
||
// 创建灵感图片实体
|
||
final inspirationImage = InspirationImage(
|
||
id: imageId,
|
||
filePath: targetFilePath,
|
||
thumbnailPath: thumbnailPath ?? targetFilePath,
|
||
folderId: null, // 默认无文件夹
|
||
tags: const [], // 初始无标签
|
||
note: null, // 初始无备注
|
||
createdAt: DateTime.now(),
|
||
updatedAt: DateTime.now(),
|
||
originalName: originalFileName,
|
||
fileSize: fileSize,
|
||
mimeType: mimeType,
|
||
width: width,
|
||
height: height,
|
||
isFavorite: false,
|
||
);
|
||
|
||
Logger.info('分享文件处理完成: $imageId');
|
||
return inspirationImage;
|
||
|
||
} catch (e) {
|
||
Logger.error('处理分享文件失败: ${sharedFile.path}', error: e);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// 获取存储路径 - 获取图片存储目录路径
|
||
/// 返回基于日期的存储路径
|
||
Future<String> _getStoragePath() async {
|
||
final storagePath = await PathUtils.buildImageStoragePath(
|
||
fileName: 'temp', // 临时文件名,后面会替换
|
||
);
|
||
// 获取目录路径(去掉文件名)
|
||
final dirPath = PathUtils.getParentDirectory(storagePath);
|
||
|
||
// 确保目录存在
|
||
await Directory(dirPath).create(recursive: true);
|
||
return dirPath;
|
||
}
|
||
|
||
/// 获取MIME类型 - 根据文件扩展名获取MIME类型
|
||
/// [extension] 文件扩展名(不含点)
|
||
/// 返回对应的MIME类型
|
||
String _getMimeType(String extension) {
|
||
final mimeTypes = {
|
||
'jpg': 'image/jpeg',
|
||
'jpeg': 'image/jpeg',
|
||
'png': 'image/png',
|
||
'gif': 'image/gif',
|
||
'webp': 'image/webp',
|
||
'bmp': 'image/bmp',
|
||
'heic': 'image/heic',
|
||
'heif': 'image/heif',
|
||
};
|
||
|
||
return mimeTypes[extension.toLowerCase()] ?? 'image/jpeg';
|
||
}
|
||
|
||
/// 处理分享错误 - 统一处理分享过程中的错误
|
||
/// [error] 分享错误信息
|
||
void _handleShareError(dynamic error) {
|
||
Logger.error('分享接收错误', error: error);
|
||
// 这里可以通知UI层显示错误提示
|
||
// 例如通过事件总线或状态管理
|
||
}
|
||
|
||
/// 获取待处理的分享文件 - 获取当前待处理的分享文件
|
||
/// 返回待处理的分享文件列表
|
||
@override
|
||
List<SharedMediaFile> getPendingShareFiles() {
|
||
return _shareDataSource.getCurrentSharedFiles();
|
||
}
|
||
|
||
/// 是否有待处理的分享 - 检查是否有待处理的分享文件
|
||
/// 返回是否有待处理文件
|
||
@override
|
||
bool hasPendingShare() {
|
||
return _shareDataSource.hasPendingShare();
|
||
}
|
||
|
||
/// 清除当前分享数据 - 清除已处理的分享数据
|
||
/// 在分享处理完成后调用
|
||
@override
|
||
void clearCurrentShare() {
|
||
_shareDataSource.clearCurrentShare();
|
||
}
|
||
|
||
/// 获取分享文件数量 - 获取当前分享文件数量
|
||
/// 返回分享文件数量
|
||
@override
|
||
int getShareFileCount() {
|
||
return _shareDataSource.getShareFileCount();
|
||
}
|
||
|
||
/// 释放资源 - 清理分享接收相关资源
|
||
/// 在应用退出时调用
|
||
@override
|
||
Future<void> dispose() async {
|
||
Logger.info('释放分享仓库资源');
|
||
|
||
// 取消分享数据流订阅
|
||
await _shareSubscription?.cancel();
|
||
_shareSubscription = null;
|
||
|
||
// 释放分享数据源
|
||
await _shareDataSource.dispose();
|
||
}
|
||
}
|
||
|