Browse Source

新增:保存问题文件

dev
徐振升 3 weeks ago
parent
commit
2ba07bde25
  1. 2
      android/app/src/main/AndroidManifest.xml
  2. 135
      lib/modules/problem/models/problem.dart
  3. 244
      lib/modules/problem/problem_new_page.dart
  4. 6
      lib/modules/problem/problem_page.dart
  5. 29
      lib/modules/problem/view_model/image_controller.dart
  6. 39
      lib/modules/problem/view_model/problem_new_controller.dart
  7. 2
      macos/Flutter/GeneratedPluginRegistrant.swift
  8. 156
      pubspec.lock
  9. 5
      pubspec.yaml
  10. 3
      windows/flutter/generated_plugin_registrant.cc
  11. 1
      windows/flutter/generated_plugins.cmake

2
android/app/src/main/AndroidManifest.xml

@ -1,4 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application <application
android:label="problem_check_system" android:label="problem_check_system"
android:name="${applicationName}" android:name="${applicationName}"

135
lib/modules/problem/models/problem.dart

@ -0,0 +1,135 @@
import 'package:uuid/uuid.dart';
class Problem {
final String id;
final String description;
final String location;
final String imageUrl;
final DateTime createdAt;
final DateTime updatedAt;
static final Uuid _uuid = const Uuid();
Problem._({
required this.id,
required this.description,
required this.location,
required this.imageUrl,
required this.createdAt,
required this.updatedAt,
});
//
factory Problem({
required String description,
required String location,
required String imageUrl,
String? id,
DateTime? createdAt,
DateTime? updatedAt,
}) {
//
_validateParameters(description, location, imageUrl);
final now = DateTime.now();
return Problem._(
id: id ?? _uuid.v4(),
description: description.trim(),
location: location.trim(),
imageUrl: imageUrl,
createdAt: createdAt ?? now,
updatedAt: updatedAt ?? now,
);
}
//
static void _validateParameters(
String description,
String location,
String imageUrl,
) {
if (description.isEmpty) {
throw ArgumentError('Description cannot be empty');
}
if (location.isEmpty) {
throw ArgumentError('Location cannot be empty');
}
if (imageUrl.isEmpty) {
throw ArgumentError('Image URL cannot be empty');
}
// URL
if (!imageUrl.startsWith('http')) {
throw ArgumentError('Image URL must be a valid URL');
}
}
// JSON
factory Problem.fromJson(Map<String, dynamic> json) {
return Problem(
id: json['id'],
description: json['description'],
location: json['location'],
imageUrl: json['imageUrl'],
createdAt: json['createdAt'] != null
? DateTime.parse(json['createdAt'])
: null,
updatedAt: json['updatedAt'] != null
? DateTime.parse(json['updatedAt'])
: null,
);
}
// JSON
Map<String, dynamic> toJson() {
return {
'id': id,
'description': description,
'location': location,
'imageUrl': imageUrl,
'createdAt': createdAt.toIso8601String(),
'updatedAt': updatedAt.toIso8601String(),
};
}
//
Problem copyWith({
String? id,
String? description,
String? location,
String? imageUrl,
DateTime? createdAt,
DateTime? updatedAt,
}) {
return Problem(
id: id ?? this.id,
description: description ?? this.description,
location: location ?? this.location,
imageUrl: imageUrl ?? this.imageUrl,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? DateTime.now(), //
);
}
//
bool get isValid => description.isNotEmpty && location.isNotEmpty;
//
String get shortDescription {
if (description.length <= 50) return description;
return '${description.substring(0, 47)}...';
}
@override
String toString() {
return 'Problem(id: $id, description: $shortDescription, location: $location)';
}
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is Problem && runtimeType == other.runtimeType && id == other.id;
@override
int get hashCode => id.hashCode;
}

244
lib/modules/problem/problem_new_page.dart

@ -0,0 +1,244 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:problem_check_system/modules/problem/models/problem.dart';
import 'package:problem_check_system/modules/problem/view_model/image_controller.dart';
import 'package:problem_check_system/modules/problem/view_model/problem_new_controller.dart';
class ProblemNewPage extends StatelessWidget {
ProblemNewPage({super.key});
final ImageController imageController = Get.put(ImageController());
final ProblemNewController problemNewController = Get.put(
ProblemNewController(),
);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
flexibleSpace: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFF418CFC), Color(0xFF3DBFFC)], //
begin: Alignment.centerLeft, //
end: Alignment.centerRight, //
),
),
),
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios_new_rounded), //
color: Colors.white,
onPressed: () {
Navigator.pop(context); //
},
),
title: const Text('新增问题', style: TextStyle(color: Colors.white)),
centerTitle: true,
backgroundColor: Colors.transparent, //
),
body: Column(
children: [
Expanded(
child: Column(
children: [
Card(
margin: EdgeInsets.all(17.w),
child: Column(
children: [
ListTile(
title: const Text(
'问题描述', //
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
subtitle: TextField(
maxLines: null,
decoration: const InputDecoration(
hintText: '请输入问题描述',
border: InputBorder.none,
),
onChanged: (value) {
//
},
),
),
Align(
alignment: Alignment.centerRight, //
child: Container(
margin: const EdgeInsets.all(10),
child: ElevatedButton(
onPressed: () {
print("按钮被点击");
},
child: const Icon(Icons.keyboard_voice),
),
),
),
],
),
),
Card(
margin: EdgeInsets.all(17.w),
child: Column(
children: [
ListTile(
title: const Text(
'所在位置', //
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
subtitle: TextField(
maxLines: null,
decoration: const InputDecoration(
hintText: '请输入问题所在位置',
border: InputBorder.none,
),
onChanged: (value) {
//
},
),
),
Align(
alignment: Alignment.centerRight, //
child: Container(
margin: const EdgeInsets.all(10),
child: ElevatedButton(
onPressed: () {
print("按钮被点击");
},
child: const Icon(Icons.keyboard_voice),
),
),
),
],
),
),
Card(
margin: EdgeInsets.all(17.w),
child: Column(
children: [
ListTile(
title: const Text(
'问题图片', //
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
subtitle: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
//
Obx(() {
final file = imageController.selectedImage.value;
if (file == null) {
return const Text('尚未选取图片');
}
return Image.file(
file,
width: 100,
height: 100,
fit: BoxFit.cover,
);
}),
const SizedBox(width: 20),
Container(
color: Color(0xffF7F7F7),
width: 100,
height: 100,
child: IconButton(
icon: const Icon(Icons.image),
iconSize: 40, //
onPressed: imageController.pickAndCopyImage,
),
),
],
),
),
],
),
),
],
),
),
//
Container(
width: 375.w,
height: 81.h,
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
onPressed: () {
//
Get.back();
},
child: const Text(
'取消',
style: TextStyle(color: Colors.grey),
),
),
),
const SizedBox(width: 10),
Expanded(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF418CFC),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
onPressed: () async {
//
final problem1 = Problem(
description: '墙面裂缝需要修复',
location: 'A栋101房间',
imageUrl: 'https://example.com/images/wall_crack.jpg',
);
await problemNewController.saveJson(
problem1.toJson(),
problem1.id,
);
Get.back();
Get.snackbar(
'保存成功',
'${problem1.id}.json 已创建',
snackPosition: SnackPosition.TOP,
snackStyle: SnackStyle.FLOATING,
backgroundColor: Colors.black87,
colorText: Colors.white,
);
},
child: const Text(
'确定',
style: TextStyle(color: Colors.white),
),
),
),
],
),
),
],
),
);
}
}

6
lib/modules/problem/problem_page.dart

@ -1,7 +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:problem_check_system/modules/problem/components/date_picker_button.dart'; import 'package:problem_check_system/modules/problem/components/date_picker_button.dart';
import 'package:problem_check_system/modules/problem/problem_card.dart'; import 'package:problem_check_system/modules/problem/problem_card.dart';
import 'package:problem_check_system/modules/problem/problem_new_page.dart';
class ProblemPage extends StatelessWidget { class ProblemPage extends StatelessWidget {
ProblemPage({super.key}); ProblemPage({super.key});
@ -110,8 +112,9 @@ class ProblemPage extends StatelessWidget {
bottom: 5.h, bottom: 5.h,
right: 160.5.w, right: 160.5.w,
child: FloatingActionButton( child: FloatingActionButton(
heroTag: "123",
onPressed: () { onPressed: () {
print('object'); Get.to(() => ProblemNewPage());
}, },
shape: CircleBorder(), shape: CircleBorder(),
backgroundColor: Colors.blue[300], backgroundColor: Colors.blue[300],
@ -132,6 +135,7 @@ class ProblemPage extends StatelessWidget {
), ),
), ),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
heroTag: "abc",
onPressed: () { onPressed: () {
print('object'); print('object');
}, },

29
lib/modules/problem/view_model/image_controller.dart

@ -0,0 +1,29 @@
import 'dart:io';
import 'package:get/get.dart';
import 'package:image_picker/image_picker.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
class ImageController extends GetxController {
// Rxn nullable
final Rxn<File> selectedImage = Rxn<File>();
//
Future<void> pickAndCopyImage() async {
// ImagePicker
final picked = await ImagePicker().pickImage(source: ImageSource.gallery);
if (picked == null) return;
final originalFile = File(picked.path);
//
final appDir = await getApplicationDocumentsDirectory();
final fileName = basename(picked.path);
//
final copiedFile = await originalFile.copy('${appDir.path}/$fileName');
//
selectedImage.value = copiedFile;
}
}

39
lib/modules/problem/view_model/problem_new_controller.dart

@ -0,0 +1,39 @@
import 'dart:convert';
import 'dart:io';
import 'package:get/get.dart';
import 'package:path_provider/path_provider.dart';
class ProblemNewController extends GetxController {
late Directory appDocDir;
@override
void onInit() {
super.onInit();
_initDirectory();
}
Future<void> _initDirectory() async {
appDocDir = await getApplicationDocumentsDirectory();
}
// todo problems文件不存在
/// Map JSON
Future<File> saveJson(Map<String, dynamic> data, String fileName) async {
final path = '${appDocDir.path}/problems/$fileName.json';
final file = File(path);
final jsonStr = jsonEncode(data);
return file.writeAsString(jsonStr);
}
/// JSON Map
Future<Map<String, dynamic>?> readJson(String fileName) async {
final path = '${appDocDir.path}/problems/$fileName.json';
final file = File(path);
if (!await file.exists()) {
return null;
}
final content = await file.readAsString();
return jsonDecode(content) as Map<String, dynamic>;
}
}

2
macos/Flutter/GeneratedPluginRegistrant.swift

@ -6,7 +6,9 @@ import FlutterMacOS
import Foundation import Foundation
import file_selector_macos import file_selector_macos
import path_provider_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
} }

