485 lines
14 KiB
Dart
485 lines
14 KiB
Dart
import 'dart:async';
|
||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
|
||
import '../../domain/repositories/share_repository.dart';
|
||
import '../../data/datasources/share/share_intent_datasource.dart';
|
||
import '../../data/repositories/share_repository_impl.dart';
|
||
import '../../core/utils/logger.dart';
|
||
|
||
/// 分享状态类 - 管理分享相关的UI状态
|
||
/// 包含分享文件列表、处理状态、错误信息等
|
||
class ShareState {
|
||
/// 待处理的分享文件列表
|
||
final List<SharedMediaFile> pendingFiles;
|
||
|
||
/// 是否正在处理分享
|
||
final bool isProcessing;
|
||
|
||
/// 处理错误信息
|
||
final String? error;
|
||
|
||
/// 是否显示分享界面
|
||
final bool showShareUI;
|
||
|
||
/// 当前处理的批次索引
|
||
final int currentBatchIndex;
|
||
|
||
/// 总批次数
|
||
final int totalBatches;
|
||
|
||
/// 是否正在批量处理
|
||
final bool isBatchProcessing;
|
||
|
||
/// 是否有待处理的分享
|
||
bool get hasPendingShare => pendingFiles.isNotEmpty;
|
||
|
||
/// 构造函数 - 创建分享状态实例
|
||
const ShareState({
|
||
this.pendingFiles = const [],
|
||
this.isProcessing = false,
|
||
this.error,
|
||
this.showShareUI = false,
|
||
this.currentBatchIndex = 0,
|
||
this.totalBatches = 0,
|
||
this.isBatchProcessing = false,
|
||
});
|
||
|
||
/// 复制构造函数 - 创建状态副本并支持字段更新
|
||
ShareState copyWith({
|
||
List<SharedMediaFile>? pendingFiles,
|
||
bool? isProcessing,
|
||
String? error,
|
||
bool? showShareUI,
|
||
int? currentBatchIndex,
|
||
int? totalBatches,
|
||
bool? isBatchProcessing,
|
||
}) {
|
||
return ShareState(
|
||
pendingFiles: pendingFiles ?? this.pendingFiles,
|
||
isProcessing: isProcessing ?? this.isProcessing,
|
||
error: error,
|
||
showShareUI: showShareUI ?? this.showShareUI,
|
||
currentBatchIndex: currentBatchIndex ?? this.currentBatchIndex,
|
||
totalBatches: totalBatches ?? this.totalBatches,
|
||
isBatchProcessing: isBatchProcessing ?? this.isBatchProcessing,
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 分享状态Notifier - 管理分享状态和业务逻辑
|
||
class ShareNotifier extends StateNotifier<ShareState> {
|
||
/// 分享仓库 - 处理分享相关的业务逻辑
|
||
final ShareRepository _shareRepository;
|
||
|
||
/// 分享数据流订阅 - 监听分享数据变化
|
||
StreamSubscription<List<SharedMediaFile>>? _shareSubscription;
|
||
|
||
ShareNotifier(this._shareRepository) : super(const ShareState()) {
|
||
_initializeShareListening();
|
||
}
|
||
|
||
/// 初始化分享监听 - 设置分享数据流监听
|
||
void _initializeShareListening() {
|
||
try {
|
||
Logger.info('初始化分享状态监听...');
|
||
|
||
// 监听分享数据流
|
||
_shareSubscription = _shareRepository.sharingStream?.listen(
|
||
handleSharedFiles,
|
||
onError: (error) {
|
||
Logger.error('分享数据流错误', error: error);
|
||
state = state.copyWith(error: '分享接收错误: $error');
|
||
},
|
||
);
|
||
|
||
// 检查当前是否有待处理的分享
|
||
_checkPendingShares();
|
||
|
||
Logger.info('分享状态监听初始化完成');
|
||
} catch (e) {
|
||
Logger.error('初始化分享监听失败', error: e);
|
||
state = state.copyWith(error: '初始化分享功能失败: $e');
|
||
}
|
||
}
|
||
|
||
/// 检查待处理的分享 - 检查当前是否有待处理的分享文件
|
||
void _checkPendingShares() {
|
||
try {
|
||
final pendingFiles = _shareRepository.getPendingShareFiles();
|
||
final hasShares = _shareRepository.hasPendingShare();
|
||
|
||
Logger.info('检查待处理分享: ${pendingFiles.length} 个文件');
|
||
|
||
if (hasShares && pendingFiles.isNotEmpty) {
|
||
state = state.copyWith(
|
||
pendingFiles: pendingFiles,
|
||
showShareUI: true,
|
||
error: null,
|
||
);
|
||
Logger.info('发现待处理分享,显示分享界面');
|
||
} else {
|
||
state = state.copyWith(
|
||
pendingFiles: [],
|
||
showShareUI: false,
|
||
error: null,
|
||
);
|
||
}
|
||
} catch (e) {
|
||
Logger.error('检查待处理分享失败', error: e);
|
||
state = state.copyWith(error: '检查分享状态失败: $e');
|
||
}
|
||
}
|
||
|
||
/// 处理接收到的分享文件 - 处理新的分享数据
|
||
/// [sharedFiles] 接收到的分享文件列表
|
||
Future<void> handleSharedFiles(List<SharedMediaFile> sharedFiles) async {
|
||
if (sharedFiles.isEmpty) {
|
||
Logger.warning('接收到的分享文件列表为空');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
Logger.info('处理新的分享文件: ${sharedFiles.length} 个');
|
||
|
||
state = state.copyWith(
|
||
pendingFiles: sharedFiles,
|
||
showShareUI: true,
|
||
isProcessing: false,
|
||
error: null,
|
||
);
|
||
|
||
// 记录详细的文件信息
|
||
for (final file in sharedFiles) {
|
||
Logger.info('分享文件: ${file.path}, 类型: ${file.type}');
|
||
}
|
||
|
||
} catch (e) {
|
||
Logger.error('处理分享文件失败', error: e);
|
||
state = state.copyWith(
|
||
error: '处理分享文件失败: $e',
|
||
isProcessing: false,
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 开始保存分享图片 - 开始批量保存分享图片,支持大量文件分批保存
|
||
/// [folderId] 目标文件夹ID,可为空
|
||
/// [tags] 要添加的标签列表,可为空
|
||
/// [note] 备注内容,可为空
|
||
Future<void> startSavingSharedImages({
|
||
String? folderId,
|
||
List<String>? tags,
|
||
String? note,
|
||
}) async {
|
||
if (state.pendingFiles.isEmpty) {
|
||
Logger.warning('没有待保存的分享图片');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
Logger.info('开始保存分享图片,共${state.pendingFiles.length}个文件');
|
||
|
||
// 初始化批量处理状态
|
||
final totalFiles = state.pendingFiles.length;
|
||
const batchSize = 10; // 每批处理10个文件
|
||
final totalBatches = (totalFiles / batchSize).ceil();
|
||
|
||
state = state.copyWith(
|
||
isProcessing: true,
|
||
isBatchProcessing: totalBatches > 1,
|
||
totalBatches: totalBatches,
|
||
currentBatchIndex: 0,
|
||
error: null,
|
||
);
|
||
|
||
if (totalBatches > 1) {
|
||
Logger.info('启用批量保存模式,共$totalBatches批,每批$batchSize个文件');
|
||
await _batchSaveSharedImages(folderId, tags, note, batchSize);
|
||
} else {
|
||
Logger.info('普通保存模式,直接处理$totalFiles个文件');
|
||
await _simulateSaveProcess(folderId, tags, note);
|
||
}
|
||
|
||
// 保存完成后清除分享数据
|
||
_shareRepository.clearCurrentShare();
|
||
|
||
state = state.copyWith(
|
||
pendingFiles: [],
|
||
isProcessing: false,
|
||
isBatchProcessing: false,
|
||
showShareUI: false,
|
||
currentBatchIndex: 0,
|
||
totalBatches: 0,
|
||
error: null,
|
||
);
|
||
|
||
Logger.info('分享图片保存完成');
|
||
|
||
} catch (e) {
|
||
Logger.error('保存分享图片失败', error: e);
|
||
state = state.copyWith(
|
||
isProcessing: false,
|
||
isBatchProcessing: false,
|
||
error: '保存图片失败: $e',
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 批量保存分享图片 - 分批处理大量分享图片
|
||
/// [folderId] 目标文件夹ID
|
||
/// [tags] 标签列表
|
||
/// [note] 备注内容
|
||
/// [batchSize] 每批处理的大小
|
||
Future<void> _batchSaveSharedImages(
|
||
String? folderId,
|
||
List<String>? tags,
|
||
String? note,
|
||
int batchSize,
|
||
) async {
|
||
final batches = _createBatches(state.pendingFiles, batchSize);
|
||
|
||
Logger.info('开始分批保存,共${batches.length}批');
|
||
|
||
for (int i = 0; i < batches.length; i++) {
|
||
final batch = batches[i];
|
||
final batchNumber = i + 1;
|
||
|
||
Logger.info('处理第$batchNumber批,共${batch.length}个文件');
|
||
|
||
// 更新当前批次状态
|
||
state = state.copyWith(currentBatchIndex: batchNumber);
|
||
|
||
// 模拟处理当前批次
|
||
await _simulateBatchSaveProcess(batch, folderId, tags, note, batchNumber);
|
||
|
||
// 批次间短暂延迟,避免系统过载
|
||
if (i < batches.length - 1) {
|
||
await Future.delayed(const Duration(milliseconds: 200));
|
||
}
|
||
}
|
||
|
||
Logger.info('批量保存完成');
|
||
}
|
||
|
||
/// 创建文件批次 - 将文件列表分成指定大小的批次
|
||
/// [files] 文件列表
|
||
/// [batchSize] 每批的大小
|
||
/// 返回批次列表
|
||
List<List<SharedMediaFile>> _createBatches(List<SharedMediaFile> files, int batchSize) {
|
||
final batches = <List<SharedMediaFile>>[];
|
||
|
||
for (int i = 0; i < files.length; i += batchSize) {
|
||
final end = (i + batchSize < files.length) ? i + batchSize : files.length;
|
||
batches.add(files.sublist(i, end));
|
||
}
|
||
|
||
return batches;
|
||
}
|
||
|
||
/// 模拟批量保存过程 - 处理单个批次的保存
|
||
/// [batch] 当前批次的文件
|
||
/// [folderId] 目标文件夹ID
|
||
/// [tags] 标签列表
|
||
/// [note] 备注内容
|
||
/// [batchNumber] 批次编号
|
||
Future<void> _simulateBatchSaveProcess(
|
||
List<SharedMediaFile> batch,
|
||
String? folderId,
|
||
List<String>? tags,
|
||
String? note,
|
||
int batchNumber,
|
||
) async {
|
||
// 模拟保存过程,实际实现中会调用真实的保存逻辑
|
||
await Future.delayed(const Duration(seconds: 1));
|
||
|
||
Logger.info('第$batchNumber批次保存完成,处理了${batch.length}个文件');
|
||
}
|
||
|
||
/// 模拟保存过程 - 临时模拟保存过程(后续替换为真实逻辑)
|
||
/// [folderId] 目标文件夹ID
|
||
/// [tags] 标签列表
|
||
/// [note] 备注内容
|
||
Future<void> _simulateSaveProcess(
|
||
String? folderId,
|
||
List<String>? tags,
|
||
String? note,
|
||
) async {
|
||
// 模拟保存过程
|
||
await Future.delayed(const Duration(seconds: 2));
|
||
|
||
Logger.info('模拟保存完成 - 文件夹: $folderId, 标签: $tags, 备注: $note');
|
||
}
|
||
|
||
/// 取消分享保存 - 取消当前的分享保存操作
|
||
void cancelShareSaving() {
|
||
Logger.info('取消分享保存');
|
||
|
||
// 清除当前分享数据
|
||
_shareRepository.clearCurrentShare();
|
||
|
||
state = const ShareState(
|
||
pendingFiles: [],
|
||
isProcessing: false,
|
||
showShareUI: false,
|
||
error: null,
|
||
);
|
||
}
|
||
|
||
/// 关闭分享界面 - 关闭分享处理界面
|
||
void closeShareUI() {
|
||
Logger.info('关闭分享界面');
|
||
|
||
state = state.copyWith(
|
||
showShareUI: false,
|
||
error: null,
|
||
);
|
||
}
|
||
|
||
/// 清除错误状态 - 清除当前的错误信息
|
||
void clearError() {
|
||
if (state.error != null) {
|
||
state = state.copyWith(error: null);
|
||
}
|
||
}
|
||
|
||
/// 刷新分享状态 - 重新检查分享状态
|
||
void refreshShareStatus() {
|
||
Logger.info('刷新分享状态');
|
||
_checkPendingShares();
|
||
}
|
||
|
||
/// 清除待处理文件 - 清空当前待处理的分享文件
|
||
void clearPendingFiles() {
|
||
Logger.info('清除待处理分享文件');
|
||
state = state.copyWith(
|
||
pendingFiles: [],
|
||
showShareUI: false,
|
||
isProcessing: false,
|
||
error: null,
|
||
);
|
||
|
||
// 同时清除仓库中的待处理文件
|
||
_shareRepository.clearCurrentShare();
|
||
}
|
||
|
||
/// 获取分享文件数量 - 获取当前待处理的分享文件数量
|
||
int getShareFileCount() {
|
||
return _shareRepository.getShareFileCount();
|
||
}
|
||
|
||
/// 批量保存图片 - 保存多张分享的图片
|
||
/// [sharedFiles] 分享的媒体文件列表
|
||
/// [folderId] 目标文件夹ID
|
||
/// [tags] 标签列表
|
||
/// [note] 备注内容
|
||
Future<void> saveBatchImages({
|
||
required List<SharedMediaFile> sharedFiles,
|
||
required String folderId,
|
||
required List<String> tags,
|
||
String? note,
|
||
}) async {
|
||
try {
|
||
Logger.info('开始批量保存${sharedFiles.length}张图片');
|
||
|
||
// 更新状态为处理中
|
||
state = state.copyWith(isProcessing: true, error: null);
|
||
|
||
// 模拟保存过程,实际实现中会调用真实的保存逻辑
|
||
await _simulateSaveProcess(folderId, tags, note);
|
||
|
||
// 保存完成
|
||
state = state.copyWith(isProcessing: false);
|
||
Logger.info('批量保存完成');
|
||
|
||
} catch (e) {
|
||
Logger.error('批量保存失败', error: e);
|
||
state = state.copyWith(
|
||
isProcessing: false,
|
||
error: '保存失败: $e',
|
||
);
|
||
rethrow;
|
||
}
|
||
}
|
||
|
||
/// 单张保存图片 - 保存单张分享的图片
|
||
/// [sharedFile] 分享的媒体文件
|
||
/// [folderId] 目标文件夹ID
|
||
/// [tags] 标签列表
|
||
/// [note] 备注内容
|
||
Future<void> saveSingleImage({
|
||
required SharedMediaFile sharedFile,
|
||
required String folderId,
|
||
required List<String> tags,
|
||
String? note,
|
||
}) async {
|
||
try {
|
||
Logger.info('开始单张保存图片: ${sharedFile.path}');
|
||
|
||
// 更新状态为处理中
|
||
state = state.copyWith(isProcessing: true, error: null);
|
||
|
||
// 模拟保存过程,实际实现中会调用真实的保存逻辑
|
||
await _simulateSaveProcess(folderId, tags, note);
|
||
|
||
// 保存完成
|
||
state = state.copyWith(isProcessing: false);
|
||
Logger.info('单张保存完成');
|
||
|
||
} catch (e) {
|
||
Logger.error('单张保存失败', error: e);
|
||
state = state.copyWith(
|
||
isProcessing: false,
|
||
error: '保存失败: $e',
|
||
);
|
||
rethrow;
|
||
}
|
||
}
|
||
|
||
/// 释放资源 - 清理分享相关资源
|
||
@override
|
||
Future<void> dispose() async {
|
||
Logger.info('释放分享状态资源');
|
||
|
||
// 调用父类的dispose方法(StateNotifier的dispose不是异步的)
|
||
super.dispose();
|
||
|
||
// 取消分享数据流订阅
|
||
await _shareSubscription?.cancel();
|
||
_shareSubscription = null;
|
||
|
||
// 释放分享仓库
|
||
await _shareRepository.dispose();
|
||
}
|
||
}
|
||
|
||
/// 分享数据源Provider - 提供分享数据源实例
|
||
final shareDataSourceProvider = Provider<ShareIntentDataSource>((ref) {
|
||
return ShareIntentDataSource();
|
||
});
|
||
|
||
/// 图片工具Provider - 提供图片工具实例
|
||
/// ImageUtils是静态工具类,不需要实例化,直接提供工具引用
|
||
final imageUtilsProvider = Provider<Object>((ref) {
|
||
return Object(); // 占位Provider,实际使用ImageUtils静态方法
|
||
});
|
||
|
||
/// 文件工具Provider - 提供文件工具实例
|
||
/// FileUtils是静态工具类,不需要实例化,直接提供工具引用
|
||
final fileUtilsProvider = Provider<Object>((ref) {
|
||
return Object(); // 占位Provider,实际使用FileUtils静态方法
|
||
});
|
||
|
||
/// 分享仓库Provider - 提供分享仓库实例
|
||
final shareRepositoryProvider = Provider<ShareRepository>((ref) {
|
||
final shareDataSource = ref.read(shareDataSourceProvider);
|
||
// ImageUtils和FileUtils是静态工具类,直接传入null占位
|
||
return ShareRepositoryImpl(
|
||
shareDataSource: shareDataSource,
|
||
);
|
||
});
|
||
|
||
/// 分享状态Provider - 管理分享相关的UI状态
|
||
final shareProvider = StateNotifierProvider<ShareNotifier, ShareState>((ref) {
|
||
final shareRepository = ref.read(shareRepositoryProvider);
|
||
return ShareNotifier(shareRepository);
|
||
}); |