Browse Source

feat : 新增企业功能,重构database_service

dev
徐振升 2 weeks ago
parent
commit
0ce0b93203
  1. 22
      lib/app/core/bindings/initial_binding.dart
  2. 0
      lib/app/core/models/auth_model.dart
  3. 60
      lib/app/core/models/company_enum.dart
  4. 2
      lib/app/core/models/image_metadata_model.dart
  5. 0
      lib/app/core/models/image_status.dart
  6. 4
      lib/app/core/models/problem_model.dart
  7. 6
      lib/app/core/models/problem_sync_status.dart
  8. 0
      lib/app/core/models/server_problem.dart
  9. 0
      lib/app/core/models/server_problem.freezed.dart
  10. 0
      lib/app/core/models/server_problem.g.dart
  11. 24
      lib/app/core/models/sync_status.dart
  12. 0
      lib/app/core/models/user/organization.dart
  13. 0
      lib/app/core/models/user/page.dart
  14. 0
      lib/app/core/models/user/role.dart
  15. 0
      lib/app/core/models/user/user.dart
  16. 8
      lib/app/core/repositories/auth_repository.dart
  17. 2
      lib/app/core/repositories/file_repository.dart
  18. 0
      lib/app/core/repositories/image_repository.dart
  19. 4
      lib/app/core/repositories/image_repository_impl.dart
  20. 15
      lib/app/core/repositories/syncable_repository.dart
  21. 25
      lib/app/core/routes/app_pages.dart
  22. 1
      lib/app/core/routes/app_routes.dart
  23. 105
      lib/app/core/services/database_service.dart
  24. 4
      lib/app/core/services/http_provider.dart
  25. 0
      lib/app/core/services/network_status_service.dart
  26. 4
      lib/app/core/services/sqlite_service.dart
  27. 55
      lib/app/core/services/sync_metadata_service.dart
  28. 186
      lib/app/core/services/sync_service.dart
  29. 0
      lib/app/core/services/upgrader_service.dart
  30. 4
      lib/app/features/auth/bindings/login_binding.dart
  31. 10
      lib/app/features/auth/controllers/login_controller.dart
  32. 2
      lib/app/features/auth/views/login_page.dart
  33. 20
      lib/app/features/enterprise/data/datasources/enterprise_local_data_source.dart
  34. 5
      lib/app/features/enterprise/data/datasources/enterprise_remote_data_source.dart
  35. 107
      lib/app/features/enterprise/data/model/enterprise_model.dart
  36. 66
      lib/app/features/enterprise/data/repositories_impl/enterprise_repository_impl.dart
  37. 38
      lib/app/features/enterprise/domain/entities/enterprise.dart
  38. 12
      lib/app/features/enterprise/domain/repositories/enterprise_repository.dart
  39. 12
      lib/app/features/enterprise/domain/usecases/add_enterprise.dart
  40. 0
      lib/app/features/enterprise/domain/usecases/editor_enterprise.dart
  41. 28
      lib/app/features/enterprise/presentation/bindings/enterprise_form_binding.dart
  42. 33
      lib/app/features/enterprise/presentation/controllers/enterprise_form_controller.dart
  43. 95
      lib/app/features/enterprise/presentation/controllers/enterprise_list_controller.dart
  44. 110
      lib/app/features/enterprise/presentation/pages/enterprise_form_page.dart
  45. 2
      lib/app/features/enterprise/presentation/pages/enterprise_info_page.dart
  46. 15
      lib/app/features/enterprise/presentation/widgets/enterprise_card.dart
  47. 12
      lib/app/features/home/bindings/home_binding.dart
  48. 4
      lib/app/features/home/controllers/home_controller.dart
  49. 14
      lib/app/features/home/views/home_page.dart
  50. 4
      lib/app/features/my/bindings/change_password_binding.dart
  51. 2
      lib/app/features/my/controllers/change_password_controller.dart
  52. 4
      lib/app/features/my/controllers/my_controller.dart
  53. 2
      lib/app/features/my/views/change_password.dart
  54. 4
      lib/app/features/my/views/my_page.dart
  55. 32
      lib/app/features/navigation/presentation/bindings/navigation_binding.dart
  56. 23
      lib/app/features/navigation/presentation/controllers/navigation_controller.dart
  57. 29
      lib/app/features/navigation/presentation/pages/navigation_page.dart
  58. 110
      lib/app/features/problem/data/datasources/problem_local_datasource.dart
  59. 12
      lib/app/features/problem/data/repositories/problem_repository.dart
  60. 19
      lib/app/features/problem/domain/repositoies/problem_repository.dart
  61. 6
      lib/app/features/problem/presentation/bindings/problem_form_binding.dart
  62. 26
      lib/app/features/problem/presentation/controllers/problem_controller.dart
  63. 10
      lib/app/features/problem/presentation/controllers/problem_form_controller.dart
  64. 0
      lib/app/features/problem/presentation/controllers/sync_progress_state.dart
  65. 2
      lib/app/features/problem/presentation/views/problem_form_page.dart
  66. 8
      lib/app/features/problem/presentation/views/problem_list_page.dart
  67. 123
      lib/app/features/problem/presentation/views/problem_page.dart
  68. 6
      lib/app/features/problem/presentation/views/problem_upload_page.dart
  69. 2
      lib/app/features/problem/presentation/views/widgets/current_filter_bar.dart
  70. 0
      lib/app/features/problem/presentation/views/widgets/custom_button.dart
  71. 2
      lib/app/features/problem/presentation/views/widgets/custom_filter_dropdown.dart
  72. 2
      lib/app/features/problem/presentation/views/widgets/history_filter_bar.dart
  73. 2
      lib/app/features/problem/presentation/views/widgets/models/date_range_enum.dart
  74. 0
      lib/app/features/problem/presentation/views/widgets/models/dropdown_option.dart
  75. 10
      lib/app/features/problem/presentation/views/widgets/problem_card.dart
  76. 2
      lib/app/features/problem/presentation/views/widgets/sync_progress_dialog.dart
  77. 6
      lib/main.dart
  78. 50
      lib/modules/home/views/home_page.dart
  79. 165
      lib/modules/problem/views/problem_page.dart
  80. 16
      pubspec.lock
  81. 2
      pubspec.yaml

22
lib/app/bindings/initial_binding.dart → lib/app/core/bindings/initial_binding.dart