156
pubspec.lock

@ -49,6 +49,14 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "0.3.4+2" version: "0.3.4+2"
crypto:
dependency: transitive
description:
name: crypto
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.0.6"
easy_refresh: easy_refresh:
dependency: transitive dependency: transitive
description: description:
@ -65,6 +73,14 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.3.3" version: "1.3.3"
ffi:
dependency: transitive
description:
name: ffi
sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.4"
file_selector_linux: file_selector_linux:
dependency: transitive dependency: transitive
description: description:
@ -97,6 +113,14 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "0.9.3+4" version: "0.9.3+4"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.1"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@ -177,7 +201,7 @@ packages:
source: hosted source: hosted
version: "4.1.2" version: "4.1.2"
image_picker: image_picker:
dependency: transitive dependency: "direct main"
description: description:
name: image_picker name: image_picker
sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a" sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a"
@ -305,7 +329,7 @@ packages:
source: hosted source: hosted
version: "2.0.0" version: "2.0.0"
path: path:
dependency: transitive dependency: "direct main"
description: description:
name: path name: path
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
@ -328,6 +352,110 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.1.0" version: "1.1.0"
path_provider:
dependency: "direct main"
description:
name: path_provider
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.5"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.2.17"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.4.2"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.2.1"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.2"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.3.0"
permission_handler:
dependency: "direct main"
description:
name: permission_handler
sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1
url: "https://pub.flutter-io.cn"
source: hosted
version: "12.0.1"
permission_handler_android:
dependency: transitive
description:
name: permission_handler_android
sha256: "1e3bc410ca1bf84662104b100eb126e066cb55791b7451307f9708d4007350e6"
url: "https://pub.flutter-io.cn"
source: hosted
version: "13.0.1"
permission_handler_apple:
dependency: transitive
description:
name: permission_handler_apple
sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023
url: "https://pub.flutter-io.cn"
source: hosted
version: "9.4.7"
permission_handler_html:
dependency: transitive
description:
name: permission_handler_html
sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.1.3+5"
permission_handler_platform_interface:
dependency: transitive
description:
name: permission_handler_platform_interface
sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.3.0"
permission_handler_windows:
dependency: transitive
description:
name: permission_handler_windows
sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.1"
platform:
dependency: transitive
description:
name: platform
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.1.6"
plugin_platform_interface: plugin_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -349,6 +477,14 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.10.1" version: "1.10.1"
sprintf:
dependency: transitive
description:
name: sprintf
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
url: "https://pub.flutter-io.cn"
source: hosted
version: "7.0.0"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -413,6 +549,14 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.4.0" version: "1.4.0"
uuid:
dependency: "direct main"
description:
name: uuid
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.5.1"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
@ -437,6 +581,14 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.1.1" version: "1.1.1"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.0"
sdks: sdks:
dart: ">=3.8.1 <4.0.0" dart: ">=3.8.1 <4.0.0"
flutter: ">=3.29.0" flutter: ">=3.29.0"

5
pubspec.yaml

@ -11,7 +11,12 @@ dependencies:
sdk: flutter sdk: flutter
flutter_screenutil: ^5.9.3 flutter_screenutil: ^5.9.3
get: ^4.7.2 get: ^4.7.2
image_picker: ^1.1.2
path: ^1.9.1
path_provider: ^2.1.5
permission_handler: ^12.0.1
tdesign_flutter: ^0.2.4 tdesign_flutter: ^0.2.4
uuid: ^4.5.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

3
windows/flutter/generated_plugin_registrant.cc

@ -7,8 +7,11 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <file_selector_windows/file_selector_windows.h> #include <file_selector_windows/file_selector_windows.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
FileSelectorWindowsRegisterWithRegistrar( FileSelectorWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FileSelectorWindows")); registry->GetRegistrarForPlugin("FileSelectorWindows"));
PermissionHandlerWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
} }

1
windows/flutter/generated_plugins.cmake

@ -4,6 +4,7 @@
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
file_selector_windows file_selector_windows
permission_handler_windows
) )
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST

Loading…
Cancel
Save