Browse Source

feat : 根据问题状态进行物理删除与逻辑删除

dev
徐振升 1 day ago
parent
commit
96f1ef1efd
  1. 1
      lib/core/utils/constants/api_endpoints.dart
  2. 10
      lib/data/models/operation.dart
  3. 52
      lib/data/models/problem_model.dart
  4. 98
      lib/data/models/problem_sync_status.dart
  5. 7
      lib/data/models/sync_status.dart
  6. 93
      lib/data/providers/sqlite_provider.dart
  7. 176
      lib/data/repositories/problem_repository.dart
  8. 250
      lib/modules/problem/controllers/problem_controller.dart
  9. 10
      lib/modules/problem/controllers/problem_form_controller.dart
  10. 26
      lib/modules/problem/views/problem_list_page.dart
  11. 22
      lib/modules/problem/views/widgets/problem_card.dart

1
lib/core/utils/constants/api_endpoints.dart

@ -11,7 +11,6 @@ abstract class ApiEndpoints {
// Memorandum
static const String getProblem = '/api/Memorandum';
static const String postProblem = '/api/Memorandum';
static const String deleteProblem = '/api/Memorandum';
static String putProblemById(String id) => '/api/Memorandum/$id';
static String deleteProblemById(String id) => '/api/Memorandum/$id';

10
lib/data/models/operation.dart

@ -1,10 +0,0 @@
enum Operation {
///
create,
///
update,
///
delete,
}

52
lib/data/models/problem_model.dart

@ -1,8 +1,7 @@
import 'dart:convert';
import 'package:problem_check_system/data/models/image_metadata_model.dart';
import 'package:problem_check_system/data/models/operation.dart';
import 'package:problem_check_system/data/models/sync_status.dart';
import 'package:problem_check_system/data/models/problem_sync_status.dart';
import 'package:uuid/uuid.dart';
///
@ -24,13 +23,10 @@ class Problem {
final DateTime creationTime;
///
final SyncStatus syncStatus;
final ProblemSyncStatus syncStatus;
///
final Operation operation;
///
final bool isDeleted;
///
final DateTime lastModifiedTime;
/// ID
final String? censorTaskId;
@ -41,38 +37,19 @@ class Problem {
/// false
final bool isChecked;
/// uuid
static final Uuid _uuid = Uuid();
Problem({
this.id,
required this.description,
required this.location,
required this.imageUrls,
required this.creationTime,
this.syncStatus = SyncStatus.notSynced,
this.operation = Operation.create,
this.isDeleted = false,
required this.lastModifiedTime,
this.syncStatus = ProblemSyncStatus.pendingCreate,
this.censorTaskId,
this.bindData,
this.isChecked = false,
});
/// ID
factory Problem.create({
required String description,
required String location,
required List<ImageMetadata> imageUrls,
}) {
return Problem(
id: _uuid.v4(),
description: description,
location: location,
imageUrls: imageUrls,
creationTime: DateTime.now(),
);
}
/// copyWith
Problem copyWith({
String? id,
@ -80,8 +57,8 @@ class Problem {
String? location,
List<ImageMetadata>? imageUrls,
DateTime? creationTime,
SyncStatus? syncStatus,
Operation? operation,
DateTime? lastModifiedTime,
ProblemSyncStatus? syncStatus,
bool? isDeleted,
String? censorTaskId,
String? bindData,
@ -93,9 +70,8 @@ class Problem {
location: location ?? this.location,
imageUrls: imageUrls ?? this.imageUrls,
creationTime: creationTime ?? this.creationTime,
lastModifiedTime: lastModifiedTime ?? this.lastModifiedTime,
syncStatus: syncStatus ?? this.syncStatus,
operation: operation ?? this.operation,
isDeleted: isDeleted ?? this.isDeleted,
censorTaskId: censorTaskId ?? this.censorTaskId,
bindData: bindData ?? this.bindData,
isChecked: isChecked ?? this.isChecked,
@ -110,9 +86,8 @@ class Problem {
'location': location,
'imageUrls': json.encode(imageUrls.map((e) => e.toMap()).toList()),
'creationTime': creationTime.millisecondsSinceEpoch,
'lastModifiedTime': lastModifiedTime.millisecondsSinceEpoch,
'syncStatus': syncStatus.index,
'operation': operation.index,
'isDeleted': isDeleted ? 1 : 0,
'censorTaskId': censorTaskId,
'bindData': bindData,
'isChecked': isChecked ? 1 : 0,
@ -139,9 +114,10 @@ class Problem {
location: map['location'],
imageUrls: imageUrlsList,
creationTime: DateTime.fromMillisecondsSinceEpoch(map['creationTime']),
syncStatus: SyncStatus.values[map['syncStatus']],
operation: Operation.values[map['operation']],
isDeleted: map['isDeleted'] == 1,
lastModifiedTime: DateTime.fromMillisecondsSinceEpoch(
map['lastModifiedTime'],
),
syncStatus: ProblemSyncStatus.values[map['syncStatus']],
censorTaskId: map['censorTaskId'],
bindData: map['bindData'],
isChecked: map['isChecked'] == 1,

98
lib/data/models/problem_sync_status.dart

@ -0,0 +1,98 @@
import 'package:problem_check_system/data/models/image_metadata_model.dart';
import 'package:problem_check_system/data/models/problem_model.dart';
import 'package:uuid/uuid.dart';
enum ProblemSyncStatus {
/// -
untracked,
/// - git的unmodified
synced,
/// - git的untracked staged
pendingCreate,
/// - git的modified staged
pendingUpdate,
/// - git的deleted staged
pendingDelete,
}
/// - git add/git commit
class ProblemStateManager {
/// uuid
static final Uuid _uuid = Uuid();
///
static Problem createNewProblem({
required String description,
required String location,
required List<ImageMetadata> imageUrls,
}) {
return Problem(
id: _uuid.v4(),
description: description,
location: location,
imageUrls: imageUrls,
creationTime: DateTime.now(),
lastModifiedTime: DateTime.now(),
syncStatus: ProblemSyncStatus.pendingCreate,
);
}
///
static Problem modifyProblem(Problem problem) {
final newStatus = problem.syncStatus == ProblemSyncStatus.synced
? ProblemSyncStatus
.pendingUpdate //
: problem.syncStatus; //
return problem.copyWith(
syncStatus: newStatus,
lastModifiedTime: DateTime.now(),
);
}
///
static Problem markForDeletion(Problem problem) {
switch (problem.syncStatus) {
case ProblemSyncStatus.pendingCreate:
//
return problem.copyWith(
syncStatus: ProblemSyncStatus.untracked,
lastModifiedTime: DateTime.now(),
);
case ProblemSyncStatus.synced:
case ProblemSyncStatus.pendingUpdate:
//
return problem.copyWith(
syncStatus: ProblemSyncStatus.pendingDelete,
lastModifiedTime: DateTime.now(),
);
case ProblemSyncStatus.untracked:
case ProblemSyncStatus.pendingDelete:
//
return problem;
}
}
/// git reset
static Problem undoDeletion(Problem problem) {
if (problem.syncStatus == ProblemSyncStatus.pendingDelete) {
return problem.copyWith(
syncStatus: ProblemSyncStatus.pendingUpdate,
lastModifiedTime: DateTime.now(),
);
}
return problem;
}
/// git commit
static Problem markAsSynced(Problem problem) {
return problem.copyWith(
syncStatus: ProblemSyncStatus.synced,
lastModifiedTime: DateTime.now(),
);
}
}

7
lib/data/models/sync_status.dart

@ -1,7 +0,0 @@
enum SyncStatus {
///
synced,
///
notSynced,
}

93
lib/data/providers/sqlite_provider.dart

@ -1,7 +1,7 @@
// sqlite_provider.dart
import 'package:get/get.dart';
import 'package:problem_check_system/data/models/sync_status.dart';
import 'package:problem_check_system/data/models/problem_sync_status.dart';
import 'package:problem_check_system/data/models/problem_model.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
@ -50,9 +50,8 @@ class SQLiteProvider extends GetxService {
location TEXT NOT NULL,
imageUrls TEXT NOT NULL,
creationTime INTEGER NOT NULL,
lastModifiedTime INTEGER NOT NULL,
syncStatus INTEGER NOT NULL,
operation INTEGER NOT NULL,
isDeleted INTEGER NOT NULL,
censorTaskId TEXT,
bindData TEXT,
isChecked INTEGER NOT NULL
@ -90,14 +89,9 @@ class SQLiteProvider extends GetxService {
///
Future<int> insertProblem(Problem problem) async {
try {
//
final problemToInsert = problem.copyWith(
syncStatus: SyncStatus.notSynced,
);
final result = await _database.insert(
_tableName,
problemToInsert.toMap(),
problem.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
@ -105,30 +99,28 @@ class SQLiteProvider extends GetxService {
return result;
} catch (e) {
Get.log('插入问题失败(ID: ${problem.id}):$e', isError: true);
return 0;
throw Exception('');
}
}
///
Future<int> deleteProblem(String id) async {
///
Future<int> deleteProblem(String problemId) async {
try {
final result = await _database.update(
final result = await _database.delete(
_tableName,
{
'isDeleted': 1,
'syncStatus': SyncStatus.notSynced.index, //
},
where: 'id = ?',
whereArgs: [id],
whereArgs: [problemId],
);
if (result > 0) {
Get.log('问题逻辑删除成功,ID: $id');
Get.log('问题删除成功,ID: $problemId');
} else {
Get.log('未找到要删除的问题,ID: $problemId');
}
return result;
} catch (e) {
Get.log('逻辑删除问题失败(ID: $id):$e', isError: true);
Get.log('删除问题失败(ID: $problemId):$e', isError: true);
return 0;
}
}
@ -154,32 +146,32 @@ class SQLiteProvider extends GetxService {
}
}
///
Future<List<Problem>> getProblemsForSync() async {
try {
final results = await _database.query(
_tableName,
where: 'syncStatus = ?',
whereArgs: [SyncStatus.notSynced.index],
orderBy: 'creationTime ASC',
);
// ///
// Future<List<Problem>> getProblemsForSync() async {
// try {
// final results = await _database.query(
// _tableName,
// where: 'syncStatus = ?',
// whereArgs: [SyncStatus.notSynced.index],
// orderBy: 'creationTime ASC',
// );
Get.log('找到 ${results.length} 条需要同步的记录');
return results.map((json) => Problem.fromMap(json)).toList();
} catch (e) {
Get.log('获取待同步问题失败:$e', isError: true);
return [];
}
}
// Get.log('找到 ${results.length} 条需要同步的记录');
// return results.map((json) => Problem.fromMap(json)).toList();
// } catch (e) {
// Get.log('获取待同步问题失败:$e', isError: true);
// return [];
// }
// }
///
Future<int> markAsSynced(String id) async {
try {
final result = await _database.update(
_tableName,
{'syncStatus': SyncStatus.synced.index},
where: 'id = ? AND syncStatus = ?',
whereArgs: [id, SyncStatus.notSynced.index],
{'syncStatus': ProblemSyncStatus.synced.index},
where: 'id = ?',
whereArgs: [id],
);
if (result > 0) {
@ -198,7 +190,7 @@ class SQLiteProvider extends GetxService {
try {
final results = await _database.query(
_tableName,
where: 'id = ? AND isDeleted = 0',
where: 'id = ?',
whereArgs: [id],
limit: 1,
);
@ -216,17 +208,11 @@ class SQLiteProvider extends GetxService {
DateTime? endDate,
String? syncStatus,
String? bindStatus,
bool includeDeleted = false,
}) async {
try {
final whereClauses = <String>[];
final whereArgs = <dynamic>[];
//
if (!includeDeleted) {
whereClauses.add('isDeleted = 0');
}
//
if (startDate != null) {
whereClauses.add('creationTime >= ?');
@ -240,12 +226,17 @@ class SQLiteProvider extends GetxService {
//
if (syncStatus != null && syncStatus != '全部') {
final statusValue = syncStatus == '已上传'
? SyncStatus.synced.index
: SyncStatus.notSynced.index;
if (syncStatus == '未上传') {
whereClauses.add('syncStatus IN (?, ?, ?)');
whereArgs.addAll([
ProblemSyncStatus.pendingCreate.index,
ProblemSyncStatus.pendingUpdate.index,
ProblemSyncStatus.pendingDelete.index,
]);
} else {
whereClauses.add('syncStatus = ?');
whereArgs.add(statusValue);
whereArgs.add(ProblemSyncStatus.synced.index);
}
}
//

176
lib/data/repositories/problem_repository.dart

@ -1,17 +1,10 @@
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:get/get.dart' hide MultipartFile, FormData, Response;
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';
///
///
@ -19,7 +12,6 @@ class ProblemRepository extends GetxService {
final SQLiteProvider sqliteProvider;
final HttpProvider httpProvider;
final ConnectivityProvider connectivityProvider;
final FileRepository fileRepository = Get.find<FileRepository>();
RxBool get isOnline => connectivityProvider.isOnline;
@ -30,18 +22,8 @@ class ProblemRepository extends GetxService {
});
///
///
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);
}
}
///
@ -66,151 +48,51 @@ class ProblemRepository extends GetxService {
await sqliteProvider.insertProblem(problem);
}
Future<void> deleteProblem(String id) async {
await sqliteProvider.deleteProblem(id);
Future<void> deleteProblem(String problemId) async {
await sqliteProvider.deleteProblem(problemId);
}
// * /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: '上传已取消',
);
/// getAll
Future<Response> getAll() async {
final response = await httpProvider.get(ApiEndpoints.getProblem);
return response;
}
final url = await fileRepository.uploadImage(
image.localPath,
/// post
Future<Response> post(
Map<String, Object?> apiPayload,
CancelToken cancelToken,
) async {
// 3.
final response = await httpProvider.post(
ApiEndpoints.postProblem,
data: apiPayload,
cancelToken: cancelToken,
onSendProgress: (sent, total) {
double overallProgress =
(filesUploadedCount + (sent / total)) / totalFilesToUpload;
onProgress(overallProgress);
},
);
remoteUrls.add(url);
filesUploadedCount++;
return response;
}
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,
'title': problem.description,
'location': problem.location,
'imageUrls': finalRemoteUrls,
'creationTime': problem.creationTime.toUtc().toIso8601String(),
};
/// put
Future<Response> put(
Map<String, Object?> apiPayload,
CancelToken cancelToken,
) async {
// 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 response;
}
//
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,
/// delete
Future<Response> delete(String id, CancelToken cancelToken) async {
// 3.
final response = await httpProvider.delete(
ApiEndpoints.deleteProblemById(id),
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();
return response;
}
}

250
lib/modules/problem/controllers/problem_controller.dart

@ -4,10 +4,14 @@ import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart' hide MultipartFile, FormData;
import 'package:get/get.dart' hide MultipartFile, FormData, Response;
import 'package:flutter/material.dart';
import 'package:problem_check_system/app/routes/app_routes.dart';
import 'package:problem_check_system/data/models/sync_status.dart';
import 'package:problem_check_system/core/extensions/http_response_extension.dart';
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/repositories/file_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';
@ -17,6 +21,7 @@ class ProblemController extends GetxController
with GetSingleTickerProviderStateMixin {
///
final ProblemRepository problemRepository;
final FileRepository fileRepository = Get.find<FileRepository>();
///
final RxList<Problem> problems = <Problem>[].obs;
@ -39,7 +44,7 @@ class ProblemController extends GetxController
///
int get selectedUnUploadCount => _selectedProblems
.where((p) => p.syncStatus == SyncStatus.notSynced)
.where((p) => p.syncStatus != ProblemSyncStatus.synced)
.length;
// ProblemController
@ -68,7 +73,15 @@ class ProblemController extends GetxController
final Rx<DateTime> historyStartTime = DateTime.now()
.subtract(const Duration(days: 7))
.obs;
final Rx<DateTime> historyEndTime = DateTime.now().obs;
final Rx<DateTime> historyEndTime = DateTime(
DateTime.now().year,
DateTime.now().month,
DateTime.now().day,
23,
59,
59,
999,
).obs;
final RxString historyUploadFilter = '全部'.obs;
final RxString historyBindFilter = '全部'.obs;
@ -147,7 +160,7 @@ class ProblemController extends GetxController
showUploadProgressDialog();
try {
await problemRepository.uploadProblems(
await uploadProblems(
_selectedProblems.toList(), //
cancelToken: _cancelToken,
onProgress: (progress) {
@ -226,14 +239,187 @@ class ProblemController extends GetxController
// Get.snackbar('提示', '上传已取消');
}
void uploadProblems() async {
// if (selectedUnUploadedProblems.isEmpty) return;
// // API
// //
// selectedUnUploadedProblems.clear();
// for (var problem in selectedUnUploadedProblems) {
// await uploadProblem(problem);
// }
///
///
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);
},
);
problemRepository.updateProblem(updatedProblem);
// updatedProblems.add(updatedProblem);
}
// return updatedProblems;
} on DioException {
rethrow;
}
}
///
///
Future<Problem> uploadProblem(
Problem problem, {
required CancelToken cancelToken,
required void Function(double progress) onProgress,
}) async {
try {
//
if (problem.syncStatus == ProblemSyncStatus.synced ||
problem.syncStatus == ProblemSyncStatus.untracked) {
throw Exception('问题已同步,无需再次同步');
}
// 1.
final List<String> remoteUrls = [];
if (problem.syncStatus != ProblemSyncStatus.pendingDelete) {
final newImages = problem.imageUrls
.where((img) => img.status == ImageStatus.local)
.toList();
final totalFilesToUpload = newImages.length;
int filesUploadedCount = 0;
for (var image in newImages) {
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);
}
// 2. API payloadpayload
final apiPayload = problem.syncStatus != ProblemSyncStatus.pendingDelete
? {
'id': problem.id,
'title': problem.description,
'location': problem.location,
'imageUrls': _buildFinalRemoteUrls(problem.imageUrls, remoteUrls),
'creationTime': problem.creationTime.toUtc().toIso8601String(),
}
: null;
// 3. API
late final Response response;
switch (problem.syncStatus) {
case ProblemSyncStatus.untracked:
case ProblemSyncStatus.synced:
throw Exception('无效的操作类型: none');
case ProblemSyncStatus.pendingCreate:
response = await problemRepository.post(apiPayload!, cancelToken);
break;
case ProblemSyncStatus.pendingUpdate:
response = await problemRepository.put(apiPayload!, cancelToken);
break;
case ProblemSyncStatus.pendingDelete:
response = await problemRepository.delete(problem.id!, cancelToken);
break;
}
// 4.
if (response.isSuccess) {
//
final updatedImageMetadata =
problem.syncStatus != ProblemSyncStatus.pendingDelete
? _updateImageMetadata(problem.imageUrls, remoteUrls)
: problem.imageUrls;
// none
return problem.copyWith(
syncStatus: ProblemSyncStatus.synced, // none
imageUrls: updatedImageMetadata,
);
} else {
throw Exception('操作失败,状态码: ${response.statusCode}');
}
} on DioException {
rethrow;
}
}
/// URL列表
List<String> _buildFinalRemoteUrls(
List<ImageMetadata> images,
List<String> newRemoteUrls,
) {
final List<String> finalRemoteUrls = [];
int newImageIndex = 0;
for (var image in images) {
if (image.status == ImageStatus.synced) {
finalRemoteUrls.add(image.remoteUrl!);
} else if (image.status == ImageStatus.local) {
finalRemoteUrls.add(newRemoteUrls[newImageIndex]);
newImageIndex++;
}
}
return finalRemoteUrls;
}
///
List<ImageMetadata> _updateImageMetadata(
List<ImageMetadata> images,
List<String> newRemoteUrls,
) {
final List<ImageMetadata> updatedImageMetadata = [];
int uploadedUrlIndex = 0;
for (var image in images) {
if (image.status == ImageStatus.local) {
updatedImageMetadata.add(
ImageMetadata(
localPath: image.localPath,
remoteUrl: newRemoteUrls[uploadedUrlIndex],
status: ImageStatus.synced,
),
);
uploadedUrlIndex++;
} else {
updatedImageMetadata.add(image);
}
}
return updatedImageMetadata;
}
// #endregion
@ -406,12 +592,14 @@ class ProblemController extends GetxController
loadUnUploadedProblems();
}
//
//
Future<void> loadUnUploadedProblems() async {
isLoading.value = true;
try {
// _localDatabase.getProblems '未上传'
unUploadedProblems.value = await problemRepository.getProblemsForSync();
unUploadedProblems.value = await problemRepository.getProblems(
syncStatus: '未上传',
);
} catch (e) {
Get.snackbar('错误', '加载未上传问题失败: $e');
} finally {
@ -419,33 +607,21 @@ class ProblemController extends GetxController
}
}
// Future<void> addProblem(Problem problem) async {
// try {
// await problemRepository.insertProblem(problem);
// loadProblems();
// } catch (e) {
// Get.snackbar('错误', '保存问题失败: $e');
// rethrow;
// }
// }
// Future<void> updateProblem(Problem problem) async {
// try {
// await problemRepository.updateProblem(problem);
// loadProblems();
// } catch (e) {
// Get.snackbar('错误', '更新问题失败: $e');
// rethrow;
// }
// }
///
///
Future<void> deleteProblem(Problem problem) async {
try {
if (problem.id != null) {
final deleteProblem = ProblemStateManager.markForDeletion(problem);
if (deleteProblem.syncStatus == ProblemSyncStatus.untracked) {
//
await problemRepository.deleteProblem(problem.id!);
await _deleteProblemImages(problem);
loadProblems();
} else {
//
await problemRepository.updateProblem(deleteProblem);
}
loadProblems();
} catch (e) {
Get.snackbar('错误', '删除问题失败: $e');
rethrow;

10
lib/modules/problem/controllers/problem_form_controller.dart

@ -8,6 +8,7 @@ import 'package:problem_check_system/data/models/image_status.dart';
import 'package:problem_check_system/data/models/image_metadata_model.dart';
import 'dart:io';
import 'package:problem_check_system/data/models/problem_model.dart';
import 'package:problem_check_system/data/models/problem_sync_status.dart';
import 'package:problem_check_system/data/repositories/problem_repository.dart';
class ProblemFormController extends GetxController {
@ -141,25 +142,26 @@ class ProblemFormController extends GetxController {
final List<ImageMetadata> imagePaths = await _saveImagesToLocal();
if (problem != null) {
//
final updatedProblem = problem!.copyWith(
description: descriptionController.text,
location: locationController.text,
imageUrls: imagePaths,
);
//
final modifyProblem = ProblemStateManager.modifyProblem(updatedProblem);
await problemRepository.updateProblem(updatedProblem);
await problemRepository.updateProblem(modifyProblem);
Get.back(result: true); //
Get.snackbar('成功', '问题已更新');
} else {
//
final problem = Problem.create(
final newProblem = ProblemStateManager.createNewProblem(
description: descriptionController.text,
location: locationController.text,
imageUrls: imagePaths,
);
await problemRepository.insertProblem(problem);
await problemRepository.insertProblem(newProblem);
Get.back(result: true); //
Get.snackbar('成功', '问题已保存');
}

26
lib/modules/problem/views/problem_list_page.dart

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:problem_check_system/data/models/problem_sync_status.dart';
import 'package:problem_check_system/modules/problem/controllers/problem_controller.dart';
import 'package:problem_check_system/data/models/problem_model.dart';
import 'package:problem_check_system/modules/problem/views/widgets/problem_card.dart';
@ -37,10 +38,16 @@ class ProblemListPage extends GetView<ProblemController> {
}
Widget _buildSwipeableProblemCard(Problem problem) {
// buttons
//
final bool isPendingDelete =
problem.syncStatus == ProblemSyncStatus.pendingDelete;
if (viewType == ProblemCardViewType.buttons) {
// buttons
if (!isPendingDelete) {
//
return Dismissible(
key: Key(problem.id ?? UniqueKey().toString()),
key: ValueKey('${problem.id}-${problem.syncStatus}'),
direction: DismissDirection.endToStart,
background: Container(
color: Colors.red,
@ -59,18 +66,27 @@ class ProblemListPage extends GetView<ProblemController> {
key: ValueKey(problem.id),
problem: problem,
viewType: viewType,
isSelected: false, // false
isSelected: false,
),
);
} else {
//
return ProblemCard(
key: ValueKey(problem.id),
problem: problem,
viewType: viewType,
isSelected: false,
);
}
} else {
// listgrid等使 Obx
return Obx(() {
// 使 Obx
final isSelected = controller.selectedProblems.contains(problem);
return ProblemCard(
key: ValueKey(problem.id),
problem: problem,
viewType: viewType,
isSelected: isSelected, //
isSelected: isSelected,
onChanged: (problem, isChecked) {
controller.updateProblemSelection(problem, isChecked);
},

22
lib/modules/problem/views/widgets/problem_card.dart

@ -3,7 +3,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:problem_check_system/app/routes/app_routes.dart';
import 'package:problem_check_system/data/models/sync_status.dart';
import 'package:problem_check_system/data/models/problem_sync_status.dart';
import 'package:problem_check_system/data/models/problem_model.dart';
import 'package:problem_check_system/modules/problem/views/widgets/custom_button.dart';
import 'package:tdesign_flutter/tdesign_flutter.dart';
@ -29,7 +29,8 @@ class ProblemCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
//
final bool isDeleted = problem.isDeleted;
final bool isDeleted =
problem.syncStatus == ProblemSyncStatus.pendingDelete;
final Color cardColor = isDeleted
? Colors.grey[300]!
: Theme.of(context).cardColor;
@ -101,19 +102,20 @@ class ProblemCard extends StatelessWidget {
Wrap(
spacing: 8,
children: [
...problem.isDeleted
...problem.syncStatus == ProblemSyncStatus.pendingDelete
? [
TDTag(
'已删除',
theme: TDTagTheme.danger,
isLight: true,
theme: TDTagTheme.defaultTheme,
textColor: isDeleted ? Colors.grey[700] : null,
backgroundColor: isDeleted
? Colors.grey[400]
: null,
// backgroundColor: isDeleted
// ? Colors.grey[400]
// : null,
),
]
: [
problem.syncStatus == SyncStatus.synced
problem.syncStatus == ProblemSyncStatus.synced
? TDTag(
'已上传',
isLight: true,
@ -232,9 +234,7 @@ class ProblemCard extends StatelessWidget {
padding: EdgeInsets.only(right: 16.w),
child: Checkbox(
value: isSelected,
onChanged: isDeleted
? null
: (bool? value) {
onChanged: (bool? value) {
if (value != null) {
onChanged?.call(problem, value);
}

Loading…
Cancel
Save