You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

216 lines
7.2 KiB

import 'dart:io';
import 'package:dio/dio.dart';
import 'package:get/get.dart' hide MultipartFile, FormData;
import 'package:problem_check_system/core/extensions/http_response_extension.dart';
import 'package:problem_check_system/core/utils/constants/api_endpoints.dart';
import 'package:problem_check_system/data/models/image_status.dart';
import 'package:problem_check_system/data/models/sync_status.dart';
import 'package:problem_check_system/data/models/image_metadata_model.dart';
import 'package:problem_check_system/data/models/problem_model.dart';
import 'package:problem_check_system/data/providers/connectivity_provider.dart';
import 'package:problem_check_system/data/providers/http_provider.dart';
import 'package:problem_check_system/data/providers/sqlite_provider.dart';
import 'package:problem_check_system/data/repositories/file_repository.dart';
/// 问题仓库,负责处理问题数据的本地持久化。
/// 它封装了底层数据库操作,为业务逻辑层提供一个简洁的接口。
class ProblemRepository extends GetxService {
final SQLiteProvider sqliteProvider;
final HttpProvider httpProvider;
final ConnectivityProvider connectivityProvider;
final FileRepository fileRepository = Get.find<FileRepository>();
RxBool get isOnline => connectivityProvider.isOnline;
ProblemRepository({
required this.sqliteProvider,
required this.httpProvider,
required this.connectivityProvider,
});
/// 更新本地数据库中的一个问题。
/// 如果问题存在则更新,如果不存在则插入。
Future<void> updateProblem(Problem problem) async {
// 检查问题是否存在,通常通过ID判断
final existingProblem = await sqliteProvider.getProblemById(problem.id!);
if (existingProblem != null) {
// 问题已存在,执行更新操作
await sqliteProvider.updateProblem(problem);
} else {
// 问题不存在,执行插入操作
await sqliteProvider.insertProblem(problem);
}
}
/// 通用查询方法,根据可选的筛选条件获取问题列表。
/// - `startDate`/`endDate`:筛选创建时间范围。
/// - `syncStatus`:筛选上传状态('已上传', '未上传', '全部')。
/// - `bindStatus`:筛选绑定状态('已绑定', '未绑定', '全部')。
Future getProblems({
DateTime? startDate,
DateTime? endDate,
String? syncStatus,
String? bindStatus,
}) async {
return await sqliteProvider.getProblems(
startDate: startDate,
endDate: endDate,
syncStatus: syncStatus,
bindStatus: bindStatus,
);
}
Future<void> insertProblem(Problem problem) async {
await sqliteProvider.insertProblem(problem);
}
Future<void> deleteProblem(String id) async {
await sqliteProvider.deleteProblem(id);
}
// * /api/Objects/association/${file.name}
/// 上传单个问题及其所有关联的图片。
Future<Problem> uploadProblem(
Problem problem, {
required CancelToken cancelToken,
required void Function(double progress) onProgress,
}) async {
try {
// 找到需要上传的图片
final newImages = problem.imageUrls
.where((img) => img.status == ImageStatus.local)
.toList();
final totalFilesToUpload = newImages.length;
int filesUploadedCount = 0;
// 1. 上传所有状态为 ImageStatus.local 的新图片
final List<String> remoteUrls = [];
for (var image in newImages) {
// 修正:当上传被取消时,抛出异常而不是无返回值的 return
if (cancelToken.isCancelled) {
throw DioException(
requestOptions: RequestOptions(path: ''),
type: DioExceptionType.cancel,
error: '上传已取消',
);
}
final url = await fileRepository.uploadImage(
image.localPath,
cancelToken: cancelToken,
onSendProgress: (sent, total) {
double overallProgress =
(filesUploadedCount + (sent / total)) / totalFilesToUpload;
onProgress(overallProgress);
},
);
remoteUrls.add(url);
filesUploadedCount++;
}
onProgress(1.0); // 确保图片上传进度为100%
// 2. 构建 API payload
final List<String> finalRemoteUrls = [];
int newImageIndex = 0;
for (var image in problem.imageUrls) {
if (image.status == ImageStatus.synced) {
finalRemoteUrls.add(image.remoteUrl!);
} else if (image.status == ImageStatus.local) {
finalRemoteUrls.add(remoteUrls[newImageIndex]);
newImageIndex++;
}
}
final apiPayload = {
'title': problem.description,
'location': problem.location,
'imageUrls': finalRemoteUrls,
'creationTime': problem.creationTime.toUtc().toIso8601String(),
};
// 3. 发送给服务器
final response = await httpProvider.post(
ApiEndpoints.postProblem,
data: apiPayload,
cancelToken: cancelToken,
);
// 4. 处理服务器响应,并更新本地模型状态
if (response.isSuccess) {
final List<ImageMetadata> updatedImageMetadata = [];
int uploadedUrlIndex = 0;
for (var image in problem.imageUrls) {
if (image.status == ImageStatus.local) {
updatedImageMetadata.add(
ImageMetadata(
localPath: image.localPath,
remoteUrl: remoteUrls[uploadedUrlIndex],
status: ImageStatus.synced,
),
);
uploadedUrlIndex++;
} else {
updatedImageMetadata.add(image);
}
}
// 返回一个包含新同步状态和更新后图片列表的对象
return problem.copyWith(
syncStatus: SyncStatus.synced,
imageUrls: updatedImageMetadata,
);
} else {
throw Exception('问题上传失败,状态码: ${response.statusCode}');
}
} on DioException {
rethrow;
}
}
/// 新增:上传问题列表。
/// 遍历问题列表,并计算总进度。
Future<void> uploadProblems(
List<Problem> problems, {
required CancelToken cancelToken,
required void Function(double progress) onProgress,
}) async {
final int totalProblems = problems.length;
// final List<Problem> updatedProblems = [];
try {
for (int i = 0; i < totalProblems; i++) {
// 如果取消令牌被触发,停止并返回
if (cancelToken.isCancelled) {
break;
}
final problemToUpload = problems[i];
// 传递一个子进度回调,用于计算单个问题的进度
final updatedProblem = await uploadProblem(
problemToUpload,
cancelToken: cancelToken,
onProgress: (progress) {
// 计算总体进度:(已完成的问题数 + 当前问题的进度) / 总问题数
final overallProgress = (i + progress) / totalProblems;
onProgress(overallProgress);
},
);
sqliteProvider.updateProblem(updatedProblem);
// updatedProblems.add(updatedProblem);
}
// return updatedProblems;
} on DioException {
rethrow;
}
}
getProblemsForSync() {
return sqliteProvider.getProblemsForSync();
}
}