diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 9ebd2d3..1331019 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,6 @@ + + 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 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; +} diff --git a/lib/modules/problem/problem_new_page.dart b/lib/modules/problem/problem_new_page.dart new file mode 100644 index 0000000..603b5fd --- /dev/null +++ b/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), + ), + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/modules/problem/problem_page.dart b/lib/modules/problem/problem_page.dart index 69f2af0..e802f54 100644 --- a/lib/modules/problem/problem_page.dart +++ b/lib/modules/problem/problem_page.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.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/problem_card.dart'; +import 'package:problem_check_system/modules/problem/problem_new_page.dart'; class ProblemPage extends StatelessWidget { ProblemPage({super.key}); @@ -110,8 +112,9 @@ class ProblemPage extends StatelessWidget { bottom: 5.h, right: 160.5.w, child: FloatingActionButton( + heroTag: "123", onPressed: () { - print('object'); + Get.to(() => ProblemNewPage()); }, shape: CircleBorder(), backgroundColor: Colors.blue[300], @@ -132,6 +135,7 @@ class ProblemPage extends StatelessWidget { ), ), floatingActionButton: FloatingActionButton( + heroTag: "abc", onPressed: () { print('object'); }, diff --git a/lib/modules/problem/view_model/image_controller.dart b/lib/modules/problem/view_model/image_controller.dart new file mode 100644 index 0000000..ef95616 --- /dev/null +++ b/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 selectedImage = Rxn(); + + // 从相册选取并复制 + Future 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; + } +} diff --git a/lib/modules/problem/view_model/problem_new_controller.dart b/lib/modules/problem/view_model/problem_new_controller.dart new file mode 100644 index 0000000..e6edec5 --- /dev/null +++ b/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 _initDirectory() async { + appDocDir = await getApplicationDocumentsDirectory(); + } + + // todo problems文件不存在,需要创建 + /// 保存 Map 数据为 JSON 文件 + Future saveJson(Map 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?> 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; + } +} diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 14b5f7c..b878e03 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,7 +6,9 @@ import FlutterMacOS import Foundation import file_selector_macos +import path_provider_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 3cb504e..05e4120 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -49,6 +49,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted 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: dependency: transitive description: @@ -65,6 +73,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted 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: dependency: transitive description: @@ -97,6 +113,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted 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: dependency: "direct main" description: flutter @@ -177,7 +201,7 @@ packages: source: hosted version: "4.1.2" image_picker: - dependency: transitive + dependency: "direct main" description: name: image_picker sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a" @@ -305,7 +329,7 @@ packages: source: hosted version: "2.0.0" path: - dependency: transitive + dependency: "direct main" description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" @@ -328,6 +352,110 @@ packages: url: "https://pub.flutter-io.cn" source: hosted 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: dependency: transitive description: @@ -349,6 +477,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted 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: dependency: transitive description: @@ -413,6 +549,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted 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: dependency: transitive description: @@ -437,6 +581,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted 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: dart: ">=3.8.1 <4.0.0" flutter: ">=3.29.0" diff --git a/pubspec.yaml b/pubspec.yaml index 9cf07f5..b65dd8c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,12 @@ dependencies: sdk: flutter flutter_screenutil: ^5.9.3 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 + uuid: ^4.5.1 dev_dependencies: flutter_test: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 77ab7a0..2c256bd 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -7,8 +7,11 @@ #include "generated_plugin_registrant.h" #include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { FileSelectorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("FileSelectorWindows")); + PermissionHandlerWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index a423a02..230eabf 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST file_selector_windows + permission_handler_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST