diff --git a/lib/http/api.dart b/lib/http/api.dart index 02203ef34..86074c798 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -625,4 +625,6 @@ class Api { static const String createTextDynamic = '/dynamic_svr/v1/dynamic_svr/create'; static const String removeDynamic = '/dynamic_svr/v1/dynamic_svr/rm_dynamic'; + + static const String uploadBfs = '/x/dynamic/feed/draw/upload_bfs'; } diff --git a/lib/http/msg.dart b/lib/http/msg.dart index a6461ee5d..1e539c453 100644 --- a/lib/http/msg.dart +++ b/lib/http/msg.dart @@ -1,3 +1,4 @@ +import 'dart:io'; import 'dart:math'; import 'package:PiliPalaX/http/constants.dart'; import 'package:dio/dio.dart'; @@ -145,8 +146,9 @@ class MsgHttp { static Future createDynamic({ dynamic mid, - dynamic dynIdStr, + dynamic dynIdStr, // repost dynamic rawText, + List? pics, }) async { String csrf = await Request.getCsrf(); var res = await Request().post( @@ -165,7 +167,12 @@ class MsgHttp { {"raw_text": rawText, "type": 1, "biz_id": ""} ] }, - "scene": 4, + "scene": dynIdStr != null + ? 4 + : pics != null + ? 2 + : 1, + if (pics != null) 'pics': pics, "attach_card": null, "upload_id": "${mid}_${DateTime.now().millisecondsSinceEpoch ~/ 1000}_${Random().nextInt(9000) + 1000}", @@ -173,7 +180,7 @@ class MsgHttp { "app_meta": {"from": "create.dynamic.web", "mobi_app": "web"} } }, - "web_repost_src": {"dyn_id_str": dynIdStr} + if (dynIdStr != null) "web_repost_src": {"dyn_id_str": dynIdStr} }, ); if (res.data['code'] == 0) { @@ -186,6 +193,32 @@ class MsgHttp { } } + static Future uploadBfs( + dynamic path, + ) async { + String csrf = await Request.getCsrf(); + Map data = await WbiSign().makSign({ + 'file_up': await MultipartFile.fromFile(path), + 'category': 'daily', + 'csrf': csrf, + }); + var res = await Request().post( + Api.uploadBfs, + data: FormData.fromMap(data), + ); + if (res.data['code'] == 0) { + return { + 'status': true, + 'data': res.data['data'], + }; + } else { + return { + 'status': false, + 'msg': res.data['message'], + }; + } + } + static Future createTextDynamic( dynamic content, ) async { diff --git a/lib/pages/dynamics/view.dart b/lib/pages/dynamics/view.dart index 8ec3a0ff2..2bc19ef36 100644 --- a/lib/pages/dynamics/view.dart +++ b/lib/pages/dynamics/view.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'package:PiliPalaX/http/msg.dart'; import 'package:PiliPalaX/models/common/dynamics_type.dart'; @@ -12,6 +13,7 @@ import 'package:get/get.dart'; import 'package:hive/hive.dart'; import 'package:PiliPalaX/utils/feed_back.dart'; import 'package:PiliPalaX/utils/storage.dart'; +import 'package:image_picker/image_picker.dart'; import 'controller.dart'; import 'widgets/up_panel.dart'; @@ -219,20 +221,60 @@ class _CreatePanelState extends State { final _ctr = TextEditingController(); bool _isEnable = false; final _isEnableStream = StreamController(); + late final _imagePicker = ImagePicker(); + late final _pics = []; + late final _pathList = []; + late final _pathStream = StreamController>(); @override void dispose() { + _isEnableStream.close(); + _pathStream.close(); _ctr.dispose(); super.dispose(); } Future _onCreate() async { - dynamic result = await MsgHttp.createTextDynamic(_ctr.text); - if (result['status']) { - Get.back(); - SmartDialog.showToast('发布成功'); + if (_pathList.isEmpty) { + dynamic result = await MsgHttp.createTextDynamic(_ctr.text); + if (result['status']) { + Get.back(); + SmartDialog.showToast('发布成功'); + } else { + SmartDialog.showToast(result['msg']); + } } else { - SmartDialog.showToast(result['msg']); + for (int i = 0; i < _pathList.length; i++) { + SmartDialog.showLoading(msg: '正在上传图片: ${i + 1}/${_pathList.length}'); + dynamic result = await MsgHttp.uploadBfs(_pathList[i]); + if (result['status']) { + int imageSize = await File(_pathList[i]).length(); + _pics.add({ + 'img_width': result['data']['image_width'], + 'img_height': result['data']['image_height'], + 'img_size': imageSize / 1024, + 'img_src': result['data']['image_url'], + }); + } else { + SmartDialog.dismiss(); + SmartDialog.showToast(result['msg']); + return; + } + if (i == _pathList.length - 1) { + SmartDialog.dismiss(); + } + } + dynamic result = await MsgHttp.createDynamic( + mid: GStorage.userInfo.get('userInfoCache').mid, + rawText: _ctr.text, + pics: _pics, + ); + if (result['status']) { + Get.back(); + SmartDialog.showToast('发布成功'); + } else { + SmartDialog.showToast(result['msg']); + } } } @@ -320,6 +362,84 @@ class _CreatePanelState extends State { ), ), ), + StreamBuilder( + initialData: const [], + stream: _pathStream.stream, + builder: (_, snapshot) => SizedBox( + height: 75, + child: ListView.separated( + scrollDirection: Axis.horizontal, + physics: const AlwaysScrollableScrollPhysics( + parent: BouncingScrollPhysics(), + ), + padding: const EdgeInsets.symmetric(horizontal: 15), + itemCount: _pathList.length == 9 ? 9 : _pathList.length + 1, + itemBuilder: (context, index) { + if (_pathList.length != 9 && index == _pathList.length) { + return Ink( + width: 75, + height: 75, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: Theme.of(context).colorScheme.secondaryContainer, + ), + child: InkWell( + onTap: () async { + List pickedFiles = + await _imagePicker.pickMultiImage( + limit: 9, + imageQuality: 100, + ); + if (pickedFiles.isNotEmpty) { + for (int i = 0; i < pickedFiles.length; i++) { + if (_pathList.length == 9) { + SmartDialog.showToast('最多选择9张图片'); + if (i != 0) { + _pathStream.add(_pathList); + } + break; + } else { + _pathList.add(pickedFiles[i].path); + if (i == pickedFiles.length - 1) { + _pathStream.add(_pathList); + } + } + } + if (_pathList.isNotEmpty && !_isEnable) { + _isEnable = true; + _isEnableStream.add(true); + } + } + }, + borderRadius: BorderRadius.circular(12), + child: const Center(child: Icon(Icons.add)), + ), + ); + } else { + return GestureDetector( + onTap: () { + _pics.clear(); + _pathList.removeAt(index); + _pathStream.add(_pathList); + if (_pathList.isEmpty && + _ctr.text.replaceAll('\n', '').isEmpty) { + _isEnable = false; + _isEnableStream.add(false); + } + }, + child: Image( + height: 75, + fit: BoxFit.fitHeight, + filterQuality: FilterQuality.low, + image: FileImage(File(_pathList[index])), + ), + ); + } + }, + separatorBuilder: (_, index) => const SizedBox(width: 10), + ), + ), + ), ], ); } diff --git a/pubspec.lock b/pubspec.lock index b3eee1745..c3353e6cf 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -559,6 +559,38 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492" + url: "https://pub.dev" + source: hosted + version: "0.9.2+1" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: cb284e267f8e2a45a904b5c094d2ba51d0aabfc20b1538ab786d9ef7dc2bf75c + url: "https://pub.dev" + source: hosted + version: "0.9.4+1" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b + url: "https://pub.dev" + source: hosted + version: "2.6.2" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: "2ad726953f6e8affbc4df8dc78b77c3b4a060967a291e528ef72ae846c60fb69" + url: "https://pub.dev" + source: hosted + version: "0.9.3+2" fixnum: dependency: transitive description: @@ -890,6 +922,70 @@ packages: url: "https://pub.dev" source: hosted version: "4.2.0" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: c0a6763d50b354793d0192afd0a12560b823147d3ded7c6b77daf658fa05cc85 + url: "https://pub.dev" + source: hosted + version: "0.8.12+13" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "65d94623e15372c5c51bebbcb820848d7bcb323836e12dfdba60b5d3a8b39e50" + url: "https://pub.dev" + source: hosted + version: "3.0.5" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: "6703696ad49f5c3c8356d576d7ace84d1faf459afb07accbb0fae780753ff447" + url: "https://pub.dev" + source: hosted + version: "0.8.12" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "4ed1d9bb36f7cd60aa6e6cd479779cc56a4cb4e4de8f49d487b1aaad831300fa" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "3f5ad1e8112a9a6111c46d0b57a7be2286a9a07fc6e1976fdf5be2bd31d4ff62" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "9ec26d410ff46f483c5519c29c02ef0e02e13a543f882b152d4bfd2f06802f80" + url: "https://pub.dev" + source: hosted + version: "2.10.0" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" intl: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index a90ef5ecf..6f7721465 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -159,6 +159,8 @@ dependencies: #富文本 extended_text: ^14.0.1 chat_bottom_container: ^0.2.0 + image_picker: ^1.1.2 + dependency_overrides: mime: git: https://github.com/orz12/mime.git