snap_wish/lib/data/repositories/share_repository_impl.dart
2025-09-17 13:32:25 +08:00

301 lines
9.8 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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();
}
}