Browse Source

feat : 不可变设计模式

dev
徐振升 7 days ago
parent
commit
52cc2e11fd
  1. 2
      lib/data/models/image_metadata_model.dart
  2. 13
      lib/data/models/image_status.dart
  3. 25
      lib/data/models/operation.dart
  4. 68
      lib/data/models/problem_model.dart
  5. 21
      lib/data/models/sync_status.dart
  6. 183
      lib/data/providers/sqlite_provider.dart
  7. 3
      lib/data/repositories/problem_repository.dart
  8. 26
      lib/modules/problem/controllers/problem_controller.dart
  9. 3
      lib/modules/problem/controllers/problem_form_controller.dart
  10. 6
      lib/modules/problem/views/widgets/problem_card.dart

2
lib/data/models/image_metadata_model.dart

@ -1,5 +1,5 @@
// image_metadata_model.dart
import 'package:problem_check_system/data/models/enum_model.dart';
import 'package:problem_check_system/data/models/image_status.dart';
class ImageMetadata {
final String localPath;

13
lib/data/models/enum_model.dart → lib/data/models/image_status.dart

@ -1,15 +1,4 @@
enum SyncStatus {
///
notSynced,
///
synced,
///
modified,
}
//
///
enum ImageStatus {
///
local,

25
lib/data/models/operation.dart

@ -0,0 +1,25 @@
enum Operation {
///
create,
///
update,
///
delete,
}
/// Operation
extension OperationExtension on Operation {
///
String get name {
switch (this) {
case Operation.create:
return '创建';
case Operation.update:
return '修改';
case Operation.delete:
return '删除';
}
}
}

68
lib/data/models/problem_model.dart

@ -1,32 +1,59 @@
// problem_model.dart
import 'dart:convert';
import 'package:problem_check_system/data/models/enum_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/image_metadata_model.dart';
///
///
class Problem {
String? id;
String description;
String location;
List<ImageMetadata> imageUrls;
DateTime creationTime;
SyncStatus syncStatus;
String? censorTaskId;
String? bindData;
bool isChecked;
///
final String? id;
///
final String description;
///
final String location;
///
final List<ImageMetadata> imageUrls;
///
final DateTime creationTime;
///
final SyncStatus syncStatus;
///
final Operation operation;
/// ID
final String? censorTaskId;
///
final String? bindData;
/// false
final bool isChecked;
Problem({
/// Problem
const Problem({
this.id,
required this.description,
required this.location,
required this.imageUrls,
required this.creationTime,
this.syncStatus = SyncStatus.notSynced,
this.operation = Operation.create,
this.censorTaskId,
this.bindData,
this.isChecked = false,
});
// copyWith method to create a new instance with updated values
///
///
Problem copyWith({
String? id,
String? description,
@ -34,8 +61,10 @@ class Problem {
List<ImageMetadata>? imageUrls,
DateTime? creationTime,
SyncStatus? syncStatus,
Operation? operation,
String? censorTaskId,
String? bindData,
bool? isChecked,
}) {
return Problem(
id: id ?? this.id,
@ -44,31 +73,37 @@ class Problem {
imageUrls: imageUrls ?? this.imageUrls,
creationTime: creationTime ?? this.creationTime,
syncStatus: syncStatus ?? this.syncStatus,
operation: operation ?? this.operation,
censorTaskId: censorTaskId ?? this.censorTaskId,
bindData: bindData ?? this.bindData,
isChecked: isChecked ?? this.isChecked,
);
}
// toMap method for serializing to a database-friendly Map
/// Problem Map便
Map<String, dynamic> toMap() {
return {
'id': id,
'description': description,
'location': location,
// 使 jsonEncode JSON
'imageUrls': jsonEncode(imageUrls.map((meta) => meta.toMap()).toList()),
'creationTime': creationTime.millisecondsSinceEpoch,
'syncStatus': syncStatus.index,
'operation': operation.index, // operation
'censorTaskId': censorTaskId,
'bindData': bindData,
'isChecked': isChecked, // isChecked
};
}
// fromMap factory constructor for deserializing from a Map
/// Map Problem
factory Problem.fromMap(Map<String, dynamic> map) {
return Problem(
id: map['id'],
description: map['description'],
location: map['location'],
// jsonDecode JSON ImageMetadata
imageUrls: (jsonDecode(map['imageUrls']) as List)
.map((item) => ImageMetadata.fromMap(item as Map<String, dynamic>))
.toList(),
@ -76,8 +111,13 @@ class Problem {
map['creationTime'] as int,
),
syncStatus: SyncStatus.values[map['syncStatus'] as int],
operation:
map.containsKey('operation') // operation
? Operation.values[map['operation'] as int]
: Operation.create, //
censorTaskId: map['censorTaskId'],
bindData: map['bindData'],
isChecked: map['isChecked'] as bool? ?? false, // isChecked
);
}
}

21
lib/data/models/sync_status.dart

@ -0,0 +1,21 @@
enum SyncStatus {
///
synced,
///
notSynced,
}
//
extension SyncStatusExtension on SyncStatus {
String get displayName {
switch (this) {
case SyncStatus.synced:
return '已上传';
case SyncStatus.notSynced:
return '未上传';
}
}
bool get isSynced => this == SyncStatus.synced;
}

183
lib/data/providers/sqlite_provider.dart

@ -1,6 +1,7 @@
// sqlite_provider.dart
import 'package:get/get.dart';
import 'package:problem_check_system/data/models/enum_model.dart';
import 'package:problem_check_system/data/models/sync_status.dart';
import 'package:problem_check_system/data/models/problem_model.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
@ -24,13 +25,19 @@ class SQLiteProvider extends GetxService {
///
Future<void> _initDatabase() async {
final databasePath = await getDatabasesPath();
final path = join(databasePath, _dbName);
_database = await openDatabase(path, version: 1, onCreate: _onCreate);
try {
final databasePath = await getDatabasesPath();
final path = join(databasePath, _dbName);
_database = await openDatabase(path, version: 1, onCreate: _onCreate);
} catch (e) {
// Get.log('数据库初始化失败:$e');
rethrow;
}
}
///
/// **** `operation` `isChecked`
Future<void> _onCreate(Database db, int version) async {
await db.execute('''
CREATE TABLE $_tableName(
@ -40,102 +47,146 @@ class SQLiteProvider extends GetxService {
imageUrls TEXT NOT NULL,
creationTime INTEGER NOT NULL,
syncStatus INTEGER NOT NULL,
operation INTEGER NOT NULL, --
censorTaskId TEXT,
bindData TEXT
bindData TEXT,
isChecked INTEGER NOT NULL -- SQLite INTEGER
)
''');
}
/// ---
// ---
/// ** (CRUD) **
///
/// `problem` `id` UUID
///
/// `Future<int>`ID 0
Future<int> insertProblem(Problem problem) async {
final problemToInsert = problem.copyWith(
id: problem.id ?? const Uuid().v4(),
);
return await _database.insert(
_tableName,
problemToInsert.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
try {
final problemToInsert = problem.copyWith(
id: problem.id ?? const Uuid().v4(),
);
return await _database.insert(
_tableName,
problemToInsert.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
} catch (e) {
//
return 0; // 0
}
}
/// ID
///
///
/// `id` - ID
/// `Future<int>`
Future<int> deleteProblem(String id) async {
return await _database.delete(_tableName, where: 'id = ?', whereArgs: [id]);
try {
return await _database.delete(
_tableName,
where: 'id = ?',
whereArgs: [id],
);
} catch (e) {
//
return 0;
}
}
///
///
///
/// `problem` - `Problem`
/// `Future<int>`
Future<int> updateProblem(Problem problem) async {
return await _database.update(
_tableName,
problem.toMap(),
where: 'id = ?',
whereArgs: [problem.id],
);
try {
return await _database.update(
_tableName,
problem.toMap(),
where: 'id = ?',
whereArgs: [problem.id],
);
} catch (e) {
//
return 0;
}
}
/// ID
/// `Problem` `null`
///
/// `id` - ID
/// `Future<Problem?>` `Problem` `null`
Future<Problem?> getProblemById(String id) async {
final List<Map<String, dynamic>> maps = await _database.query(
_tableName,
where: 'id = ?',
whereArgs: [id],
limit: 1,
);
if (maps.isNotEmpty) {
return Problem.fromMap(maps.first);
try {
final List<Map<String, dynamic>> maps = await _database.query(
_tableName,
where: 'id = ?',
whereArgs: [id],
limit: 1,
);
if (maps.isNotEmpty) {
return Problem.fromMap(maps.first);
}
return null;
} catch (e) {
//
return null;
}
return null;
}
///
///
///
///
/// - `startDate`:
/// - `endDate`:
/// - `syncStatus`:
///
/// `Future<List<Problem>>`
Future<List<Problem>> getProblems({
DateTime? startDate,
DateTime? endDate,
SyncStatus? syncStatus,
}) async {
final List<String> whereClauses = [];
final List<dynamic> whereArgs = [];
if (startDate != null) {
whereClauses.add('creationTime >= ?');
whereArgs.add(startDate.millisecondsSinceEpoch);
try {
final List<String> whereClauses = [];
final List<dynamic> whereArgs = [];
if (startDate != null) {
whereClauses.add('creationTime >= ?');
whereArgs.add(startDate.millisecondsSinceEpoch);
}
if (endDate != null) {
whereClauses.add('creationTime <= ?');
whereArgs.add(endDate.millisecondsSinceEpoch);
}
if (syncStatus != null) {
whereClauses.add('syncStatus = ?');
whereArgs.add(syncStatus.index);
}
final String? whereString = whereClauses.isNotEmpty
? whereClauses.join(' AND ')
: null;
final List<Map<String, dynamic>> maps = await _database.query(
_tableName,
where: whereString,
whereArgs: whereArgs.isEmpty ? null : whereArgs,
orderBy: 'creationTime DESC',
);
return maps.map((json) => Problem.fromMap(json)).toList();
} catch (e) {
//
return [];
}
if (endDate != null) {
whereClauses.add('creationTime <= ?');
whereArgs.add(endDate.millisecondsSinceEpoch);
}
if (syncStatus != null) {
whereClauses.add('syncStatus = ?');
whereArgs.add(syncStatus.index);
}
final String? whereString = whereClauses.isNotEmpty
? whereClauses.join(' AND ')
: null;
final List<Map<String, dynamic>> maps = await _database.query(
_tableName,
where: whereString,
whereArgs: whereArgs.isEmpty ? null : whereArgs,
orderBy: 'creationTime DESC',
);
return maps.map((json) => Problem.fromMap(json)).toList();
}
/// ---
// ---
/// `GetxService`
///

3
lib/data/repositories/problem_repository.dart

@ -2,7 +2,8 @@ import 'dart:io';
import 'package:dio/dio.dart';
import 'package:get/get.dart' hide MultipartFile, FormData;
import 'package:problem_check_system/data/models/enum_model.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';

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

@ -80,17 +80,29 @@ class ProblemController extends GetxController
}
// #region
///
void onProblemCheckedChange() {}
///
// Controller
void updateProblemCheckedStatus(Rx<Problem> problem, bool isChecked) {
// 1. 使 copyWith Problem
final updatedProblem = problem.value.copyWith(isChecked: isChecked);
// 2. Rx<Problem> UI
problem.value = updatedProblem;
}
void selectAll() {
final bool newState = !allSelected.value;
for (var problem in unUploadedProblems) {
problem.isChecked = newState;
}
// 使 .map()
final updatedProblems = unUploadedProblems.map((problem) {
return problem.copyWith(isChecked: newState);
}).toList();
// 使 assignAll
unUploadedProblems.assignAll(updatedProblems);
//
allSelected.value = newState;
// _updateSelectedList();
}
//

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

@ -4,7 +4,8 @@ import 'package:image_picker/image_picker.dart';
import 'package:path/path.dart' as path;
import 'package:permission_handler/permission_handler.dart';
import 'package:path_provider/path_provider.dart';
import 'package:problem_check_system/data/models/enum_model.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 'dart:io';
import 'package:problem_check_system/data/models/problem_model.dart';

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

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:problem_check_system/data/models/enum_model.dart';
import 'package:problem_check_system/data/models/sync_status.dart';
import 'package:problem_check_system/data/models/problem_model.dart';
import 'package:problem_check_system/modules/problem/controllers/problem_controller.dart';
import 'package:problem_check_system/modules/problem/views/widgets/custom_button.dart';
@ -135,8 +135,8 @@ class ProblemCard extends GetView<ProblemController> {
value: problem.value.isChecked,
// Checkbox controller
onChanged: (bool? value) {
problem.value.isChecked = value ?? false;
controller.onProblemCheckedChange();
// Controller
controller.updateProblemCheckedStatus(problem, value ?? false);
},
),
),

Loading…
Cancel
Save