|
|
|
import 'dart:io';
|
|
|
|
|
|
|
|
import 'package:dio/dio.dart';
|
|
|
|
import 'package:get/get.dart' hide MultipartFile, FormData;
|
|
|
|
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`:筛选创建时间范围。
|
|
|
|
/// - `uploadStatus`:筛选上传状态('已上传', '未上传', '全部')。
|
|
|
|
/// - `bindStatus`:筛选绑定状态('已绑定', '未绑定', '全部')。
|
|
|
|
Future getProblems({
|
|
|
|
DateTime? startDate,
|
|
|
|
DateTime? endDate,
|
|
|
|
String uploadStatus = '全部',
|
|
|
|
String bindStatus = '全部',
|
|
|
|
}) async {
|
|
|
|
return await sqliteProvider.getProblems(
|
|
|
|
startDate: startDate,
|
|
|
|
endDate: endDate,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
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 imageFile = File(image.localPath);
|
|
|
|
final url = await fileRepository.uploadImage(
|
|
|
|
imageFile,
|
|
|
|
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 = {
|
|
|
|
'id': problem.id,
|
|
|
|
'description': problem.description,
|
|
|
|
'location': problem.location,
|
|
|
|
'imageUrls': finalRemoteUrls, // 使用整合后的网络URL列表
|
|
|
|
'createdAt': problem.creationTime.toIso8601String(),
|
|
|
|
// ... 其他字段
|
|
|
|
};
|
|
|
|
|
|
|
|
// 3. 发送给服务器
|
|
|
|
final response = await httpProvider.post(
|
|
|
|
'/api/problem',
|
|
|
|
data: apiPayload,
|
|
|
|
cancelToken: cancelToken,
|
|
|
|
);
|
|
|
|
|
|
|
|
// 4. 处理服务器响应,并更新本地模型状态
|
|
|
|
if (response.statusCode == 200) {
|
|
|
|
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<List<Problem>> 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);
|
|
|
|
},
|
|
|
|
);
|
|
|
|
updatedProblems.add(updatedProblem);
|
|
|
|
}
|
|
|
|
return updatedProblems;
|
|
|
|
} on DioException {
|
|
|
|
rethrow;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|