|
|
|
@ -11,7 +11,9 @@ import 'package:problem_check_system/core/extensions/http_response_extension.dar
|
|
|
|
|
import 'package:problem_check_system/data/models/image_metadata_model.dart'; |
|
|
|
|
import 'package:problem_check_system/data/models/image_status.dart'; |
|
|
|
|
import 'package:problem_check_system/data/models/problem_sync_status.dart'; |
|
|
|
|
import 'package:problem_check_system/data/models/server_problem.dart'; |
|
|
|
|
import 'package:problem_check_system/data/repositories/file_repository.dart'; |
|
|
|
|
import 'package:problem_check_system/data/repositories/image_repository.dart'; |
|
|
|
|
import 'package:problem_check_system/data/repositories/problem_repository.dart'; |
|
|
|
|
import 'package:problem_check_system/data/models/problem_model.dart'; |
|
|
|
|
import 'package:problem_check_system/modules/problem/views/widgets/models/date_range_enum.dart'; |
|
|
|
@ -272,7 +274,7 @@ class ProblemController extends GetxController
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
if (updatedProblem.syncStatus == ProblemSyncStatus.untracked) { |
|
|
|
|
problemRepository.deleteProblem(updatedProblem.id!); |
|
|
|
|
problemRepository.deleteProblem(updatedProblem.id); |
|
|
|
|
} else { |
|
|
|
|
problemRepository.updateProblem(updatedProblem); |
|
|
|
|
} |
|
|
|
@ -301,7 +303,7 @@ class ProblemController extends GetxController
|
|
|
|
|
final List<String> remoteUrls = []; |
|
|
|
|
if (problem.syncStatus != ProblemSyncStatus.pendingDelete) { |
|
|
|
|
final newImages = problem.imageUrls |
|
|
|
|
.where((img) => img.status == ImageStatus.local) |
|
|
|
|
.where((img) => img.status == ImageStatus.pendingUpload) |
|
|
|
|
.toList(); |
|
|
|
|
|
|
|
|
|
final totalFilesToUpload = newImages.length; |
|
|
|
@ -354,13 +356,13 @@ class ProblemController extends GetxController
|
|
|
|
|
break; |
|
|
|
|
case ProblemSyncStatus.pendingUpdate: |
|
|
|
|
response = await problemRepository.put( |
|
|
|
|
problem.id!, |
|
|
|
|
problem.id, |
|
|
|
|
apiPayload!, |
|
|
|
|
cancelToken, |
|
|
|
|
); |
|
|
|
|
break; |
|
|
|
|
case ProblemSyncStatus.pendingDelete: |
|
|
|
|
response = await problemRepository.delete(problem.id!, cancelToken); |
|
|
|
|
response = await problemRepository.delete(problem.id, cancelToken); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -400,7 +402,7 @@ class ProblemController extends GetxController
|
|
|
|
|
for (var image in images) { |
|
|
|
|
if (image.status == ImageStatus.synced) { |
|
|
|
|
finalRemoteUrls.add(image.remoteUrl!); |
|
|
|
|
} else if (image.status == ImageStatus.local) { |
|
|
|
|
} else if (image.status == ImageStatus.pendingUpload) { |
|
|
|
|
finalRemoteUrls.add(newRemoteUrls[newImageIndex]); |
|
|
|
|
newImageIndex++; |
|
|
|
|
} |
|
|
|
@ -418,7 +420,7 @@ class ProblemController extends GetxController
|
|
|
|
|
int uploadedUrlIndex = 0; |
|
|
|
|
|
|
|
|
|
for (var image in images) { |
|
|
|
|
if (image.status == ImageStatus.local) { |
|
|
|
|
if (image.status == ImageStatus.pendingUpload) { |
|
|
|
|
updatedImageMetadata.add( |
|
|
|
|
ImageMetadata( |
|
|
|
|
localPath: image.localPath, |
|
|
|
@ -438,21 +440,27 @@ class ProblemController extends GetxController
|
|
|
|
|
// #endregion |
|
|
|
|
|
|
|
|
|
// #region 问题同步 |
|
|
|
|
|
|
|
|
|
// TODO 同步服务器问题到本地 |
|
|
|
|
Future<void> pullDataFromServer() async { |
|
|
|
|
isLoading.value = true; |
|
|
|
|
try { |
|
|
|
|
// 1. 从服务器获取最新数据 |
|
|
|
|
final List<Problem> serverProblems = await problemRepository |
|
|
|
|
final List<ServerProblem> serverProblems = await problemRepository |
|
|
|
|
.fetchProblemsFromServer(); |
|
|
|
|
|
|
|
|
|
// 2. 获取本地数据 |
|
|
|
|
final List<Problem> localProblems = await problemRepository.getProblems(); |
|
|
|
|
|
|
|
|
|
// 3. 同步策略:以服务器数据为准,合并本地未上传的更改 |
|
|
|
|
await _syncProblems(serverProblems, localProblems); |
|
|
|
|
// 3. 同步策略:以服务器数据为准,保留本地未同步的更改 |
|
|
|
|
final List<Problem> downloadedProblems = await _syncProblems( |
|
|
|
|
serverProblems, |
|
|
|
|
localProblems, |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
// 4. 启动图片下载任务 |
|
|
|
|
_downloadImagesForProblems(downloadedProblems); |
|
|
|
|
|
|
|
|
|
// 4. 重新加载本地问题列表 |
|
|
|
|
// 5. 重新加载本地问题列表 |
|
|
|
|
await loadProblems(); |
|
|
|
|
|
|
|
|
|
Get.snackbar('成功', '数据同步完成', snackPosition: SnackPosition.TOP); |
|
|
|
@ -463,60 +471,152 @@ class ProblemController extends GetxController
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// 同步服务器和本地数据 |
|
|
|
|
Future<void> _syncProblems( |
|
|
|
|
List<Problem> serverProblems, |
|
|
|
|
/// 异步下载问题的图片 |
|
|
|
|
void _downloadImagesForProblems(List<Problem> problems) { |
|
|
|
|
if (problems.isEmpty) return; |
|
|
|
|
|
|
|
|
|
// 在后台执行图片下载 |
|
|
|
|
Future(() async { |
|
|
|
|
final imageRepository = Get.find<ImageRepository>(); // 使用GetX获取实例 |
|
|
|
|
|
|
|
|
|
for (final problem in problems) { |
|
|
|
|
try { |
|
|
|
|
final List<ImageMetadata> downloadedImages = []; |
|
|
|
|
|
|
|
|
|
for (final imageMeta in problem.imageUrls) { |
|
|
|
|
if (imageMeta.remoteUrl != null && |
|
|
|
|
imageMeta.remoteUrl!.isNotEmpty) { |
|
|
|
|
// 检查是否已下载 |
|
|
|
|
final bool isDownloaded = await imageRepository.isImageDownloaded( |
|
|
|
|
imageMeta.remoteUrl!, |
|
|
|
|
problem.id, |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
String localPath; |
|
|
|
|
if (isDownloaded) { |
|
|
|
|
// 如果已下载,获取本地路径 |
|
|
|
|
localPath = (await imageRepository.getLocalImagePath( |
|
|
|
|
imageMeta.remoteUrl!, |
|
|
|
|
problem.id, |
|
|
|
|
))!; |
|
|
|
|
} else { |
|
|
|
|
// 下载图片到本地 |
|
|
|
|
localPath = await imageRepository.downloadImage( |
|
|
|
|
imageMeta.remoteUrl!, |
|
|
|
|
problem.id, |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 更新图片元数据 |
|
|
|
|
final downloadedImage = imageMeta.copyWith( |
|
|
|
|
localPath: localPath, |
|
|
|
|
status: ImageStatus.synced, |
|
|
|
|
); |
|
|
|
|
downloadedImages.add(downloadedImage); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 更新问题的图片数据 |
|
|
|
|
if (downloadedImages.isNotEmpty) { |
|
|
|
|
final updatedProblem = problem.copyWith( |
|
|
|
|
imageUrls: downloadedImages, |
|
|
|
|
); |
|
|
|
|
await problemRepository.updateProblem(updatedProblem); |
|
|
|
|
} |
|
|
|
|
} catch (e) { |
|
|
|
|
Get.log('下载问题 ${problem.id} 的图片失败: $e'); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// 同步服务器和本地数据,返回需要下载图片的问题列表 |
|
|
|
|
Future<List<Problem>> _syncProblems( |
|
|
|
|
List<ServerProblem> serverProblems, |
|
|
|
|
List<Problem> localProblems, |
|
|
|
|
) async { |
|
|
|
|
final List<Problem> needDownloadImages = []; |
|
|
|
|
|
|
|
|
|
// 创建映射以便快速查找 |
|
|
|
|
final Map<String, Problem> serverProblemsMap = { |
|
|
|
|
for (var problem in serverProblems.where((p) => p.id != null)) |
|
|
|
|
problem.id!: problem, |
|
|
|
|
final Map<String, ServerProblem> serverProblemsMap = { |
|
|
|
|
for (var problem in serverProblems) problem.id: problem, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
final Map<String, Problem> localProblemsMap = { |
|
|
|
|
for (var problem in localProblems.where((p) => p.id != null)) |
|
|
|
|
problem.id!: problem, |
|
|
|
|
for (var problem in localProblems) problem.id: problem, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
// 处理服务器有但本地没有的数据(新增) |
|
|
|
|
for (final serverProblem in serverProblems) { |
|
|
|
|
if (serverProblem.id != null && |
|
|
|
|
!localProblemsMap.containsKey(serverProblem.id)) { |
|
|
|
|
if (!localProblemsMap.containsKey(serverProblem.id)) { |
|
|
|
|
// 服务器新增的问题,添加到本地 |
|
|
|
|
await problemRepository.insertProblem(serverProblem); |
|
|
|
|
final newProblem = _convertServerProblemToLocal(serverProblem); |
|
|
|
|
await problemRepository.insertProblem(newProblem); |
|
|
|
|
needDownloadImages.add(newProblem); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 处理本地有但服务器没有的数据(删除) |
|
|
|
|
for (final localProblem in localProblems) { |
|
|
|
|
if (localProblem.id != null && |
|
|
|
|
!serverProblemsMap.containsKey(localProblem.id)) { |
|
|
|
|
// 服务器已删除的问题,从本地删除 |
|
|
|
|
await problemRepository.deleteProblem(localProblem.id!); |
|
|
|
|
if (!serverProblemsMap.containsKey(localProblem.id)) { |
|
|
|
|
// 只有已同步的数据才从本地删除,未同步的数据保留 |
|
|
|
|
if (localProblem.syncStatus == ProblemSyncStatus.synced) { |
|
|
|
|
await problemRepository.deleteProblem(localProblem.id); |
|
|
|
|
} |
|
|
|
|
// 如果是未同步的数据(pendingCreate/pendingUpdate),保留在本地等待上传 |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 处理双方都有的数据(更新) |
|
|
|
|
for (final serverProblem in serverProblems) { |
|
|
|
|
if (serverProblem.id != null && |
|
|
|
|
localProblemsMap.containsKey(serverProblem.id)) { |
|
|
|
|
if (localProblemsMap.containsKey(serverProblem.id)) { |
|
|
|
|
final localProblem = localProblemsMap[serverProblem.id]!; |
|
|
|
|
|
|
|
|
|
// 只有当本地数据已同步时才更新(避免覆盖本地未上传的更改) |
|
|
|
|
if (localProblem.syncStatus == ProblemSyncStatus.synced) { |
|
|
|
|
// 比较更新时间,使用最新的数据 |
|
|
|
|
final serverUpdated = serverProblem.lastModifiedTime; |
|
|
|
|
final serverUpdated = serverProblem.lastModificationTime; |
|
|
|
|
final localUpdated = localProblem.lastModifiedTime; |
|
|
|
|
|
|
|
|
|
if (serverUpdated.isAfter(localUpdated)) { |
|
|
|
|
// 服务器数据更新,更新本地数据 |
|
|
|
|
await problemRepository.updateProblem(serverProblem); |
|
|
|
|
final updatedProblem = _convertServerProblemToLocal(serverProblem); |
|
|
|
|
await problemRepository.updateProblem(updatedProblem); |
|
|
|
|
needDownloadImages.add(updatedProblem); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// 如果本地有未同步的更改,保留本地更改(下次上传时会同步到服务器) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return needDownloadImages; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// 将服务器问题转换为本地问题模型 |
|
|
|
|
Problem _convertServerProblemToLocal(ServerProblem serverProblem) { |
|
|
|
|
// 转换图片URL为ImageMetadata列表(初始状态为待下载) |
|
|
|
|
final List<ImageMetadata> imageMetadatas = (serverProblem.imageUrls ?? []) |
|
|
|
|
.map( |
|
|
|
|
(url) => ImageMetadata( |
|
|
|
|
remoteUrl: url, |
|
|
|
|
localPath: '', // 初始为空,等待下载 |
|
|
|
|
status: ImageStatus.pendingDownload, // 标记为待下载状态 |
|
|
|
|
), |
|
|
|
|
) |
|
|
|
|
.toList(); |
|
|
|
|
|
|
|
|
|
return Problem( |
|
|
|
|
id: serverProblem.id, |
|
|
|
|
description: serverProblem.title, |
|
|
|
|
location: serverProblem.location, |
|
|
|
|
imageUrls: imageMetadatas, |
|
|
|
|
creationTime: serverProblem.creationTime, |
|
|
|
|
lastModifiedTime: serverProblem.lastModificationTime, |
|
|
|
|
syncStatus: ProblemSyncStatus.synced, // 来自服务器的数据标记为已同步 |
|
|
|
|
censorTaskId: serverProblem.censorTaskId, |
|
|
|
|
bindData: serverProblem.bindData, |
|
|
|
|
isChecked: false, // 默认未检查 |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
// #endregion |
|
|
|
|
|
|
|
|
@ -710,7 +810,7 @@ class ProblemController extends GetxController
|
|
|
|
|
final deleteProblem = ProblemStateManager.markForDeletion(problem); |
|
|
|
|
if (deleteProblem.syncStatus == ProblemSyncStatus.untracked) { |
|
|
|
|
// 直接删除问题和图片 |
|
|
|
|
await problemRepository.deleteProblem(problem.id!); |
|
|
|
|
await problemRepository.deleteProblem(problem.id); |
|
|
|
|
await _deleteProblemImages(problem); |
|
|
|
|
} else { |
|
|
|
|
// 更新状态 |
|
|
|
|