@ -1,14 +1,15 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart'; import 'package:get_storage/get_storage.dart';
import 'package:problem_check_system/app/core/data/services/network_status_service.dart'; import 'package:problem_check_system/app/core/services/database_service.dart';
import 'package:problem_check_system/app/core/data/services/http_provider.dart'; import 'package:problem_check_system/app/core/services/network_status_service.dart';
import 'package:problem_check_system/app/core/data/services/sqlite_service.dart'; import 'package:problem_check_system/app/core/services/http_provider.dart';
import 'package:problem_check_system/app/core/data/repositories/auth_repository.dart'; import 'package:problem_check_system/app/core/services/sqlite_service.dart';
import 'package:problem_check_system/app/core/data/repositories/file_repository.dart'; import 'package:problem_check_system/app/core/repositories/auth_repository.dart';
import 'package:problem_check_system/app/core/data/repositories/image_repository.dart'; import 'package:problem_check_system/app/core/repositories/file_repository.dart';
import 'package:problem_check_system/app/core/data/repositories/image_repository_impl.dart'; import 'package:problem_check_system/app/core/repositories/image_repository.dart';
import 'package:problem_check_system/app/core/data/repositories/problem_repository.dart'; import 'package:problem_check_system/app/core/repositories/image_repository_impl.dart';
import 'package:problem_check_system/app/core/data/services/upgrader_service.dart'; import 'package:problem_check_system/app/features/problem/data/repositories/problem_repository.dart';
import 'package:problem_check_system/app/core/services/upgrader_service.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
class InitialBinding implements Bindings { class InitialBinding implements Bindings {
@ -23,7 +24,8 @@ class InitialBinding implements Bindings {
Get.put<GetStorage>(GetStorage(), permanent: true); Get.put<GetStorage>(GetStorage(), permanent: true);
Get.put<Uuid>(Uuid(), permanent: true); Get.put<Uuid>(Uuid(), permanent: true);
Get.put<HttpProvider>(HttpProvider()); Get.put<HttpProvider>(HttpProvider());
Get.put<SQLiteService>(SQLiteService()); // Get.put<SQLiteService>(SQLiteService());
Get.put<DatabaseService>(DatabaseService());
Get.put<NetworkStatusService>(NetworkStatusService()); Get.put<NetworkStatusService>(NetworkStatusService());
Get.put(UpgraderService()); Get.put(UpgraderService());
} }

0
lib/app/core/data/models/auth_model.dart → lib/app/core/models/auth_model.dart

60
lib/app/core/models/company_enum.dart

@ -0,0 +1,60 @@
///
enum CompanyType {
// (使/) ->
production('生产'),
usage('使用'),
storage('储存'),
business('经营');
///
const CompanyType(this.serverValue);
///
final String serverValue;
/// UI中显示的文本 ( serverValue)
String get displayText => serverValue;
/// [] CompanyType
///
/// `null`
static CompanyType? fromServerValue(String? value) {
if (value == null) return null;
for (final type in values) {
// `values`
if (type.serverValue == value) {
return type;
}
}
return null; //
}
}
///
enum CompanyScope {
// ->
micro('微型'),
small('小型'),
medium('中型'),
large('大型');
///
const CompanyScope(this.serverValue);
///
final String serverValue;
/// UI中显示的文本
String get displayText => serverValue;
/// [] CompanyScope
static CompanyScope? fromServerValue(String? value) {
if (value == null) return null;
for (final scope in values) {
if (scope.serverValue == value) {
return scope;
}
}
return null; //
}
}

2
lib/app/core/data/models/image_metadata_model.dart → lib/app/core/models/image_metadata_model.dart

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

0
lib/app/core/data/models/image_status.dart → lib/app/core/models/image_status.dart

4
lib/app/core/data/models/problem_model.dart → lib/app/core/models/problem_model.dart

@ -1,7 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'package:problem_check_system/app/core/data/models/image_metadata_model.dart'; import 'package:problem_check_system/app/core/models/image_metadata_model.dart';
import 'package:problem_check_system/app/core/data/models/problem_sync_status.dart'; import 'package:problem_check_system/app/core/models/problem_sync_status.dart';
/// ///
/// ///

6
lib/app/core/data/models/problem_sync_status.dart → lib/app/core/models/problem_sync_status.dart

@ -1,7 +1,7 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:problem_check_system/app/core/data/models/image_metadata_model.dart'; import 'package:problem_check_system/app/core/models/image_metadata_model.dart';
import 'package:problem_check_system/app/core/data/models/problem_model.dart'; import 'package:problem_check_system/app/core/models/problem_model.dart';
import 'package:problem_check_system/app/core/data/repositories/auth_repository.dart'; import 'package:problem_check_system/app/core/repositories/auth_repository.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
enum ProblemSyncStatus { enum ProblemSyncStatus {

0
lib/app/core/data/models/server_problem.dart → lib/app/core/models/server_problem.dart

0
lib/app/core/data/models/server_problem.freezed.dart → lib/app/core/models/server_problem.freezed.dart

0
lib/app/core/data/models/server_problem.g.dart → lib/app/core/models/server_problem.g.dart

24
lib/app/core/models/sync_status.dart

@ -0,0 +1,24 @@
enum SyncStatus {
/// -
untracked,
/// - git的unmodified
synced,
/// - git的untracked staged
pendingCreate,
/// - git的modified staged
pendingUpdate,
/// - git的deleted staged
pendingDelete,
}
///
/// 线
abstract class SyncableEntity {
String get id;
SyncStatus get syncStatus;
DateTime get lastModifiedTime; //
}

0
lib/app/core/data/models/user/organization.dart → lib/app/core/models/user/organization.dart

0
lib/app/core/data/models/user/page.dart → lib/app/core/models/user/page.dart

0
lib/app/core/data/models/user/role.dart → lib/app/core/models/user/role.dart

0
lib/app/core/data/models/user/user.dart → lib/app/core/models/user/user.dart

8
lib/app/core/data/repositories/auth_repository.dart → lib/app/core/repositories/auth_repository.dart

@ -1,10 +1,10 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart'; import 'package:get_storage/get_storage.dart';
import 'package:problem_check_system/app/core/utils/constants/api_endpoints.dart'; import 'package:problem_check_system/app/core/utils/constants/api_endpoints.dart';
import 'package:problem_check_system/app/core/data/models/auth_model.dart'; import 'package:problem_check_system/app/core/models/auth_model.dart';
import 'package:problem_check_system/app/core/data/models/user/user.dart'; import 'package:problem_check_system/app/core/models/user/user.dart';
import 'package:problem_check_system/app/core/data/services/network_status_service.dart'; import 'package:problem_check_system/app/core/services/network_status_service.dart';
import 'package:problem_check_system/app/core/data/services/http_provider.dart'; import 'package:problem_check_system/app/core/services/http_provider.dart';
class AuthRepository extends GetxService { class AuthRepository extends GetxService {
final HttpProvider httpProvider; final HttpProvider httpProvider;

2
lib/app/core/data/repositories/file_repository.dart → lib/app/core/repositories/file_repository.dart

@ -4,7 +4,7 @@ import 'package:get/get.dart' hide FormData, MultipartFile;
import 'package:path/path.dart' as p; import 'package:path/path.dart' as p;
import 'package:problem_check_system/app/core/extensions/http_response_extension.dart'; import 'package:problem_check_system/app/core/extensions/http_response_extension.dart';
import 'package:problem_check_system/app/core/utils/constants/api_endpoints.dart'; import 'package:problem_check_system/app/core/utils/constants/api_endpoints.dart';
import 'package:problem_check_system/app/core/data/services/http_provider.dart'; import 'package:problem_check_system/app/core/services/http_provider.dart';
class FileRepository extends GetxService { class FileRepository extends GetxService {
final HttpProvider _httpProvider = Get.find<HttpProvider>(); final HttpProvider _httpProvider = Get.find<HttpProvider>();

0
lib/app/core/data/repositories/image_repository.dart → lib/app/core/repositories/image_repository.dart

4
lib/app/core/data/repositories/image_repository_impl.dart → lib/app/core/repositories/image_repository_impl.dart

@ -4,8 +4,8 @@ import 'package:dio/dio.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import 'package:problem_check_system/app/core/data/services/http_provider.dart'; import 'package:problem_check_system/app/core/services/http_provider.dart';
import 'package:problem_check_system/app/core/data/repositories/image_repository.dart'; import 'package:problem_check_system/app/core/repositories/image_repository.dart';
class ImageRepositoryImpl extends GetxService implements ImageRepository { class ImageRepositoryImpl extends GetxService implements ImageRepository {
final HttpProvider httpProvider; final HttpProvider httpProvider;

15
lib/app/core/repositories/syncable_repository.dart

@ -0,0 +1,15 @@
import 'package:problem_check_system/app/core/models/sync_status.dart';
abstract class SyncableRepository<T extends SyncableEntity> {
// --- () ---
Future<T> syncCreate(T item);
Future<T> syncUpdate(T item);
Future<void> syncDelete(String id);
// --- [] ---
///
/// [lastSyncTimestamp]:
///
///
Future<List<T>> pullFromServer(DateTime? lastSyncTimestamp);
}

25
lib/app/routes/app_pages.dart → lib/app/core/routes/app_pages.dart

@ -1,15 +1,17 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:problem_check_system/app/features/enterprise/presentation/bindings/enterprise_form_binding.dart'; import 'package:problem_check_system/app/features/enterprise/presentation/bindings/enterprise_form_binding.dart';
import 'package:problem_check_system/app/features/enterprise/presentation/pages/enterprise_form_page.dart'; import 'package:problem_check_system/app/features/enterprise/presentation/pages/enterprise_form_page.dart';
import 'package:problem_check_system/modules/home/bindings/home_binding.dart'; import 'package:problem_check_system/app/features/navigation/presentation/bindings/navigation_binding.dart';
import 'package:problem_check_system/modules/home/views/home_page.dart'; import 'package:problem_check_system/app/features/navigation/presentation/pages/navigation_page.dart';
import 'package:problem_check_system/modules/auth/bindings/login_binding.dart'; import 'package:problem_check_system/app/features/home/bindings/home_binding.dart';
import 'package:problem_check_system/modules/auth/views/login_page.dart'; import 'package:problem_check_system/app/features/home/views/home_page.dart';
import 'package:problem_check_system/modules/my/bindings/change_password_binding.dart'; import 'package:problem_check_system/app/features/auth/bindings/login_binding.dart';
import 'package:problem_check_system/modules/my/views/change_password.dart'; import 'package:problem_check_system/app/features/auth/views/login_page.dart';
import 'package:problem_check_system/modules/problem/bindings/problem_form_binding.dart'; import 'package:problem_check_system/app/features/my/bindings/change_password_binding.dart';
import 'package:problem_check_system/modules/problem/views/problem_form_page.dart'; import 'package:problem_check_system/app/features/my/views/change_password.dart';
import 'package:problem_check_system/modules/problem/views/problem_upload_page.dart'; import 'package:problem_check_system/app/features/problem/presentation/bindings/problem_form_binding.dart';
import 'package:problem_check_system/app/features/problem/presentation/views/problem_form_page.dart';
import 'package:problem_check_system/app/features/problem/presentation/views/problem_upload_page.dart';
import 'package:problem_check_system/app/features/enterprise/presentation/pages/enterprise_info_page.dart'; import 'package:problem_check_system/app/features/enterprise/presentation/pages/enterprise_info_page.dart';
import 'app_routes.dart'; import 'app_routes.dart';
@ -17,6 +19,11 @@ import 'app_routes.dart';
abstract class AppPages { abstract class AppPages {
// GetPage // GetPage
static final routes = <GetPage>[ static final routes = <GetPage>[
GetPage(
name: AppRoutes.navigation,
page: () => const NavigationPage(),
binding: NavigationBinding(),
),
GetPage( GetPage(
name: AppRoutes.home, name: AppRoutes.home,
page: () => const HomePage(), page: () => const HomePage(),

1
lib/app/routes/app_routes.dart → lib/app/core/routes/app_routes.dart

@ -1,5 +1,6 @@
abstract class AppRoutes { abstract class AppRoutes {
// 使 const // 使 const
static const navigation = '/navigation';
static const home = '/home'; static const home = '/home';
static const login = '/login'; static const login = '/login';

105
lib/app/core/services/database_service.dart

@ -0,0 +1,105 @@
import 'package:get/get.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
///
const String _createProblemsTable = '''
CREATE TABLE problems(
id TEXT PRIMARY KEY,
description TEXT NOT NULL,
location TEXT NOT NULL,
imageUrls TEXT NOT NULL,
creationTime INTEGER NOT NULL,
creatorId TEXT NOT NULL,
lastModifiedTime INTEGER NOT NULL,
syncStatus INTEGER NOT NULL,
censorTaskId TEXT,
bindData TEXT,
isChecked INTEGER NOT NULL
)
''';
///
const String _createEnterprisesTable = '''
CREATE TABLE enterprises(
id TEXT PRIMARY KEY,
syncStatus INTEGER NOT NULL,
lastModifiedTime INTEGER NOT NULL,
creationTime INTEGER NOT NULL,
name TEXT NOT NULL,
type TEXT NOT NULL,
address TEXT,
scale TEXT,
contactPerson TEXT,
contactPhone TEXT,
majorHazardsDescription TEXT
)
''';
///
///
class DatabaseService extends GetxService {
static const String _dbName = 'problems.db';
static const int _dbVersion = 1;
Database? _database;
/// getter 访
Future<Database> get database async {
if (_database != null) {
return _database!;
}
await _initDatabase();
return _database!;
}
///
Future<void> _initDatabase() async {
try {
final databasePath = await getDatabasesPath();
final path = join(databasePath, _dbName);
_database = await openDatabase(
path,
version: _dbVersion,
onCreate: _onCreate,
onUpgrade: _onUpgrade,
);
Get.log('数据库初始化成功');
} catch (e) {
Get.log('数据库初始化失败:$e', isError: true);
rethrow;
}
}
///
Future<void> _onCreate(Database db, int version) async {
//
await db.execute(_createProblemsTable);
Get.log('`problems` 表创建成功');
//
await db.execute(_createEnterprisesTable);
Get.log('`enterprises` 表创建成功');
}
///
Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async {
Get.log('正在将数据库从版本 $oldVersion 升级到 $newVersion...');
//
}
@override
void onInit() {
super.onInit();
_initDatabase();
}
@override
void onClose() {
_database?.close();
Get.log('数据库连接已关闭');
super.onClose();
}
}

4
lib/app/core/data/services/http_provider.dart → lib/app/core/services/http_provider.dart

@ -3,9 +3,9 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart' hide Response; import 'package:get/get.dart' hide Response;
import 'package:pretty_dio_logger/pretty_dio_logger.dart'; import 'package:pretty_dio_logger/pretty_dio_logger.dart';
import 'package:problem_check_system/app/routes/app_routes.dart'; import 'package:problem_check_system/app/core/routes/app_routes.dart';
import 'package:problem_check_system/app/core/utils/constants/api_endpoints.dart'; import 'package:problem_check_system/app/core/utils/constants/api_endpoints.dart';
import 'package:problem_check_system/app/core/data/repositories/auth_repository.dart'; import 'package:problem_check_system/app/core/repositories/auth_repository.dart';
// DioProvider GetxService // DioProvider GetxService
// Dio // Dio

0
lib/app/core/data/services/network_status_service.dart → lib/app/core/services/network_status_service.dart

4
lib/app/core/data/services/sqlite_service.dart → lib/app/core/services/sqlite_service.dart

@ -1,8 +1,8 @@
// sqlite_provider.dart // sqlite_provider.dart
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:problem_check_system/app/core/data/models/problem_sync_status.dart'; import 'package:problem_check_system/app/core/models/problem_sync_status.dart';
import 'package:problem_check_system/app/core/data/models/problem_model.dart'; import 'package:problem_check_system/app/core/models/problem_model.dart';
import 'package:sqflite/sqflite.dart'; import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart'; import 'package:path/path.dart';

55
lib/app/core/services/sync_metadata_service.dart

@ -0,0 +1,55 @@
import 'package:get_storage/get_storage.dart';
/// SyncMetadataService
///
/// ( Problem, Enterprise)
///
///
/// 使 `get_storage` API
class SyncMetadataService {
// 使 "box" ()
final GetStorage _box = GetStorage('sync_metadata');
///
/// `Problem` 'last_sync_timestamp_Problem'
String _getKeyForEntityType(Type type) {
return 'last_sync_timestamp_$type';
}
///
///
/// [entityType]: `Problem` `Enterprise`
///
/// `null`
DateTime? getLastSyncTimestamp(Type entityType) {
final key = _getKeyForEntityType(entityType);
// 使 get_storage `read`
final int? timestampMillis = _box.read<int?>(key);
if (timestampMillis != null) {
// UTC DateTime
return DateTime.fromMillisecondsSinceEpoch(timestampMillis, isUtc: true);
}
//
return null;
}
///
///
/// [entityType]: `Problem` `Enterprise`
/// [timestamp]:
///
/// UTC
Future<void> setLastSyncTimestamp(Type entityType, DateTime timestamp) async {
final key = _getKeyForEntityType(entityType);
// UTC
final int timestampMillis = timestamp.toUtc().millisecondsSinceEpoch;
// 使 get_storage `write`
// `await`
await _box.write(key, timestampMillis);
}
}

186
lib/app/core/services/sync_service.dart

@ -0,0 +1,186 @@
import 'package:get/get.dart';
import 'package:problem_check_system/app/core/models/sync_status.dart';
import 'package:problem_check_system/app/core/repositories/syncable_repository.dart';
import 'package:problem_check_system/app/core/services/sync_metadata_service.dart';
//
// LocalDataSource DAO
// import 'package/your_app/data/datasources/combined_local_data_source.dart';
/// SyncService
///
///
/// 1. (Repositories)
/// 2. PushPull
/// 3. `SyncMetadataService`
///
/// Problem或Enterprise
/// `SyncableRepository`
class SyncService extends GetxService {
//
final SyncMetadataService _metadataService = Get.find();
//
// final CombinedLocalDataSource _combinedLocalDataSource = Get.find(); //
// 使 Map
final Map<Type, SyncableRepository> _repositories = {};
/// `SyncableRepository`
///
/// Repository `SyncService`
/// 使 `SyncService`
///
/// `<T extends SyncableEntity>`
void registerRepository<T extends SyncableEntity>(
SyncableRepository<T> repository,
) {
print('[SyncService] Registering repository for type: $T');
_repositories[T] = repository;
}
///
///
/// UI或后台任务应该调用的主要方法
/// :
/// 1. ****:
/// 2. ****:
Future<void> performFullSync() async {
print('[SyncService] === Starting Full Sync Cycle ===');
// :
await _pushAllPendingChanges();
// :
await _pullAllServerUpdates();
print('[SyncService] === Full Sync Cycle Finished ===');
}
/// --- --- ///
/// ****
///
Future<void> _pushAllPendingChanges() async {
print('[SyncService] >> Phase 1: Pushing local changes...');
// : feature
// `_getAllPendingItems()`
final List<SyncableEntity> allPendingItems = await _getAllPendingItems();
if (allPendingItems.isEmpty) {
print('[SyncService] No local changes to push.');
return;
}
print(
'[SyncService] Found ${allPendingItems.length} pending items to push.',
);
for (final item in allPendingItems) {
try {
await _syncSingleItem(item);
} catch (e) {
print(
'[SyncService] ERROR: Failed to push item ${item.id} of type ${item.runtimeType}. Error: $e',
);
// :
}
}
print('[SyncService] << Push phase completed.');
}
/// ****
///
Future<void> _pullAllServerUpdates() async {
print('[SyncService] >> Phase 2: Pulling server updates...');
//
final DateTime syncCycleStartTime = DateTime.now().toUtc();
for (final entry in _repositories.entries) {
final entityType = entry.key;
final repository = entry.value;
try {
// 1.
final lastSyncTimestamp = _metadataService.getLastSyncTimestamp(
entityType,
);
print(
'[SyncService] Pulling updates for $entityType since: ${lastSyncTimestamp ?? 'the beginning'}',
);
// 2.
await repository.pullFromServer(lastSyncTimestamp);
// 3.
await _metadataService.setLastSyncTimestamp(
entityType,
syncCycleStartTime,
);
print(
'[SyncService] Successfully pulled and updated timestamp for $entityType.',
);
} catch (e) {
print(
'[SyncService] ERROR: Failed to pull updates for $entityType. Error: $e',
);
// :
}
}
print('[SyncService] << Pull phase completed.');
}
///
Future<void> _syncSingleItem(SyncableEntity item) async {
final repository = _repositories[item.runtimeType];
if (repository == null) {
print(
'[SyncService] WARNING: No repository registered for type ${item.runtimeType}. Skipping item ${item.id}.',
);
return;
}
print(
'[SyncService] Pushing item ${item.id} (${item.runtimeType}) with status ${item.syncStatus.name}...',
);
switch (item.syncStatus) {
case SyncStatus.pendingCreate:
await repository.syncCreate(item);
break;
case SyncStatus.pendingUpdate:
await repository.syncUpdate(item);
break;
case SyncStatus.pendingDelete:
await repository.syncDelete(item.id);
break;
case SyncStatus.synced:
//
break;
case SyncStatus.untracked:
// TODO: Handle this case.
throw UnimplementedError();
}
}
/// []
///
///
/// `syncStatus` `synced`
///
Future<List<SyncableEntity>> _getAllPendingItems() async {
//
// final problems = await problemLocalDataSource.getPendingProblems();
// final enterprises = await enterpriseLocalDataSource.getPendingEnterprises();
// return [...problems, ...enterprises];
print(
'[SyncService] WARNING: _getAllPendingItems() is not implemented. Returning empty list.',
);
return []; //
}
}

0
lib/app/core/data/services/upgrader_service.dart → lib/app/core/services/upgrader_service.dart

4
lib/modules/auth/bindings/login_binding.dart → lib/app/features/auth/bindings/login_binding.dart

@ -1,6 +1,6 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:problem_check_system/app/core/data/repositories/auth_repository.dart'; import 'package:problem_check_system/app/core/repositories/auth_repository.dart';
import 'package:problem_check_system/modules/auth/controllers/login_controller.dart'; import 'package:problem_check_system/app/features/auth/controllers/login_controller.dart';
class LoginBinding implements Bindings { class LoginBinding implements Bindings {
@override @override

10
lib/modules/auth/controllers/login_controller.dart → lib/app/features/auth/controllers/login_controller.dart

@ -1,9 +1,9 @@
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:problem_check_system/app/core/data/models/auth_model.dart'; import 'package:problem_check_system/app/core/models/auth_model.dart';
import 'package:problem_check_system/app/routes/app_routes.dart'; import 'package:problem_check_system/app/core/routes/app_routes.dart';
import 'package:problem_check_system/app/core/data/repositories/auth_repository.dart'; import 'package:problem_check_system/app/core/repositories/auth_repository.dart';
class LoginController extends GetxController { class LoginController extends GetxController {
final AuthRepository _authRepository; final AuthRepository _authRepository;
@ -72,7 +72,7 @@ class LoginController extends GetxController {
} else { } else {
_authRepository.removeLoginKey(); _authRepository.removeLoginKey();
} }
Get.offAllNamed(AppRoutes.home); Get.offAllNamed(AppRoutes.navigation);
// 访 // 访
var user = await _authRepository.getUserProfile(); var user = await _authRepository.getUserProfile();
if (user.id != null) { if (user.id != null) {
@ -100,7 +100,7 @@ class LoginController extends GetxController {
if (loginData.username == loginRequest.username && if (loginData.username == loginRequest.username &&
loginData.password == loginRequest.password) { loginData.password == loginRequest.password) {
Get.offAllNamed(AppRoutes.home); Get.offAllNamed(AppRoutes.navigation);
Get.snackbar('离线登录成功', '您已离线登录到系统'); Get.snackbar('离线登录成功', '您已离线登录到系统');
} else { } else {
Get.snackbar('登录失败', '无网络连接,且无法验证本地凭证'); Get.snackbar('登录失败', '无网络连接,且无法验证本地凭证');

2
lib/modules/auth/views/login_page.dart → lib/app/features/auth/views/login_page.dart

@ -2,7 +2,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:problem_check_system/modules/auth/controllers/login_controller.dart'; import 'package:problem_check_system/app/features/auth/controllers/login_controller.dart';
class LoginPage extends GetView<LoginController> { class LoginPage extends GetView<LoginController> {
const LoginPage({super.key}); const LoginPage({super.key});

20
lib/app/features/enterprise/data/datasources/enterprise_local_data_source.dart

@ -0,0 +1,20 @@
import 'package:problem_check_system/app/features/enterprise/data/model/enterprise_model.dart';
import 'package:sqflite/sqflite.dart';
abstract class EnterpriseLocalDataSource {
Future<void> addEnterprise(EnterpriseModel enterprise);
}
class EnterpriseLocalDataSourceImpl implements EnterpriseLocalDataSource {
@override
Future<void> addEnterprise(EnterpriseModel enterprise) async {
// // Enterprise
// // 使 SQLiteSharedPreferences
// final db = await databaseService.database;
// await db.insert(
// 'enterprises', //
// enterprise.toMap(),
// conflictAlgorithm: ConflictAlgorithm.replace,
// );
}
}

5
lib/app/features/enterprise/data/datasources/enterprise_remote_data_source.dart

@ -0,0 +1,5 @@
class EnterpriseRemoteDataSource {}
class EnterpriseRemoteDataSourceImpl implements EnterpriseRemoteDataSource {
//
}

107
lib/app/features/enterprise/data/model/enterprise_model.dart

@ -1,21 +1,92 @@
// lib/app/modules/enterprise_list/models/enterprise_model.dart import 'package:problem_check_system/app/core/models/sync_status.dart';
import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise.dart';
class Enterprise { /// `EnterpriseModel` `Enterprise`
final int? id; // ID ///
final String companyName; /// `Enterprise`SQLite
final String companyType; /// Data 使 Domain Presentation
final int totalIssues; class EnterpriseModel extends Enterprise {
final String creationTime; const EnterpriseModel({
final int uploaded; required super.id,
final int notUploaded; required super.syncStatus,
required super.lastModifiedTime,
Enterprise({ required super.creationTime,
this.id, required super.name,
required this.companyName, required super.type,
required this.companyType, super.address,
required this.totalIssues, super.scale,
required this.creationTime, super.contactPerson,
required this.uploaded, super.contactPhone,
required this.notUploaded, super.majorHazardsDescription,
}); });
/// [] Domain `Enterprise` `EnterpriseModel`
///
/// `Repository` UseCase
/// `Enterprise` `Model`
factory EnterpriseModel.fromEntity(Enterprise entity) {
return EnterpriseModel(
id: entity.id,
syncStatus: entity.syncStatus,
lastModifiedTime: entity.lastModifiedTime,
creationTime: entity.creationTime,
name: entity.name,
type: entity.type,
address: entity.address,
scale: entity.scale,
contactPerson: entity.contactPerson,
contactPhone: entity.contactPhone,
majorHazardsDescription: entity.majorHazardsDescription,
);
}
/// [] `Map` `EnterpriseModel`
///
/// `DataSource` SQLite
/// `Map` `Model`
factory EnterpriseModel.fromMap(Map<String, dynamic> map) {
return EnterpriseModel(
id: map['id'] as String,
//
syncStatus: SyncStatus.values[map['syncStatus'] as int],
// DateTime
lastModifiedTime: DateTime.fromMillisecondsSinceEpoch(
map['lastModifiedTime'] as int,
isUtc: true,
),
creationTime: DateTime.fromMillisecondsSinceEpoch(
map['creationTime'] as int,
isUtc: true,
),
name: map['name'] as String,
type: map['type'] as String,
address: map['address'] as String?,
scale: map['scale'] as String?,
contactPerson: map['contactPerson'] as String?,
contactPhone: map['contactPhone'] as String?,
majorHazardsDescription: map['majorHazardsDescription'] as String?,
);
}
/// `EnterpriseModel` `sqflite` `Map`
///
/// `DataSource` `Model`
///
Map<String, dynamic> toMap() {
return {
'id': id,
//
'syncStatus': syncStatus.index,
// DateTime UTC
'lastModifiedTime': lastModifiedTime.toUtc().millisecondsSinceEpoch,
'creationTime': creationTime.toUtc().millisecondsSinceEpoch,
'name': name,
'type': type,
'address': address,
'scale': scale,
'contactPerson': contactPerson,
'contactPhone': contactPhone,
'majorHazardsDescription': majorHazardsDescription,
};
}
} }

66
lib/app/features/enterprise/data/repositories_impl/enterprise_repository_impl.dart

@ -0,0 +1,66 @@
// ...
import 'package:problem_check_system/app/core/models/sync_status.dart';
import 'package:problem_check_system/app/features/enterprise/data/datasources/enterprise_local_data_source.dart';
import 'package:problem_check_system/app/features/enterprise/data/datasources/enterprise_remote_data_source.dart';
import 'package:problem_check_system/app/features/enterprise/data/model/enterprise_model.dart';
import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise.dart';
import 'package:problem_check_system/app/features/enterprise/domain/repositories/enterprise_repository.dart';
import 'package:uuid/uuid.dart';
class EnterpriseRepositoryImpl implements EnterpriseRepository {
final EnterpriseLocalDataSource localDataSource;
final EnterpriseRemoteDataSource remoteDataSource; //
final Uuid uuid = Uuid();
EnterpriseRepositoryImpl({
required this.localDataSource,
required this.remoteDataSource,
});
@override
Future<void> addEnterprise(Enterprise enterprise) async {
final now = DateTime.now();
// 1.
final fullEnterprise = Enterprise(
id: uuid.v4(), // ID
name: enterprise.name,
type: enterprise.type,
// ... ...
syncStatus: SyncStatus.pendingCreate, //
lastModifiedTime: now,
creationTime: now,
);
// 2.
final enterpriseModel = EnterpriseModel.fromEntity(fullEnterprise);
// 3.
await localDataSource.addEnterprise(enterpriseModel);
}
@override
Future<List<Enterprise>> pullFromServer(DateTime? lastSyncTimestamp) {
// TODO: implement pullFromServer
throw UnimplementedError();
}
@override
Future<Enterprise> syncCreate(Enterprise item) {
// TODO: implement syncCreate
throw UnimplementedError();
}
@override
Future<void> syncDelete(String id) {
// TODO: implement syncDelete
throw UnimplementedError();
}
@override
Future<Enterprise> syncUpdate(Enterprise item) {
// TODO: implement syncUpdate
throw UnimplementedError();
}
}

38
lib/app/features/enterprise/domain/entities/enterprise.dart

@ -0,0 +1,38 @@
import 'package:equatable/equatable.dart';
import 'package:problem_check_system/app/core/models/sync_status.dart';
/// `Enterprise`
///
class Enterprise extends Equatable implements SyncableEntity {
@override
final String id;
@override
final SyncStatus syncStatus;
@override
final DateTime lastModifiedTime;
final String name;
final String type;
final String? address;
final String? scale;
final String? contactPerson;
final String? contactPhone;
final String? majorHazardsDescription;
final DateTime creationTime;
const Enterprise({
required this.id,
required this.name,
required this.type,
this.address,
this.scale,
this.contactPerson,
this.contactPhone,
this.majorHazardsDescription,
required this.lastModifiedTime,
required this.creationTime,
required this.syncStatus,
});
@override
List<Object?> get props => [id, syncStatus, name, type];
}

12
lib/app/features/enterprise/domain/repositories/enterprise_repository.dart

@ -0,0 +1,12 @@
import 'package:problem_check_system/app/core/repositories/syncable_repository.dart';
import '../entities/enterprise.dart';
abstract class EnterpriseRepository implements SyncableRepository<Enterprise> {
///
/// ID生成
Future<void> addEnterprise(Enterprise enterprise);
// --- ---
// Future<List<EnterpriseListItem>> getEnterpriseListItems();
}

12
lib/app/features/enterprise/domain/usecases/add_enterprise.dart

@ -0,0 +1,12 @@
import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise.dart';
import 'package:problem_check_system/app/features/enterprise/domain/repositories/enterprise_repository.dart';
class AddEnterprise {
final EnterpriseRepository repository;
AddEnterprise({required this.repository});
Future<void> call(Enterprise enterprise) async {
await repository.addEnterprise(enterprise);
}
}

0
lib/app/features/enterprise/domain/usecases/editor_enterprise.dart

28
lib/app/features/enterprise/presentation/bindings/enterprise_form_binding.dart

@ -1,7 +1,12 @@
// lib/app/features/enterprise/presentation/bindings/enterprise_form_binding.dart // lib/app/features/enterprise/presentation/bindings/enterprise_form_binding.dart
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:problem_check_system/app/features/enterprise/data/model/enterprise_model.dart'; import 'package:problem_check_system/app/features/enterprise/data/datasources/enterprise_local_data_source.dart';
import 'package:problem_check_system/app/features/enterprise/data/datasources/enterprise_remote_data_source.dart';
import 'package:problem_check_system/app/features/enterprise/data/repositories_impl/enterprise_repository_impl.dart';
import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise.dart';
import 'package:problem_check_system/app/features/enterprise/domain/repositories/enterprise_repository.dart';
import 'package:problem_check_system/app/features/enterprise/domain/usecases/add_enterprise.dart';
import 'package:problem_check_system/app/features/enterprise/presentation/controllers/enterprise_form_controller.dart'; // import 'package:problem_check_system/app/features/enterprise/presentation/controllers/enterprise_form_controller.dart'; //
class EnterpriseFormBinding extends Bindings { class EnterpriseFormBinding extends Bindings {
@ -13,9 +18,28 @@ class EnterpriseFormBinding extends Bindings {
// - 使 as Enterprise? // - 使 as Enterprise?
final Enterprise? enterpriseToEdit = Get.arguments as Enterprise?; final Enterprise? enterpriseToEdit = Get.arguments as Enterprise?;
Get.lazyPut<EnterpriseLocalDataSource>(
() => EnterpriseLocalDataSourceImpl(),
);
Get.lazyPut<EnterpriseRemoteDataSource>(
() => EnterpriseRemoteDataSourceImpl(),
);
Get.lazyPut<EnterpriseRepository>(
(() => EnterpriseRepositoryImpl(
localDataSource: Get.find<EnterpriseLocalDataSource>(),
remoteDataSource: Get.find<EnterpriseRemoteDataSource>(),
)),
);
Get.lazyPut<AddEnterprise>(
() => AddEnterprise(repository: Get.find<EnterpriseRepository>()),
);
// 2. null Controller // 2. null Controller
Get.lazyPut<EnterpriseFormController>( Get.lazyPut<EnterpriseFormController>(
() => EnterpriseFormController(initialData: enterpriseToEdit), () => EnterpriseFormController(
initialData: enterpriseToEdit,
usecase: Get.find(),
),
); );
} }
} }

33
lib/app/features/enterprise/presentation/controllers/enterprise_form_controller.dart

@ -1,15 +1,23 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:problem_check_system/app/features/enterprise/data/model/enterprise_model.dart'; import 'package:problem_check_system/app/core/models/company_enum.dart';
import 'package:problem_check_system/app/core/models/sync_status.dart';
import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise.dart';
import 'package:problem_check_system/app/features/enterprise/domain/usecases/add_enterprise.dart';
import 'package:uuid/uuid.dart';
class EnterpriseFormController extends GetxController { class EnterpriseFormController extends GetxController {
// //
final Enterprise? initialData; final Enterprise? initialData;
EnterpriseFormController({this.initialData}); final AddEnterprise usecase;
EnterpriseFormController({this.initialData, required this.usecase});
// --- --- // --- ---
var isEditMode = false.obs; var isEditMode = false.obs;
var pageTitle = '新增企业'.obs; var pageTitle = '新增企业'.obs;
final Rx<CompanyType?> selectedType = Rx<CompanyType?>(null);
final Rx<CompanyScope?> selectedScope = Rx<CompanyScope?>(null);
// --- --- // --- ---
final enterpriseNameController = TextEditingController(); final enterpriseNameController = TextEditingController();
@ -30,7 +38,7 @@ class EnterpriseFormController extends GetxController {
isEditMode.value = true; isEditMode.value = true;
pageTitle.value = '修改信息'; pageTitle.value = '修改信息';
// //
enterpriseNameController.text = initialData!.companyName; enterpriseNameController.text = initialData!.name;
// ... // ...
} else { } else {
isEditMode.value = false; isEditMode.value = false;
@ -48,10 +56,21 @@ class EnterpriseFormController extends GetxController {
} }
} }
void _createEnterprise() { void _createEnterprise() async {
print('执行新增逻辑...'); final enterprise = Enterprise(
// API id: Uuid().v4(),
// Get.find<ApiService>().createEnterprise(...); name: enterpriseNameController.text,
type: selectedType.value!.displayText,
address: enterpriseAddressController.text,
scale: selectedScope.value!.displayText,
contactPerson: contactPersonController.text,
contactPhone: contactPhoneController.text,
majorHazardsDescription: hazardSourceController.text,
lastModifiedTime: DateTime.now(),
creationTime: DateTime.now(),
syncStatus: SyncStatus.pendingCreate,
);
await usecase.call(enterprise);
Get.back(result: true); // Get.back(result: true); //
} }

95
lib/app/features/enterprise/presentation/controllers/enterprise_list_controller.dart

@ -1,8 +1,10 @@
// lib/app/modules/enterprise_list/enterprise_list_controller.dart // lib/app/modules/enterprise_list/enterprise_list_controller.dart
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:problem_check_system/app/core/models/sync_status.dart';
import 'package:problem_check_system/app/features/enterprise/data/model/enterprise_model.dart'; import 'package:problem_check_system/app/features/enterprise/data/model/enterprise_model.dart';
import 'package:problem_check_system/app/routes/app_routes.dart'; import 'package:problem_check_system/app/core/routes/app_routes.dart';
import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise.dart';
class EnterpriseListController extends GetxController { class EnterpriseListController extends GetxController {
// 使 .obs 使UI会自动更新 // 使 .obs 使UI会自动更新
@ -22,82 +24,21 @@ class EnterpriseListController extends GetxController {
void fetchEnterprises() { void fetchEnterprises() {
// //
Future.delayed(const Duration(milliseconds: 500), () { Future.delayed(const Duration(milliseconds: 500), () {
// // //
var mockData = [ // var mockData = [
Enterprise( // Enterprise(
companyName: '山东汇丰石化集团有限公司', // id: "1",
companyType: '危险化学品生产', // name: '企业A',
totalIssues: 29, // type: '类型1',
creationTime: '2025-07-31 15:30:29', // address: '地址A',
uploaded: 15, // contactPerson: '联系人A',
notUploaded: 14, // contactPhone: '123456',
), // lastModifiedTime: DateTime.now(),
Enterprise( // creationTime: DateTime.now(),
companyName: '山东荣源石化集团有限公司', // syncStatus: SyncStatus.synced,
companyType: '危险化学品生产', // ),
totalIssues: 29, // ];
creationTime: '2025-07-31 15:30:29', // enterpriseList.assignAll(mockData); //
uploaded: 15,
notUploaded: 14,
),
Enterprise(
companyName: '山东江潜石化集团有限公司',
companyType: '危险化学品生产',
totalIssues: 29,
creationTime: '2025-07-31 15:30:29',
uploaded: 15,
notUploaded: 14,
),
Enterprise(
companyName: '山东汇绿武化集团有限公司',
companyType: '危险化学品生产',
totalIssues: 29,
creationTime: '2025-07-31 15:30:29',
uploaded: 15,
notUploaded: 14,
),
Enterprise(
companyName: '山东汇绿武化集团有限公司',
companyType: '危险化学品生产',
totalIssues: 29,
creationTime: '2025-07-31 15:30:29',
uploaded: 15,
notUploaded: 14,
),
Enterprise(
companyName: '山东汇绿武化集团有限公司',
companyType: '危险化学品生产',
totalIssues: 29,
creationTime: '2025-07-31 15:30:29',
uploaded: 15,
notUploaded: 14,
),
Enterprise(
companyName: '山东汇绿武化集团有限公司',
companyType: '危险化学品生产',
totalIssues: 29,
creationTime: '2025-07-31 15:30:29',
uploaded: 15,
notUploaded: 14,
),
Enterprise(
companyName: '山东汇绿武化集团有限公司',
companyType: '危险化学品生产',
totalIssues: 29,
creationTime: '2025-07-31 15:30:29',
uploaded: 15,
notUploaded: 14,
),
Enterprise(
companyName: '山东汇绿武化集团有限公司',
companyType: '危险化学品生产',
totalIssues: 29,
creationTime: '2025-07-31 15:30:29',
uploaded: 15,
notUploaded: 14,
),
];
enterpriseList.assignAll(mockData); //
}); });
} }

110
lib/app/features/enterprise/presentation/pages/enterprise_form_page.dart

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:problem_check_system/app/core/models/company_enum.dart';
import 'package:problem_check_system/app/features/enterprise/presentation/controllers/enterprise_form_controller.dart'; import 'package:problem_check_system/app/features/enterprise/presentation/controllers/enterprise_form_controller.dart';
// ------------------- (View) ------------------- // ------------------- (View) -------------------
@ -28,14 +29,10 @@ class EnterpriseFormPage extends GetView<EnterpriseFormController> {
_buildTextField( _buildTextField(
label: '企业名称', label: '企业名称',
hint: '达拉特旗', hint: '达拉特旗',
controller: controller.enterpriseNameController, textController: controller.enterpriseNameController,
isRequired: true,
),
_buildDropdownField(
label: '企业类型',
hint: '请选择企业类型',
isRequired: true, isRequired: true,
), ),
_buildCompanyTypeDropdown(),
], ],
), ),
SizedBox(height: 12.h), SizedBox(height: 12.h),
@ -46,23 +43,24 @@ class EnterpriseFormPage extends GetView<EnterpriseFormController> {
_buildTextField( _buildTextField(
label: '企业地址', label: '企业地址',
hint: '请输入企业地址', hint: '请输入企业地址',
controller: controller.enterpriseAddressController, textController:
controller.enterpriseAddressController,
), ),
_buildDropdownField(label: '企业规模', hint: '请选择企业规模'), _buildCompanyScopeDropdown(),
_buildTextField( _buildTextField(
label: '联系人', label: '联系人',
hint: '请输入联系人姓名', hint: '请输入联系人姓名',
controller: controller.contactPersonController, textController: controller.contactPersonController,
), ),
_buildTextField( _buildTextField(
label: '联系电话', label: '联系电话',
hint: '请输入联系电话', hint: '请输入联系电话',
controller: controller.contactPhoneController, textController: controller.contactPhoneController,
), ),
_buildTextField( _buildTextField(
label: '有无重大危险源;重大危险源情况', label: '有无重大危险源;重大危险源情况',
hint: '请输入有无重大危险源;重大危险源情况', hint: '请输入有无重大危险源;重大危险源情况',
controller: controller.hazardSourceController, textController: controller.hazardSourceController,
hasDivider: false, hasDivider: false,
), ),
], ],
@ -148,7 +146,7 @@ class EnterpriseFormPage extends GetView<EnterpriseFormController> {
Widget _buildTextField({ Widget _buildTextField({
required String label, required String label,
required String hint, required String hint,
required TextEditingController controller, required TextEditingController textController,
bool isRequired = false, bool isRequired = false,
bool hasDivider = true, bool hasDivider = true,
}) { }) {
@ -160,7 +158,7 @@ class EnterpriseFormPage extends GetView<EnterpriseFormController> {
child: _buildLabel(label, isRequired), child: _buildLabel(label, isRequired),
), ),
TextFormField( TextFormField(
controller: controller, controller: textController,
decoration: InputDecoration( decoration: InputDecoration(
hintText: hint, hintText: hint,
hintStyle: TextStyle(color: Colors.grey[400], fontSize: 14.sp), hintStyle: TextStyle(color: Colors.grey[400], fontSize: 14.sp),
@ -176,35 +174,85 @@ class EnterpriseFormPage extends GetView<EnterpriseFormController> {
} }
// //
Widget _buildDropdownField({ // []
required String label, Widget _buildCompanyTypeDropdown() {
required String hint,
bool isRequired = false,
}) {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Padding( Padding(
padding: EdgeInsets.symmetric(vertical: 10.h), padding: EdgeInsets.symmetric(vertical: 10.h),
child: _buildLabel(label, isRequired), child: _buildLabel('企业类型', true), // isRequired = true
),
// 使 Obx controller selectedType
Obx(
() => DropdownButtonFormField<CompanyType>(
initialValue: controller.selectedType.value,
hint: Text(
'请选择企业类型',
style: TextStyle(color: Colors.grey[400], fontSize: 14.sp),
), ),
GestureDetector( isExpanded: true, //
onTap: () { decoration: const InputDecoration(
// border: InputBorder.none,
Get.snackbar('提示', '这是一个下拉选择框'); isDense: true,
contentPadding: EdgeInsets.zero,
),
items: CompanyType.values.map((CompanyType type) {
return DropdownMenuItem<CompanyType>(
value: type,
child: Text(
type.displayText,
style: TextStyle(fontSize: 14.sp),
),
);
}).toList(),
onChanged: (CompanyType? newValue) {
controller.selectedType.value = newValue;
}, },
child: Container( //
color: Colors.transparent, // 使 icon: const Icon(Icons.arrow_drop_down, color: Colors.grey),
child: Row( ),
mainAxisAlignment: MainAxisAlignment.spaceBetween, ),
const Divider(height: 1, color: Color(0xFFF0F0F0)),
],
);
}
// []
Widget _buildCompanyScopeDropdown() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Padding(
hint, padding: EdgeInsets.symmetric(vertical: 10.h),
child: _buildLabel('企业规模', false), // isRequired = false
),
Obx(
() => DropdownButtonFormField<CompanyScope>(
initialValue: controller.selectedScope.value,
hint: Text(
'请选择企业规模',
style: TextStyle(color: Colors.grey[400], fontSize: 14.sp), style: TextStyle(color: Colors.grey[400], fontSize: 14.sp),
), ),
const Icon(Icons.arrow_drop_down, color: Colors.grey), isExpanded: true,
], decoration: const InputDecoration(
border: InputBorder.none,
isDense: true,
contentPadding: EdgeInsets.zero,
), ),
items: CompanyScope.values.map((CompanyScope scope) {
return DropdownMenuItem<CompanyScope>(
value: scope,
child: Text(
scope.displayText,
style: TextStyle(fontSize: 14.sp),
),
);
}).toList(),
onChanged: (CompanyScope? newValue) {
controller.selectedScope.value = newValue;
},
icon: const Icon(Icons.arrow_drop_down, color: Colors.grey),
), ),
), ),
const Divider(height: 1, color: Color(0xFFF0F0F0)), const Divider(height: 1, color: Color(0xFFF0F0F0)),

2
lib/app/features/enterprise/presentation/pages/enterprise_info_page.dart

@ -20,7 +20,7 @@ class EnterpriseInfoPage extends StatelessWidget {
// TabBarView Tab // TabBarView Tab
body: const TabBarView( body: const TabBarView(
children: [ children: [
Center(child: Text('问题列表')), Center(child: Text('企业问题列表')),
Center(child: Text('企业基本信息')), Center(child: Text('企业基本信息')),
], ],
), ),

15
lib/app/features/enterprise/presentation/widgets/enterprise_card.dart

@ -1,8 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart'; import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise.dart';
import 'package:problem_check_system/app/features/enterprise/data/model/enterprise_model.dart';
import 'package:problem_check_system/app/routes/app_routes.dart';
// //
class EnterpriseCard extends StatelessWidget { class EnterpriseCard extends StatelessWidget {
@ -135,7 +133,7 @@ class EnterpriseCard extends StatelessWidget {
), ),
SizedBox(height: 4.h), // .h SizedBox(height: 4.h), // .h
Text( Text(
enterprise.companyName, enterprise.name,
style: TextStyle( style: TextStyle(
fontSize: 12.5.sp, fontSize: 12.5.sp,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
@ -145,6 +143,7 @@ class EnterpriseCard extends StatelessWidget {
), ),
], ],
), ),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -154,7 +153,7 @@ class EnterpriseCard extends StatelessWidget {
), ),
SizedBox(height: 4.h), SizedBox(height: 4.h),
Text( Text(
enterprise.companyType, enterprise.type,
style: TextStyle( style: TextStyle(
fontSize: 12.5.sp, fontSize: 12.5.sp,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
@ -195,7 +194,7 @@ class EnterpriseCard extends StatelessWidget {
style: TextStyle(fontSize: 12.sp, color: Colors.grey), style: TextStyle(fontSize: 12.sp, color: Colors.grey),
), ),
Text( Text(
enterprise.totalIssues.toString(), "111", // enterprise.totalIssues.toString(),
style: TextStyle( style: TextStyle(
fontSize: 12.5.sp, fontSize: 12.5.sp,
color: Colors.black87, color: Colors.black87,
@ -223,13 +222,13 @@ class EnterpriseCard extends StatelessWidget {
return Row( return Row(
children: [ children: [
_buildTag( _buildTag(
text: '已上传 ${enterprise.uploaded}', text: '已上传 ${enterprise.id}',
textColor: Colors.blue.shade700, textColor: Colors.blue.shade700,
backgroundColor: Colors.blue.shade50, backgroundColor: Colors.blue.shade50,
), ),
SizedBox(width: 8.w), SizedBox(width: 8.w),
_buildTag( _buildTag(
text: '未上传 ${enterprise.notUploaded}', text: '未上传 ${enterprise.id}',
textColor: Colors.red.shade600, textColor: Colors.red.shade600,
backgroundColor: Colors.red.shade50, backgroundColor: Colors.red.shade50,
), ),

12
lib/modules/home/bindings/home_binding.dart → lib/app/features/home/bindings/home_binding.dart

@ -1,11 +1,11 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:problem_check_system/app/core/data/models/problem_sync_status.dart'; import 'package:problem_check_system/app/core/models/problem_sync_status.dart';
import 'package:problem_check_system/app/core/data/repositories/auth_repository.dart'; import 'package:problem_check_system/app/core/repositories/auth_repository.dart';
import 'package:problem_check_system/app/core/data/repositories/problem_repository.dart'; import 'package:problem_check_system/app/features/problem/data/repositories/problem_repository.dart';
import 'package:problem_check_system/app/features/enterprise/presentation/controllers/enterprise_list_controller.dart'; import 'package:problem_check_system/app/features/enterprise/presentation/controllers/enterprise_list_controller.dart';
import 'package:problem_check_system/modules/home/controllers/home_controller.dart'; import 'package:problem_check_system/app/features/home/controllers/home_controller.dart';
import 'package:problem_check_system/modules/my/controllers/my_controller.dart'; import 'package:problem_check_system/app/features/my/controllers/my_controller.dart';
import 'package:problem_check_system/modules/problem/controllers/problem_controller.dart'; import 'package:problem_check_system/app/features/problem/presentation/controllers/problem_controller.dart';
class HomeBinding implements Bindings { class HomeBinding implements Bindings {
@override @override

4
lib/modules/home/controllers/home_controller.dart → lib/app/features/home/controllers/home_controller.dart

@ -1,8 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:problem_check_system/app/features/enterprise/presentation/pages/enterprise_list_page.dart'; import 'package:problem_check_system/app/features/enterprise/presentation/pages/enterprise_list_page.dart';
import 'package:problem_check_system/modules/my/views/my_page.dart'; import 'package:problem_check_system/app/features/my/views/my_page.dart';
import 'package:problem_check_system/modules/problem/views/problem_page.dart'; import 'package:problem_check_system/app/features/problem/presentation/views/problem_page.dart';
class HomeController extends GetxController { class HomeController extends GetxController {
var selectedIndex = 0.obs; var selectedIndex = 0.obs;

14
lib/app/features/home/views/home_page.dart

@ -0,0 +1,14 @@
// lib/modules/home/views/home_page.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:problem_check_system/app/features/home/controllers/home_controller.dart';
class HomePage extends GetView<HomeController> {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(body: Center(child: Text("首页")));
}
}

4
lib/modules/my/bindings/change_password_binding.dart → lib/app/features/my/bindings/change_password_binding.dart

@ -1,6 +1,6 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:problem_check_system/app/core/data/repositories/auth_repository.dart'; import 'package:problem_check_system/app/core/repositories/auth_repository.dart';
import 'package:problem_check_system/modules/my/controllers/change_password_controller.dart'; import 'package:problem_check_system/app/features/my/controllers/change_password_controller.dart';
class ChangePasswordBinding implements Bindings { class ChangePasswordBinding implements Bindings {
@override @override

2
lib/modules/my/controllers/change_password_controller.dart → lib/app/features/my/controllers/change_password_controller.dart

@ -1,5 +1,5 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:problem_check_system/app/core/data/repositories/auth_repository.dart'; import 'package:problem_check_system/app/core/repositories/auth_repository.dart';
class ChangePasswordController extends GetxController { class ChangePasswordController extends GetxController {
// //

4
lib/modules/my/controllers/my_controller.dart → lib/app/features/my/controllers/my_controller.dart

@ -1,7 +1,7 @@
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:problem_check_system/app/routes/app_routes.dart'; import 'package:problem_check_system/app/core/routes/app_routes.dart';
import 'package:problem_check_system/app/core/data/repositories/auth_repository.dart'; import 'package:problem_check_system/app/core/repositories/auth_repository.dart';
class MyController extends GetxController { class MyController extends GetxController {
final AuthRepository authRepository; final AuthRepository authRepository;

2
lib/modules/my/views/change_password.dart → lib/app/features/my/views/change_password.dart

@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:problem_check_system/modules/my/controllers/change_password_controller.dart'; import 'package:problem_check_system/app/features/my/controllers/change_password_controller.dart';
class ChangePasswordPage extends StatelessWidget { class ChangePasswordPage extends StatelessWidget {
const ChangePasswordPage({super.key}); const ChangePasswordPage({super.key});

4
lib/modules/my/views/my_page.dart → lib/app/features/my/views/my_page.dart

@ -1,9 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:problem_check_system/modules/my/controllers/my_controller.dart'; import 'package:problem_check_system/app/features/my/controllers/my_controller.dart';
import 'package:problem_check_system/app/routes/app_routes.dart'; import 'package:problem_check_system/app/core/routes/app_routes.dart';
class MyPage extends GetView<MyController> { class MyPage extends GetView<MyController> {
const MyPage({super.key}); const MyPage({super.key});

32
lib/app/features/navigation/presentation/bindings/navigation_binding.dart

@ -0,0 +1,32 @@
import 'package:get/get.dart';
import 'package:problem_check_system/app/core/models/problem_sync_status.dart';
import 'package:problem_check_system/app/core/repositories/auth_repository.dart';
import 'package:problem_check_system/app/features/problem/data/repositories/problem_repository.dart';
import 'package:problem_check_system/app/features/enterprise/presentation/controllers/enterprise_list_controller.dart';
import 'package:problem_check_system/app/features/navigation/presentation/controllers/navigation_controller.dart';
import 'package:problem_check_system/app/features/my/controllers/my_controller.dart';
import 'package:problem_check_system/app/features/problem/presentation/controllers/problem_controller.dart';
class NavigationBinding extends Bindings {
@override
void dependencies() {
///
Get.lazyPut<NavigationController>(() => NavigationController());
Get.put(ProblemStateManager(uuid: Get.find(), authRepository: Get.find()));
Get.lazyPut<EnterpriseListController>(() => EnterpriseListController());
///
Get.lazyPut<ProblemController>(
() => ProblemController(
problemRepository: Get.find<ProblemRepository>(),
problemStateManager: Get.find<ProblemStateManager>(),
),
fenix: true,
);
///
Get.lazyPut<MyController>(
() => MyController(authRepository: Get.find<AuthRepository>()),
);
}
}

23
lib/app/features/navigation/presentation/controllers/navigation_controller.dart

@ -0,0 +1,23 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:problem_check_system/app/features/enterprise/presentation/pages/enterprise_list_page.dart';
import 'package:problem_check_system/app/features/home/views/home_page.dart';
import 'package:problem_check_system/app/features/my/views/my_page.dart';
import 'package:problem_check_system/app/features/problem/presentation/views/problem_page.dart';
class NavigationController extends GetxController {
var selectedIndex = 0.obs;
//
final List<Widget> pages = [
const HomePage(),
const EnterpriseListPage(),
const ProblemPage(),
const MyPage(),
];
get currentPage => pages[selectedIndex.value];
void changePageIndex(int index) {
selectedIndex.value = index;
}
}

29
lib/app/features/navigation/presentation/pages/navigation_page.dart

@ -0,0 +1,29 @@
import 'package:curved_navigation_bar/curved_navigation_bar.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:problem_check_system/app/features/navigation/presentation/controllers/navigation_controller.dart';
class NavigationPage extends GetView<NavigationController> {
const NavigationPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: CurvedNavigationBar(
backgroundColor: Colors.transparent,
color: Colors.blueAccent,
items: <Widget>[
Icon(Icons.home, color: Colors.white, size: 30),
Icon(Icons.business, color: Colors.white, size: 30),
Icon(Icons.list, color: Colors.white, size: 30),
Icon(Icons.person, color: Colors.white, size: 30),
],
onTap: (index) {
//Handle button tap
controller.changePageIndex(index);
},
),
body: Obx(() => controller.currentPage),
);
}
}

110
lib/app/features/problem/data/datasources/problem_local_datasource.dart

@ -0,0 +1,110 @@
import 'package:problem_check_system/app/core/models/problem_model.dart';
import 'package:problem_check_system/app/core/models/problem_sync_status.dart';
import 'package:problem_check_system/app/core/services/database_service.dart';
import 'package:sqflite/sqflite.dart';
const String _tableName = 'problems';
///
abstract class ProblemLocalDataSource {
Future<int> insertProblem(Problem problem);
Future<int> updateProblem(Problem problem);
Future<int> deleteProblem(String problemId);
Future<Problem?> getProblemById(String id);
Future<List<Problem>> getProblems({
DateTime? startDate,
DateTime? endDate,
List<ProblemSyncStatus>? syncStatuses,
bool? hasBindData,
});
}
///
class ProblemLocalDataSourceImpl implements ProblemLocalDataSource {
final DatabaseService _databaseService;
ProblemLocalDataSourceImpl({required DatabaseService databaseService})
: _databaseService = databaseService;
@override
Future<int> insertProblem(Problem problem) async {
final db = await _databaseService.database;
return await db.insert(
_tableName,
problem.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
@override
Future<int> updateProblem(Problem problem) async {
final db = await _databaseService.database;
return await db.update(
_tableName,
problem.toMap(),
where: 'id = ?',
whereArgs: [problem.id],
);
}
@override
Future<int> deleteProblem(String problemId) async {
final db = await _databaseService.database;
return await db.delete(_tableName, where: 'id = ?', whereArgs: [problemId]);
}
@override
Future<Problem?> getProblemById(String id) async {
final db = await _databaseService.database;
final results = await db.query(
_tableName,
where: 'id = ?',
whereArgs: [id],
limit: 1,
);
return results.isNotEmpty ? Problem.fromMap(results.first) : null;
}
@override
Future<List<Problem>> getProblems({
DateTime? startDate,
DateTime? endDate,
List<ProblemSyncStatus>? syncStatuses,
bool? hasBindData,
}) async {
final db = await _databaseService.database;
final whereClauses = <String>[];
final whereArgs = <dynamic>[];
if (startDate != null) {
whereClauses.add('creationTime >= ?');
whereArgs.add(startDate.millisecondsSinceEpoch);
}
if (endDate != null) {
whereClauses.add('creationTime <= ?');
whereArgs.add(endDate.millisecondsSinceEpoch);
}
if (syncStatuses != null && syncStatuses.isNotEmpty) {
final statuses = syncStatuses.map((s) => s.index).toList();
whereClauses.add(
'syncStatus IN (${List.filled(statuses.length, '?').join(',')})',
);
whereArgs.addAll(statuses);
}
if (hasBindData != null) {
if (hasBindData) {
whereClauses.add('bindData IS NOT NULL AND bindData != ""');
} else {
whereClauses.add('(bindData IS NULL OR bindData = "")');
}
}
final results = await db.query(
_tableName,
where: whereClauses.isNotEmpty ? whereClauses.join(' AND ') : null,
whereArgs: whereArgs.isEmpty ? null : whereArgs,
orderBy: 'creationTime DESC',
);
return results.map((json) => Problem.fromMap(json)).toList();
}
}

12
lib/app/core/data/repositories/problem_repository.dart → lib/app/features/problem/data/repositories/problem_repository.dart

@ -2,12 +2,12 @@ import 'package:dio/dio.dart';
import 'package:get/get.dart' hide MultipartFile, FormData, Response; import 'package:get/get.dart' hide MultipartFile, FormData, Response;
import 'package:problem_check_system/app/core/extensions/http_response_extension.dart'; import 'package:problem_check_system/app/core/extensions/http_response_extension.dart';
import 'package:problem_check_system/app/core/utils/constants/api_endpoints.dart'; import 'package:problem_check_system/app/core/utils/constants/api_endpoints.dart';
import 'package:problem_check_system/app/core/data/models/problem_model.dart'; import 'package:problem_check_system/app/core/models/problem_model.dart';
import 'package:problem_check_system/app/core/data/models/server_problem.dart'; import 'package:problem_check_system/app/core/models/server_problem.dart';
import 'package:problem_check_system/app/core/data/services/network_status_service.dart'; import 'package:problem_check_system/app/core/services/network_status_service.dart';
import 'package:problem_check_system/app/core/data/services/http_provider.dart'; import 'package:problem_check_system/app/core/services/http_provider.dart';
import 'package:problem_check_system/app/core/data/services/sqlite_service.dart'; import 'package:problem_check_system/app/core/services/sqlite_service.dart';
import 'package:problem_check_system/app/core/data/repositories/auth_repository.dart'; import 'package:problem_check_system/app/core/repositories/auth_repository.dart';
/// ///
/// ///

19
lib/app/features/problem/domain/repositoies/problem_repository.dart

@ -0,0 +1,19 @@
import 'package:problem_check_system/app/core/models/problem_model.dart';
/// Problem
///
abstract class ProblemRepository {
Future<void> addProblem(Problem problem);
Future<void> updateProblem(Problem problem);
Future<void> deleteProblem(String problemId);
Future<Problem?> getProblemById(String id);
Future<void> markAsSynced(String id);
Future<List<Problem>> getProblems({
DateTime? startDate,
DateTime? endDate,
String? syncStatus, // 使
String? bindStatus,
});
}

6
lib/modules/problem/bindings/problem_form_binding.dart → lib/app/features/problem/presentation/bindings/problem_form_binding.dart

@ -1,7 +1,7 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:problem_check_system/app/core/data/models/problem_model.dart'; import 'package:problem_check_system/app/core/models/problem_model.dart';
import 'package:problem_check_system/app/core/data/models/problem_sync_status.dart'; import 'package:problem_check_system/app/core/models/problem_sync_status.dart';
import 'package:problem_check_system/modules/problem/controllers/problem_form_controller.dart'; import 'package:problem_check_system/app/features/problem/presentation/controllers/problem_form_controller.dart';
class ProblemFormBinding extends Bindings { class ProblemFormBinding extends Bindings {
@override @override

26
lib/modules/problem/controllers/problem_controller.dart → lib/app/features/problem/presentation/controllers/problem_controller.dart

@ -6,20 +6,20 @@ import 'package:dio/dio.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart' hide MultipartFile, FormData, Response; import 'package:get/get.dart' hide MultipartFile, FormData, Response;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:problem_check_system/app/routes/app_routes.dart'; import 'package:problem_check_system/app/core/routes/app_routes.dart';
import 'package:problem_check_system/app/core/extensions/http_response_extension.dart'; import 'package:problem_check_system/app/core/extensions/http_response_extension.dart';
import 'package:problem_check_system/app/core/data/models/image_metadata_model.dart'; import 'package:problem_check_system/app/core/models/image_metadata_model.dart';
import 'package:problem_check_system/app/core/data/models/image_status.dart'; import 'package:problem_check_system/app/core/models/image_status.dart';
import 'package:problem_check_system/app/core/data/models/problem_sync_status.dart'; import 'package:problem_check_system/app/core/models/problem_sync_status.dart';
import 'package:problem_check_system/app/core/data/models/server_problem.dart'; import 'package:problem_check_system/app/core/models/server_problem.dart';
import 'package:problem_check_system/app/core/data/repositories/file_repository.dart'; import 'package:problem_check_system/app/core/repositories/file_repository.dart';
import 'package:problem_check_system/app/core/data/repositories/image_repository.dart'; import 'package:problem_check_system/app/core/repositories/image_repository.dart';
import 'package:problem_check_system/app/core/data/repositories/problem_repository.dart'; import 'package:problem_check_system/app/features/problem/data/repositories/problem_repository.dart';
import 'package:problem_check_system/app/core/data/models/problem_model.dart'; import 'package:problem_check_system/app/core/models/problem_model.dart';
import 'package:problem_check_system/modules/problem/controllers/sync_progress_state.dart'; import 'package:problem_check_system/app/features/problem/presentation/controllers/sync_progress_state.dart';
import 'package:problem_check_system/modules/problem/views/widgets/models/date_range_enum.dart'; import 'package:problem_check_system/app/features/problem/presentation/views/widgets/models/date_range_enum.dart';
import 'package:problem_check_system/modules/problem/views/widgets/models/dropdown_option.dart'; import 'package:problem_check_system/app/features/problem/presentation/views/widgets/models/dropdown_option.dart';
import 'package:problem_check_system/modules/problem/views/widgets/sync_progress_dialog.dart'; import 'package:problem_check_system/app/features/problem/presentation/views/widgets/sync_progress_dialog.dart';
class ProblemController extends GetxController class ProblemController extends GetxController
with GetSingleTickerProviderStateMixin { with GetSingleTickerProviderStateMixin {

10
lib/modules/problem/controllers/problem_form_controller.dart → lib/app/features/problem/presentation/controllers/problem_form_controller.dart

@ -6,12 +6,12 @@ import 'package:image_picker/image_picker.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:problem_check_system/app/core/data/models/image_status.dart'; import 'package:problem_check_system/app/core/models/image_status.dart';
import 'package:problem_check_system/app/core/data/models/image_metadata_model.dart'; import 'package:problem_check_system/app/core/models/image_metadata_model.dart';
import 'dart:io'; import 'dart:io';
import 'package:problem_check_system/app/core/data/models/problem_model.dart'; import 'package:problem_check_system/app/core/models/problem_model.dart';
import 'package:problem_check_system/app/core/data/models/problem_sync_status.dart'; import 'package:problem_check_system/app/core/models/problem_sync_status.dart';
import 'package:problem_check_system/app/core/data/repositories/problem_repository.dart'; import 'package:problem_check_system/app/features/problem/data/repositories/problem_repository.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
class ProblemFormController extends GetxController { class ProblemFormController extends GetxController {

0
lib/modules/problem/controllers/sync_progress_state.dart → lib/app/features/problem/presentation/controllers/sync_progress_state.dart

2
lib/modules/problem/views/problem_form_page.dart → lib/app/features/problem/presentation/views/problem_form_page.dart

@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import 'package:problem_check_system/modules/problem/controllers/problem_form_controller.dart'; import 'package:problem_check_system/app/features/problem/presentation/controllers/problem_form_controller.dart';
class ProblemFormPage extends GetView<ProblemFormController> { class ProblemFormPage extends GetView<ProblemFormController> {
// //

8
lib/modules/problem/views/problem_list_page.dart → lib/app/features/problem/presentation/views/problem_list_page.dart

@ -2,10 +2,10 @@ import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:easy_refresh/easy_refresh.dart'; import 'package:easy_refresh/easy_refresh.dart';
import 'package:problem_check_system/app/core/data/models/problem_sync_status.dart'; import 'package:problem_check_system/app/core/models/problem_sync_status.dart';
import 'package:problem_check_system/modules/problem/controllers/problem_controller.dart'; import 'package:problem_check_system/app/features/problem/presentation/controllers/problem_controller.dart';
import 'package:problem_check_system/app/core/data/models/problem_model.dart'; import 'package:problem_check_system/app/core/models/problem_model.dart';
import 'package:problem_check_system/modules/problem/views/widgets/problem_card.dart'; import 'package:problem_check_system/app/features/problem/presentation/views/widgets/problem_card.dart';
class ProblemListPage extends GetView<ProblemController> { class ProblemListPage extends GetView<ProblemController> {
final RxList<Problem> problemsToShow; final RxList<Problem> problemsToShow;

123
lib/app/features/problem/presentation/views/problem_page.dart

@ -0,0 +1,123 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:problem_check_system/app/features/problem/presentation/controllers/problem_controller.dart';
import 'package:problem_check_system/app/features/problem/presentation/views/problem_list_page.dart'; // ProblemListPage
import 'package:problem_check_system/app/features/problem/presentation/views/widgets/current_filter_bar.dart';
import 'package:problem_check_system/app/features/problem/presentation/views/widgets/history_filter_bar.dart';
import 'package:problem_check_system/app/features/problem/presentation/views/widgets/problem_card.dart'; //
class ProblemPage extends GetView<ProblemController> {
const ProblemPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'问题列表',
style: TextStyle(
fontWeight: FontWeight.bold,
fontFamily: 'MyFont',
fontSize: 18.sp,
color: Colors.white,
),
),
backgroundColor: Colors.transparent,
flexibleSpace: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFF418CFC), Color(0xFF3DBFFC)],
begin: Alignment.centerLeft,
end: Alignment.centerRight,
),
),
),
elevation: 0,
centerTitle: true,
actions: [
IconButton(
icon: Icon(Icons.add, color: Colors.white), // 使 .sp
onPressed: () {},
),
IconButton(
icon: Icon(Icons.upload, color: Colors.pink[300]), // 使 .sp
onPressed: () {},
),
],
),
body: Column(
children: [
CurrentFilterBar(),
Expanded(
child: // 使
ProblemListPage(
problemsToShow: controller.problems,
viewType: ProblemCardViewType.buttons,
),
),
],
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
// 使 Stack
floatingActionButton: Stack(
children: [
// "添加"
// 使 Align Positioned
Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: EdgeInsets.only(bottom: 24.h), //
child: FloatingActionButton(
heroTag: "btn_add",
onPressed: () {
controller.toProblemFormPageAndRefresh();
},
shape: const CircleBorder(),
backgroundColor: Colors.blue[300],
foregroundColor: Colors.white,
child: const Icon(Icons.add),
),
),
),
// "上传"
Obx(() {
final isOnline = controller.isOnline.value;
return Positioned(
// 使left/right dxtop/bottom dy
left: controller.fabUploadPosition.value.dx,
top: controller.fabUploadPosition.value.dy,
child: GestureDetector(
onPanUpdate: (details) {
//
controller.updateFabUploadPosition(details.delta);
},
onPanEnd: (details) {
//
controller.snapToEdge();
},
child: FloatingActionButton(
heroTag: "btn_upload",
onPressed: isOnline
? () => controller.showUploadPage()
: null,
foregroundColor: Colors.white,
backgroundColor: isOnline
? Colors.red[300]
: Colors.grey[400],
child: Icon(
isOnline
? Icons.file_upload_outlined
: Icons.cloud_off_outlined,
),
),
),
);
}),
],
),
);
}
}

6
lib/modules/problem/views/problem_upload_page.dart → lib/app/features/problem/presentation/views/problem_upload_page.dart

@ -1,9 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:problem_check_system/modules/problem/controllers/problem_controller.dart'; import 'package:problem_check_system/app/features/problem/presentation/controllers/problem_controller.dart';
import 'package:problem_check_system/modules/problem/views/problem_list_page.dart'; import 'package:problem_check_system/app/features/problem/presentation/views/problem_list_page.dart';
import 'package:problem_check_system/modules/problem/views/widgets/problem_card.dart'; import 'package:problem_check_system/app/features/problem/presentation/views/widgets/problem_card.dart';
class ProblemUploadPage extends GetView<ProblemController> { class ProblemUploadPage extends GetView<ProblemController> {
const ProblemUploadPage({super.key}); const ProblemUploadPage({super.key});

2
lib/modules/problem/views/widgets/current_filter_bar.dart → lib/app/features/problem/presentation/views/widgets/current_filter_bar.dart

@ -2,7 +2,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:problem_check_system/modules/problem/controllers/problem_controller.dart'; import 'package:problem_check_system/app/features/problem/presentation/controllers/problem_controller.dart';
import 'custom_filter_dropdown.dart'; import 'custom_filter_dropdown.dart';

0
lib/modules/problem/views/widgets/custom_button.dart → lib/app/features/problem/presentation/views/widgets/custom_button.dart

2
lib/modules/problem/views/widgets/custom_filter_dropdown.dart → lib/app/features/problem/presentation/views/widgets/custom_filter_dropdown.dart

@ -1,7 +1,7 @@
// widgets/custom_filter_dropdown.dart // widgets/custom_filter_dropdown.dart
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:problem_check_system/modules/problem/views/widgets/models/dropdown_option.dart'; import 'package:problem_check_system/app/features/problem/presentation/views/widgets/models/dropdown_option.dart';
class CustomFilterDropdown extends StatelessWidget { class CustomFilterDropdown extends StatelessWidget {
final String title; final String title;

2
lib/modules/problem/views/widgets/history_filter_bar.dart → lib/app/features/problem/presentation/views/widgets/history_filter_bar.dart

@ -2,7 +2,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:problem_check_system/modules/problem/controllers/problem_controller.dart'; import 'package:problem_check_system/app/features/problem/presentation/controllers/problem_controller.dart';
import 'custom_filter_dropdown.dart'; import 'custom_filter_dropdown.dart';

2
lib/modules/problem/views/widgets/models/date_range_enum.dart → lib/app/features/problem/presentation/views/widgets/models/date_range_enum.dart

@ -1,6 +1,6 @@
// models/date_range_enum.dart // models/date_range_enum.dart
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:problem_check_system/modules/problem/views/widgets/models/dropdown_option.dart'; import 'package:problem_check_system/app/features/problem/presentation/views/widgets/models/dropdown_option.dart';
enum DateRange { threeDays, oneWeek, oneMonth } enum DateRange { threeDays, oneWeek, oneMonth }

0
lib/modules/problem/views/widgets/models/dropdown_option.dart → lib/app/features/problem/presentation/views/widgets/models/dropdown_option.dart

10
lib/modules/problem/views/widgets/problem_card.dart → lib/app/features/problem/presentation/views/widgets/problem_card.dart

@ -2,11 +2,11 @@ import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:problem_check_system/app/routes/app_routes.dart'; import 'package:problem_check_system/app/core/routes/app_routes.dart';
import 'package:problem_check_system/app/core/data/models/problem_sync_status.dart'; import 'package:problem_check_system/app/core/models/problem_sync_status.dart';
import 'package:problem_check_system/app/core/data/models/problem_model.dart'; import 'package:problem_check_system/app/core/models/problem_model.dart';
import 'package:problem_check_system/modules/problem/controllers/problem_controller.dart'; import 'package:problem_check_system/app/features/problem/presentation/controllers/problem_controller.dart';
import 'package:problem_check_system/modules/problem/views/widgets/custom_button.dart'; import 'package:problem_check_system/app/features/problem/presentation/views/widgets/custom_button.dart';
import 'package:tdesign_flutter/tdesign_flutter.dart'; import 'package:tdesign_flutter/tdesign_flutter.dart';
import 'dart:io'; // import 'dart:io'; //

2
lib/modules/problem/views/widgets/sync_progress_dialog.dart → lib/app/features/problem/presentation/views/widgets/sync_progress_dialog.dart

@ -1,7 +1,7 @@
// sync_progress_dialog.dart // sync_progress_dialog.dart
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:problem_check_system/modules/problem/controllers/sync_progress_state.dart'; import 'package:problem_check_system/app/features/problem/presentation/controllers/sync_progress_state.dart';
class SyncProgressDialog extends StatelessWidget { class SyncProgressDialog extends StatelessWidget {
final SyncProgressState progressState; final SyncProgressState progressState;

6
lib/main.dart

@ -3,9 +3,9 @@ import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get_navigation/src/root/get_material_app.dart'; import 'package:get/get_navigation/src/root/get_material_app.dart';
import 'package:get_storage/get_storage.dart'; import 'package:get_storage/get_storage.dart';
import 'package:problem_check_system/app/routes/app_pages.dart'; import 'package:problem_check_system/app/core/routes/app_pages.dart';
import 'package:problem_check_system/app/routes/app_routes.dart'; // import 'package:problem_check_system/app/core/routes/app_routes.dart'; //
import 'package:problem_check_system/app/bindings/initial_binding.dart'; import 'package:problem_check_system/app/core/bindings/initial_binding.dart';
import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart';
void main() async { void main() async {

50
lib/modules/home/views/home_page.dart

@ -1,50 +0,0 @@
// lib/modules/home/views/home_page.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:problem_check_system/modules/home/controllers/home_controller.dart';
class HomePage extends GetView<HomeController> {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Obx(
() => Scaffold(
body: controller.pages[controller.selectedIndex.value],
// 1. BottomNavigationBar NavigationBar
bottomNavigationBar: NavigationBar(
// 2. 'currentIndex' 'selectedIndex'
selectedIndex: controller.selectedIndex.value,
// 3. 'onTap' 'onDestinationSelected'
onDestinationSelected: controller.changeIndex,
// M3 (, )
//
animationDuration: const Duration(milliseconds: 800),
// 4. 'items' 'destinations'使 NavigationDestination
destinations: const [
NavigationDestination(
// M3
icon: Icon(Icons.home_outlined, color: Colors.blue),
selectedIcon: Icon(Icons.home, color: Colors.blue),
label: '企业',
),
NavigationDestination(
icon: Icon(Icons.now_widgets_outlined, color: Colors.blue),
selectedIcon: Icon(Icons.now_widgets, color: Colors.blue),
label: '全部问题',
),
NavigationDestination(
icon: Icon(Icons.person_outline, color: Colors.blue),
selectedIcon: Icon(Icons.person, color: Colors.blue),
label: '我的',
),
],
),
),
);
}
}

165
lib/modules/problem/views/problem_page.dart

@ -1,165 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:problem_check_system/modules/problem/controllers/problem_controller.dart';
import 'package:problem_check_system/modules/problem/views/problem_list_page.dart'; // ProblemListPage
import 'package:problem_check_system/modules/problem/views/widgets/current_filter_bar.dart';
import 'package:problem_check_system/modules/problem/views/widgets/history_filter_bar.dart';
import 'package:problem_check_system/modules/problem/views/widgets/problem_card.dart'; //
class ProblemPage extends GetView<ProblemController> {
const ProblemPage({super.key});
@override
Widget build(BuildContext context) {
return DefaultTabController(
initialIndex: 0,
length: 2,
child: Scaffold(
body: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 375.w,
height: 83.5.h,
alignment: Alignment.bottomLeft,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [Color(0xFF418CFC), Color(0xFF3DBFFC)],
),
),
child: TabBar(
controller: controller.tabController,
indicatorSize: TabBarIndicatorSize.tab,
indicator: const BoxDecoration(
color: Color(0xfff7f7f7),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(8),
topRight: Radius.circular(60),
),
),
tabs: const [
Tab(text: '问题列表'),
Tab(text: '历史问题列表'),
],
labelStyle: TextStyle(
fontFamily: 'MyFont',
fontWeight: FontWeight.w800,
fontSize: 14.sp,
),
unselectedLabelStyle: TextStyle(
fontFamily: 'MyFont',
fontWeight: FontWeight.w800,
fontSize: 14.sp,
),
labelColor: Colors.black,
unselectedLabelColor: Color(0xfff7f7f7),
),
),
Expanded(
child: TabBarView(
controller: controller.tabController,
children: [
// Tab
DecoratedBox(
decoration: BoxDecoration(color: Color(0xfff7f7f7)),
child: Column(
children: [
CurrentFilterBar(),
Expanded(
child: // 使
ProblemListPage(
problemsToShow: controller.problems,
viewType: ProblemCardViewType.buttons,
),
),
],
),
),
// Tab
DecoratedBox(
decoration: BoxDecoration(color: Color(0xfff7f7f7)),
child: Column(
children: [
HistoryFilterBar(),
Expanded(
child: // 使
ProblemListPage(
problemsToShow: controller.historyProblems,
viewType: ProblemCardViewType.buttons,
),
),
],
),
),
],
),
),
],
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
// 使 Stack
floatingActionButton: Stack(
children: [
// "添加"
// 使 Align Positioned
Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: EdgeInsets.only(bottom: 24.h), //
child: FloatingActionButton(
heroTag: "btn_add",
onPressed: () {
controller.toProblemFormPageAndRefresh();
},
shape: const CircleBorder(),
backgroundColor: Colors.blue[300],
foregroundColor: Colors.white,
child: const Icon(Icons.add),
),
),
),
// "上传"
Obx(() {
final isOnline = controller.isOnline.value;
return Positioned(
// 使left/right dxtop/bottom dy
left: controller.fabUploadPosition.value.dx,
top: controller.fabUploadPosition.value.dy,
child: GestureDetector(
onPanUpdate: (details) {
//
controller.updateFabUploadPosition(details.delta);
},
onPanEnd: (details) {
//
controller.snapToEdge();
},
child: FloatingActionButton(
heroTag: "btn_upload",
onPressed: isOnline
? () => controller.showUploadPage()
: null,
foregroundColor: Colors.white,
backgroundColor: isOnline
? Colors.red[300]
: Colors.grey[400],
child: Icon(
isOnline
? Icons.file_upload_outlined
: Icons.cloud_off_outlined,
),
),
),
);
}),
],
),
),
);
}
}

16
pubspec.lock

@ -169,6 +169,14 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "3.0.6" version: "3.0.6"
curved_navigation_bar:
dependency: "direct main"
description:
name: curved_navigation_bar
sha256: bb4ab128fcb6f4a9f0f1f72d227db531818b20218984789777f049fcbf919279
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.6"
dart_style: dart_style:
dependency: transitive dependency: transitive
description: description:
@ -209,6 +217,14 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "3.4.0" version: "3.4.0"
equatable:
dependency: "direct main"
description:
name: equatable
sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.7"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:

2
pubspec.yaml

@ -9,8 +9,10 @@ environment:
dependencies: dependencies:
connectivity_plus: ^6.1.5 connectivity_plus: ^6.1.5
crypto: ^3.0.6 crypto: ^3.0.6
curved_navigation_bar: ^1.0.6
dio: ^5.9.0 dio: ^5.9.0
easy_refresh: ^3.4.0 easy_refresh: ^3.4.0
equatable: ^2.0.7
flutter: flutter:
sdk: flutter sdk: flutter
flutter_localizations: flutter_localizations:

Loading…
Cancel
Save