From cb58822009f0c877c542e56fe5cd91ba295dc43c Mon Sep 17 00:00:00 2001 From: dom Date: Tue, 13 Jan 2026 10:16:45 +0800 Subject: [PATCH] feat: edit dyn feat: set pub setting feat: set reply interaction Signed-off-by: dom --- README.md | 1 + lib/http/api.dart | 10 + lib/http/dynamics.dart | 120 ++++++++++- lib/http/reply.dart | 51 +++++ lib/models/dynamics/result.dart | 28 ++- lib/models_new/reply_interaction/data.dart | 20 ++ .../reply_interaction/interact_status.dart | 14 ++ .../publish/common_rich_text_pub_page.dart | 135 ++++++++---- lib/pages/dynamics/widgets/author_panel.dart | 197 ++++++++++++++++-- lib/pages/dynamics/widgets/dynamic_panel.dart | 13 +- .../dynamics/widgets/rich_node_panel.dart | 11 +- lib/pages/dynamics_create/view.dart | 151 ++++++++++---- lib/pages/dynamics_detail/controller.dart | 31 +++ lib/pages/dynamics_detail/view.dart | 157 +++++++++++++- lib/pages/dynamics_repost/view.dart | 9 +- lib/pages/video/reply_new/view.dart | 9 +- 16 files changed, 826 insertions(+), 131 deletions(-) create mode 100644 lib/models_new/reply_interaction/data.dart create mode 100644 lib/models_new/reply_interaction/interact_status.dart diff --git a/README.md b/README.md index 6a72cc075..c7c081022 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ ## feat +- [x] 编辑动态 - [x] DLNA 投屏 - [x] 离线缓存/播放 - [x] 移动端支持点击弹幕悬停,点赞、复制、举报 by [@My-Responsitories](https://github.com/My-Responsitories) diff --git a/lib/http/api.dart b/lib/http/api.dart index 75c573352..ed6d1d14b 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -986,4 +986,14 @@ abstract final class Api { '${HttpString.liveBaseUrl}/av/v1/SuperChat/report'; static const String imMsgReport = '${HttpString.tUrl}/x/bplus/im/report/add'; + + static const String dynPrivatePubSetting = + '/x/dynamic/feed/dyn/private_pub_setting'; + + static const String editDyn = '/x/dynamic/feed/edit/dyn'; + + static const String replyInteraction = + '/x/v2/reply/subject/interaction-status'; + + static const String replySubjectModify = '/x/v2/reply/subject/modify'; } diff --git a/lib/http/dynamics.dart b/lib/http/dynamics.dart index 92a8e0ab9..a54fe50a3 100644 --- a/lib/http/dynamics.dart +++ b/lib/http/dynamics.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:PiliPlus/common/widgets/pair.dart'; import 'package:PiliPlus/http/api.dart'; import 'package:PiliPlus/http/constants.dart'; @@ -151,7 +153,7 @@ abstract final class DynamicsHttp { } } - static Future createDynamic({ + static Future> createDynamic({ dynamic mid, dynamic dynIdStr, // repost dyn dynamic rid, // repost video @@ -231,15 +233,9 @@ abstract final class DynamicsHttp { }, ); if (res.data['code'] == 0) { - return { - 'status': true, - 'data': res.data['data'], - }; + return Success(res.data['data']); } else { - return { - 'status': false, - 'msg': res.data['message'], - }; + return Error(res.data['message']); } } @@ -675,4 +671,110 @@ abstract final class DynamicsHttp { return Error(res.data['message']); } } + + static Future> dynPrivatePubSetting({ + required Object dynId, + int? dynType, + required String action, + }) async { + final res = await Request().post( + Api.dynPrivatePubSetting, + queryParameters: { + 'platform': 'web', + 'csrf': Accounts.main.csrf, + }, + data: { + "object_id": jsonEncode({ + "dyn_id": dynId.toString(), + "dyn_type": ?dynType, + }), + "action": action, + }, + options: Options(contentType: Headers.jsonContentType), + ); + if (res.data['code'] == 0) { + return const Success(null); + } else { + return Error(res.data['message']); + } + } + + static Future> editDyn({ + required Object dynId, + Object? repostDynId, + dynamic rawText, + List? pics, + ReplyOptionType? replyOption, + int? privatePub, + List>? extraContent, + Pair? topic, + String? title, + Map? attachCard, + }) async { + final uploadId = + "${Accounts.main.mid}_${DateTime.now().millisecondsSinceEpoch ~/ 1000}_${Utils.random.nextInt(9000) + 1000}"; + final res = await Request().post( + Api.editDyn, + queryParameters: await WbiSign.makSign({ + 'platform': 'web', + 'csrf': Accounts.main.csrf, + 'x-bili-device-req-json': + '{"platform":"web","device":"pc","spmid":"333.1368"}', + 'w_dyn_req.upload_id': uploadId, + 'w_dyn_req.meta': + '{"app_meta":{"from":"create.dynamic.web","mobi_app":"web"}}', + }), + data: { + "dyn_req": { + "content": { + "contents": [ + if (rawText != null) + { + "raw_text": rawText, + "type": 1, + "biz_id": "", + }, + ...?extraContent, + ], + if (title != null && title.isNotEmpty) 'title': title, + }, + if (privatePub != null || replyOption != null) + "option": { + 'private_pub': ?privatePub, + if (replyOption == ReplyOptionType.close) + "close_comment": 1 + else if (replyOption == ReplyOptionType.choose) + "up_choose_comment": 1, + }, + "scene": repostDynId != null + ? 4 + : pics != null + ? 2 + : 1, + 'pics': ?pics, + "attach_card": attachCard, + "upload_id": uploadId, + "meta": { + "app_meta": {"from": "create.dynamic.web", "mobi_app": "web"}, + }, + if (topic != null) + "topic": { + "id": topic.first, + "name": topic.second, + "from_source": "dyn.web.list", + "from_topic_id": 0, + }, + }, + "dyn_id_str": dynId.toString(), + if (repostDynId != null) + "web_repost_src": {"dyn_id_str": repostDynId.toString()}, + }, + options: Options(contentType: Headers.jsonContentType), + ); + if (res.data['code'] == 0) { + return const Success(null); + } else { + return Error(res.data['message']); + } + } } diff --git a/lib/http/reply.dart b/lib/http/reply.dart index 508522aa0..44c0b38fb 100644 --- a/lib/http/reply.dart +++ b/lib/http/reply.dart @@ -6,9 +6,11 @@ import 'package:PiliPlus/models_new/emote/data.dart'; import 'package:PiliPlus/models_new/emote/package.dart'; import 'package:PiliPlus/models_new/reply/data.dart'; import 'package:PiliPlus/models_new/reply2reply/data.dart'; +import 'package:PiliPlus/models_new/reply_interaction/data.dart'; import 'package:PiliPlus/utils/accounts.dart'; import 'package:PiliPlus/utils/accounts/account.dart'; import 'package:dio/dio.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; abstract final class ReplyHttp { static final Options options = Options( @@ -206,4 +208,53 @@ abstract final class ReplyHttp { return Error(res.data['message']); } } + + static Future> replyInteraction({ + required Object oid, + required Object type, + }) async { + final res = await Request().get( + Api.replyInteraction, + queryParameters: { + 'oid': oid, + 'type': type, + 'web_location': 333.1369, + }, + ); + if (res.data['code'] == 0) { + try { + return Success(ReplyInteractData.fromJson(res.data['data'])); + } catch (e) { + return Error(e.toString()); + } + } else { + return Error(res.data['message']); + } + } + + static Future> replySubjectModify({ + required int oid, + required int type, + required int action, + }) async { + final res = await Request().post( + Api.replySubjectModify, + data: { + 'oid': oid, + 'type': type, + 'action': action, + 'csrf': Accounts.main.csrf, + }, + options: Options(contentType: Headers.formUrlEncodedContentType), + ); + if (res.data['code'] == 0) { + if (res.data['data']?['action_toast'] case final String toast) { + SmartDialog.showToast(toast); + } + return const Success(null); + } else { + SmartDialog.showToast(res.data['message'].toString()); + return const Error(null); + } + } } diff --git a/lib/models/dynamics/result.dart b/lib/models/dynamics/result.dart index 05a69529e..ee2f460f9 100644 --- a/lib/models/dynamics/result.dart +++ b/lib/models/dynamics/result.dart @@ -410,6 +410,8 @@ class ModuleAuthorModel extends Avatar { int? pubTs; String? type; Decorate? decorate; + bool? isTop; + String? badgeText; ModuleAuthorModel.fromJson(Map json) : super.fromJson(json) { if (json['official'] != null) { @@ -426,6 +428,8 @@ class ModuleAuthorModel extends Avatar { } else { pendant = null; } + isTop = json['is_top']; + badgeText = _parseString(json['icon_badge']?['text']); } } @@ -1186,12 +1190,23 @@ class DynamicNoneModel { } } -class OpusPicModel { +sealed class PicModel {} + +class FilePicModel extends PicModel { + String path; + + FilePicModel({ + required this.path, + }); +} + +class OpusPicModel extends PicModel { OpusPicModel({ this.width, this.height, this.src, this.url, + this.size, }); int? width; @@ -1199,6 +1214,7 @@ class OpusPicModel { String? src; String? url; String? liveUrl; + num? size; OpusPicModel.fromJson(Map json) { width = Utils.safeToInt(json['width']); @@ -1206,7 +1222,15 @@ class OpusPicModel { src = json['src']; url = json['url']; liveUrl = json['live_url']; + size = json['size']; } + + Map toJson() => { + 'img_width': width, + 'img_height': height, + 'img_size': size, + 'img_src': url, + }; } class DynamicLiveModel { @@ -1269,7 +1293,7 @@ class ModuleTag { String? text; ModuleTag.fromJson(Map json) { - text = json['text']; + text = _parseString(json['text']); } } diff --git a/lib/models_new/reply_interaction/data.dart b/lib/models_new/reply_interaction/data.dart new file mode 100644 index 000000000..286012625 --- /dev/null +++ b/lib/models_new/reply_interaction/data.dart @@ -0,0 +1,20 @@ +import 'package:PiliPlus/models_new/reply_interaction/interact_status.dart'; + +class ReplyInteractData { + InteractStatus upReplySelection; + InteractStatus upReply; + // InteractStatus upDm; + + ReplyInteractData({ + required this.upReplySelection, + required this.upReply, + // required this.upDm, + }); + + factory ReplyInteractData.fromJson(Map json) => + ReplyInteractData( + upReplySelection: InteractStatus.fromJson(json["up_reply_selection"]), + upReply: InteractStatus.fromJson(json["up_reply"]), + // upDm: InteractStatus.fromJson(json["up_dm"]), + ); +} diff --git a/lib/models_new/reply_interaction/interact_status.dart b/lib/models_new/reply_interaction/interact_status.dart new file mode 100644 index 000000000..eac98363d --- /dev/null +++ b/lib/models_new/reply_interaction/interact_status.dart @@ -0,0 +1,14 @@ +class InteractStatus { + bool canModify; + int status; + + InteractStatus({ + required this.canModify, + required this.status, + }); + + factory InteractStatus.fromJson(Map json) => InteractStatus( + canModify: json["can_modify"], + status: json["status"], + ); +} diff --git a/lib/pages/common/publish/common_rich_text_pub_page.dart b/lib/pages/common/publish/common_rich_text_pub_page.dart index 2e0176785..51934e95f 100644 --- a/lib/pages/common/publish/common_rich_text_pub_page.dart +++ b/lib/pages/common/publish/common_rich_text_pub_page.dart @@ -7,20 +7,28 @@ import 'package:PiliPlus/common/widgets/flutter/text_field/text_field.dart'; import 'package:PiliPlus/http/msg.dart'; import 'package:PiliPlus/models/common/image_preview_type.dart'; import 'package:PiliPlus/models/common/publish_panel_type.dart'; +import 'package:PiliPlus/models/dynamics/result.dart' + show PicModel, FilePicModel, OpusPicModel; import 'package:PiliPlus/models_new/dynamic/dyn_mention/item.dart'; import 'package:PiliPlus/models_new/emote/emote.dart' as e; import 'package:PiliPlus/models_new/live/live_emote/emoticon.dart'; import 'package:PiliPlus/pages/common/publish/common_publish_page.dart'; import 'package:PiliPlus/pages/dynamics_mention/view.dart'; import 'package:PiliPlus/utils/extension/file_ext.dart'; +import 'package:PiliPlus/utils/extension/num_ext.dart'; +import 'package:PiliPlus/utils/extension/string_ext.dart'; import 'package:PiliPlus/utils/extension/theme_ext.dart'; import 'package:PiliPlus/utils/feed_back.dart'; +import 'package:PiliPlus/utils/image_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; +import 'package:cached_network_image/cached_network_image.dart'; import 'package:dio/dio.dart' show CancelToken; import 'package:easy_debounce/easy_throttle.dart'; +import 'package:flutter/foundation.dart' show kDebugMode; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:image_cropper/image_cropper.dart'; @@ -31,19 +39,21 @@ abstract class CommonRichTextPubPage const CommonRichTextPubPage({ super.key, this.items, + this.pics, super.onSave, super.autofocus, super.imageLengthLimit, }); final List? items; + final List? pics; } abstract class CommonRichTextPubPageState extends CommonPublishPageState { final key = GlobalKey(); late final imagePicker = ImagePicker(); - late final RxList pathList = [].obs; + late final RxList imageList; int get limit => widget.imageLengthLimit ?? 9; @override @@ -58,13 +68,16 @@ abstract class CommonRichTextPubPageState if (editController.rawText.trim().isNotEmpty) { enablePublish.value = true; } + imageList = RxList(widget.pics ?? []); } @override void dispose() { if (PlatformUtils.isMobile) { - for (final i in pathList) { - File(i).tryDel(); + for (final img in imageList) { + if (img is FilePicModel) { + File(img.path).tryDel(); + } } } super.dispose(); @@ -82,15 +95,18 @@ abstract class CommonRichTextPubPageState ).colorScheme.secondaryContainer.withValues(alpha: 0.5); void onClear() { - final path = pathList.removeAt(index); + final image = imageList.removeAt(index); if (PlatformUtils.isMobile) { - File(path).tryDel(); + if (image is FilePicModel) { + File(image.path).tryDel(); + } } - if (pathList.isEmpty && editController.rawText.trim().isEmpty) { + if (imageList.isEmpty && editController.rawText.trim().isEmpty) { enablePublish.value = false; } } + final image = imageList[index]; return Stack( clipBehavior: Clip.none, children: [ @@ -98,12 +114,18 @@ abstract class CommonRichTextPubPageState onTap: () async { controller.keepChatPanel(); await PageUtils.imageView( - imgList: pathList + imgList: imageList .map( - (path) => SourceModel( - url: path, - sourceType: SourceType.fileImage, - ), + (img) => switch (img) { + FilePicModel e => SourceModel( + url: e.path, + sourceType: .fileImage, + ), + OpusPicModel e => SourceModel( + url: e.url!, + sourceType: .networkImage, + ), + }, ) .toList(), initialPage: index, @@ -117,21 +139,35 @@ abstract class CommonRichTextPubPageState onSecondaryTap: PlatformUtils.isMobile ? null : onClear, child: ClipRRect( borderRadius: const BorderRadius.all(Radius.circular(4)), - child: Image( - height: height, - fit: BoxFit.fitHeight, - filterQuality: FilterQuality.low, - image: FileImage(File(pathList[index])), + child: ConstrainedBox( + constraints: const BoxConstraints(minWidth: 42), + child: switch (image) { + FilePicModel e => Image.file( + File(e.path), + height: height, + filterQuality: .low, + cacheHeight: height.cacheSize(context), + ), + OpusPicModel e => CachedNetworkImage( + imageUrl: ImageUtils.thumbnailUrl(e.url!), + height: height, + filterQuality: .low, + memCacheHeight: height.cacheSize(context), + fadeInDuration: .zero, + fadeOutDuration: .zero, + placeholder: (_, _) => const SizedBox(width: 42), + ), + }, ), ), ), - if (PlatformUtils.isMobile) + if (kDebugMode || PlatformUtils.isMobile) Positioned( top: 34, right: 5, child: iconButton( icon: const Icon(Icons.edit), - onPressed: () => onCropImage(index), + onPressed: () => onCropImage(index, image), size: 24, iconSize: 14, bgColor: color, @@ -152,11 +188,23 @@ abstract class CommonRichTextPubPageState ); } - Future onCropImage(int index) async { + Future onCropImage(int index, PicModel image) async { + String? path; + switch (image) { + case FilePicModel e: + path = e.path; + case OpusPicModel e: + SmartDialog.showLoading(); + final file = (await DefaultCacheManager().getSingleFile( + e.url.http2https, + )); + await SmartDialog.dismiss(); + path = file.path; + } + if (!mounted || path.isEmpty) return; late final colorScheme = ColorScheme.of(context); - final sourcePath = pathList[index]; final croppedFile = await ImageCropper.platform.cropImage( - sourcePath: sourcePath, + sourcePath: path, uiSettings: [ AndroidUiSettings( toolbarTitle: '裁剪', @@ -168,8 +216,10 @@ abstract class CommonRichTextPubPageState ], ); if (croppedFile != null) { - File(sourcePath).tryDel(); - pathList[index] = croppedFile.path; + if (image is FilePicModel) { + File(image.path).tryDel(); + } + imageList[index] = FilePicModel(path: croppedFile.path); } } @@ -185,11 +235,11 @@ abstract class CommonRichTextPubPageState ); if (pickedFiles.isNotEmpty) { for (int i = 0; i < pickedFiles.length; i++) { - if (pathList.length == limit) { + if (imageList.length == limit) { SmartDialog.showToast('最多选择$limit张图片'); break; } else { - pathList.add(pickedFiles[i].path); + imageList.add(FilePicModel(path: pickedFiles[i].path)); } } callback?.call(); @@ -465,25 +515,30 @@ abstract class CommonRichTextPubPageState Future onPublish() async { feedBack(); List>? pictures; - if (pathList.isNotEmpty) { + if (imageList.isNotEmpty) { SmartDialog.showLoading(msg: '正在上传图片...'); final cancelToken = CancelToken(); try { pictures = await Future.wait>( - pathList.map((path) async { - final result = await MsgHttp.uploadBfs( - path: path, - category: 'daily', - biz: 'new_dyn', - cancelToken: cancelToken, - ); - final data = result.data; - return { - 'img_width': data.imageWidth, - 'img_height': data.imageHeight, - 'img_size': data.imgSize, - 'img_src': data.imageUrl, - }; + imageList.map((img) async { + switch (img) { + case FilePicModel e: + final result = await MsgHttp.uploadBfs( + path: e.path, + category: 'daily', + biz: 'new_dyn', + cancelToken: cancelToken, + ); + final data = result.data; + return { + 'img_width': data.imageWidth, + 'img_height': data.imageHeight, + 'img_size': data.imgSize, + 'img_src': data.imageUrl, + }; + case OpusPicModel e: + return e.toJson(); + } }), eagerError: true, ); diff --git a/lib/pages/dynamics/widgets/author_panel.dart b/lib/pages/dynamics/widgets/author_panel.dart index 4dbc6f15b..8f16e3c6b 100644 --- a/lib/pages/dynamics/widgets/author_panel.dart +++ b/lib/pages/dynamics/widgets/author_panel.dart @@ -5,6 +5,8 @@ import 'package:PiliPlus/common/widgets/dialog/report.dart'; import 'package:PiliPlus/common/widgets/flutter/dyn/ink_well.dart'; import 'package:PiliPlus/common/widgets/pendant_avatar.dart'; import 'package:PiliPlus/http/constants.dart'; +import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/http/reply.dart'; import 'package:PiliPlus/http/user.dart'; import 'package:PiliPlus/http/video.dart'; import 'package:PiliPlus/models/dynamics/result.dart'; @@ -28,22 +30,27 @@ import 'package:get/get.dart'; class AuthorPanel extends StatelessWidget { final DynamicItemModel item; - final Function? addBannedList; final bool isSave; final bool isDetail; - final ValueChanged? onRemove; + final ValueChanged? onRemove; final void Function(bool isTop, Object dynId)? onSetTop; final VoidCallback? onBlock; + final Future Function(bool isPrivate, Object dynId)? + onSetPubSetting; + final VoidCallback? onEdit; + final ValueChanged? onSetReplySubject; const AuthorPanel({ super.key, required this.item, - this.addBannedList, this.isDetail = false, this.onRemove, this.isSave = false, this.onSetTop, this.onBlock, + this.onSetPubSetting, + this.onEdit, + this.onSetReplySubject, }); Widget _buildAvatar(ModuleAuthorModel moduleAuthor) { @@ -73,6 +80,33 @@ class AuthorPanel extends StatelessWidget { ) : DateFormatUtils.dateFormat(moduleAuthor.pubTs) : moduleAuthor.pubTime; + Widget? pubTs; + if (pubTime != null) { + pubTs = Text( + '$pubTime${moduleAuthor.pubAction != null ? ' ${moduleAuthor.pubAction}' : ''}', + style: TextStyle( + color: theme.colorScheme.outline, + fontSize: theme.textTheme.labelSmall!.fontSize, + ), + ); + if (moduleAuthor.badgeText case final badgeText?) { + pubTs = Row( + mainAxisSize: .min, + spacing: 5, + children: [ + pubTs, + Text( + badgeText, + style: TextStyle( + color: theme.colorScheme.secondary, + fontSize: theme.textTheme.labelSmall!.fontSize, + ), + ), + ], + ); + } + } + final moduleTagText = !isDetail ? item.modules.moduleTag?.text : null; return Stack( clipBehavior: Clip.none, alignment: Alignment.center, @@ -88,10 +122,10 @@ class AuthorPanel extends StatelessWidget { } : null, child: Row( + spacing: 10, mainAxisSize: MainAxisSize.min, children: [ _buildAvatar(moduleAuthor), - const SizedBox(width: 10), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -107,14 +141,7 @@ class AuthorPanel extends StatelessWidget { fontSize: theme.textTheme.titleSmall!.fontSize, ), ), - if (pubTime != null) - Text( - '$pubTime${moduleAuthor.pubAction != null ? ' ${moduleAuthor.pubAction}' : ''}', - style: TextStyle( - color: theme.colorScheme.outline, - fontSize: theme.textTheme.labelSmall!.fontSize, - ), - ), + ?pubTs, ], ), ], @@ -123,7 +150,7 @@ class AuthorPanel extends StatelessWidget { ), Align( alignment: Alignment.centerRight, - child: !isDetail && item.modules.moduleTag?.text != null + child: moduleTagText != null ? Row( mainAxisSize: MainAxisSize.min, children: [ @@ -142,7 +169,7 @@ class AuthorPanel extends StatelessWidget { ), ), child: Text( - item.modules.moduleTag!.text!, + moduleTagText, style: TextStyle( height: 1, fontSize: 12, @@ -399,18 +426,150 @@ class AuthorPanel extends StatelessWidget { ListTile( onTap: () { Get.back(); - onSetTop!( - item.modules.moduleTag?.text != null, - item.idStr, - ); + onSetTop!(moduleAuthor.isTop ?? false, item.idStr); }, minLeadingWidth: 0, leading: const Icon(Icons.vertical_align_top, size: 19), title: Text( - '${item.modules.moduleTag?.text != null ? '取消' : ''}置顶', + '${moduleAuthor.isTop == true ? '取消' : ''}置顶', style: theme.textTheme.titleSmall!, ), ), + if (onSetReplySubject != null) + ListTile( + onTap: () async { + Get.back(); + final res = await ReplyHttp.replyInteraction( + oid: item.basic!.commentIdStr!, + type: item.basic!.commentType!, + ); + if (res case Success(:final response)) { + if (context.mounted) { + showDialog( + context: context, + builder: (context) { + final selection = response.upReplySelection; + final enableSelection = selection.status == 1; + + final reply = response.upReply; + final enableReply = reply.status == 1; + + return AlertDialog( + clipBehavior: .hardEdge, + contentPadding: const .symmetric(vertical: 12), + content: Column( + mainAxisSize: .min, + crossAxisAlignment: .start, + children: [ + ListTile( + dense: true, + enabled: selection.canModify, + title: Text( + '${enableSelection ? '停止' : '开启'}评论精选', + style: const TextStyle(fontSize: 14), + ), + onTap: () { + Get.back(); + onSetReplySubject!( + enableSelection ? 2 : 1, + ); + }, + ), + ListTile( + dense: true, + enabled: reply.canModify, + title: Text( + '${enableReply ? '关闭' : '恢复'}评论', + style: const TextStyle(fontSize: 14), + ), + onTap: () { + Get.back(); + onSetReplySubject!(enableReply ? 3 : 4); + }, + ), + ], + ), + ); + }, + ); + } + } else { + res.toast(); + } + }, + minLeadingWidth: 0, + leading: const Icon( + Icons.mark_unread_chat_alt_outlined, + size: 19, + ), + title: Text( + '互动设置', + style: theme.textTheme.titleSmall!, + ), + ), + if (onSetPubSetting != null) + ListTile( + onTap: () { + Get.back(); + + final isPrivate = moduleAuthor.badgeText != null; + Future onTap() async { + Get.back(); + if ((await onSetPubSetting!( + isPrivate, + item.idStr, + )).isSuccess) { + if (context.mounted) { + (context as Element).markNeedsBuild(); + } + } + } + + showDialog( + context: context, + builder: (context) => AlertDialog( + clipBehavior: Clip.hardEdge, + contentPadding: const .symmetric(vertical: 12), + content: Column( + mainAxisSize: .min, + children: [ + ListTile( + dense: true, + enabled: isPrivate, + title: const Text( + '所有用户可见', + style: TextStyle(fontSize: 14), + ), + onTap: onTap, + ), + ListTile( + dense: true, + enabled: !isPrivate, + title: const Text( + '仅自己可见', + style: TextStyle(fontSize: 14), + ), + onTap: onTap, + ), + ], + ), + ), + ); + }, + minLeadingWidth: 0, + leading: const Icon(Icons.visibility, size: 19), + title: Text('可见范围', style: theme.textTheme.titleSmall!), + ), + if (onEdit != null) + ListTile( + onTap: () { + Get.back(); + onEdit!(); + }, + minLeadingWidth: 0, + leading: const Icon(Icons.edit_note, size: 19), + title: Text('编辑动态', style: theme.textTheme.titleSmall!), + ), if (onRemove != null) ListTile( onTap: () { diff --git a/lib/pages/dynamics/widgets/dynamic_panel.dart b/lib/pages/dynamics/widgets/dynamic_panel.dart index ee1189b37..5b309bc09 100644 --- a/lib/pages/dynamics/widgets/dynamic_panel.dart +++ b/lib/pages/dynamics/widgets/dynamic_panel.dart @@ -1,6 +1,7 @@ import 'package:PiliPlus/common/widgets/avatars.dart'; import 'package:PiliPlus/common/widgets/flutter/dyn/ink_well.dart'; import 'package:PiliPlus/common/widgets/image/image_save.dart'; +import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/dynamics/result.dart'; import 'package:PiliPlus/pages/dynamics/widgets/action_panel.dart'; import 'package:PiliPlus/pages/dynamics/widgets/author_panel.dart'; @@ -15,12 +16,16 @@ class DynamicPanel extends StatelessWidget { final DynamicItemModel item; final double maxWidth; final bool isDetail; - final ValueChanged? onRemove; + final ValueChanged? onRemove; final bool isSave; final void Function(bool isTop, Object dynId)? onSetTop; final VoidCallback? onBlock; final VoidCallback? onUnfold; final bool isDetailPortraitW; + final Future Function(bool isPrivate, Object dynId)? + onSetPubSetting; + final VoidCallback? onEdit; + final ValueChanged? onSetReplySubject; const DynamicPanel({ super.key, @@ -33,6 +38,9 @@ class DynamicPanel extends StatelessWidget { this.onBlock, this.onUnfold, this.isDetailPortraitW = true, + this.onSetPubSetting, + this.onEdit, + this.onSetReplySubject, }); @override @@ -48,6 +56,9 @@ class DynamicPanel extends StatelessWidget { isSave: isSave, onSetTop: onSetTop, onBlock: onBlock, + onSetPubSetting: onSetPubSetting, + onEdit: onEdit, + onSetReplySubject: onSetReplySubject, ); void showMore() => _imageSaveDialog(context, authorWidget.morePanel); diff --git a/lib/pages/dynamics/widgets/rich_node_panel.dart b/lib/pages/dynamics/widgets/rich_node_panel.dart index 58b2fb444..307f053cf 100644 --- a/lib/pages/dynamics/widgets/rich_node_panel.dart +++ b/lib/pages/dynamics/widgets/rich_node_panel.dart @@ -35,10 +35,13 @@ TextSpan? richNode( if (richTextNodes == null || richTextNodes.isEmpty) { return TextSpan(text: desc.text); } - } else if (moduleDynamic?.major?.opus case final opus?) { + } else if (moduleDynamic?.major?.opus case DynamicOpusModel( + :final title, + :final summary, + )) { // 动态页面 richTextNodes 层级可能与主页动态层级不同 - richTextNodes = opus.summary?.richTextNodes; - if (opus.title case final title?) { + richTextNodes = summary?.richTextNodes; + if (title != null && title.isNotEmpty) { spanChildren.add( TextSpan( text: '$title\n', @@ -76,7 +79,7 @@ TextSpan? richNode( case 'RICH_TEXT_NODE_TYPE_TOPIC': spanChildren.add( TextSpan( - text: i.origText!, + text: i.origText, style: style, recognizer: TapGestureRecognizer() ..onTap = () => Get.toNamed( diff --git a/lib/pages/dynamics_create/view.dart b/lib/pages/dynamics_create/view.dart index 20626936b..bc5ec31ee 100644 --- a/lib/pages/dynamics_create/view.dart +++ b/lib/pages/dynamics_create/view.dart @@ -10,8 +10,10 @@ import 'package:PiliPlus/common/widgets/flutter/text_field/controller.dart'; import 'package:PiliPlus/common/widgets/flutter/text_field/text_field.dart'; import 'package:PiliPlus/common/widgets/pair.dart'; import 'package:PiliPlus/http/dynamics.dart'; +import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/common/publish_panel_type.dart'; import 'package:PiliPlus/models/common/reply/reply_option_type.dart'; +import 'package:PiliPlus/models/dynamics/result.dart' show PicModel; import 'package:PiliPlus/models/dynamics/vote_model.dart'; import 'package:PiliPlus/models_new/dynamic/dyn_reserve_info/data.dart'; import 'package:PiliPlus/models_new/dynamic/dyn_topic_top/topic_item.dart'; @@ -29,7 +31,6 @@ import 'package:PiliPlus/utils/extension/context_ext.dart'; import 'package:PiliPlus/utils/extension/iterable_ext.dart'; import 'package:PiliPlus/utils/grid.dart'; import 'package:PiliPlus/utils/request_utils.dart'; -import 'package:flutter/foundation.dart' show kDebugMode; import 'package:flutter/material.dart' hide DraggableScrollableSheet; import 'package:flutter/services.dart' show LengthLimitingTextInputFormatter; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; @@ -39,48 +40,81 @@ class CreateDynPanel extends CommonRichTextPubPage { const CreateDynPanel({ super.key, super.imageLengthLimit = 18, + super.items, + super.pics, this.scrollController, this.topic, + this.editConfig, + this.title, + this.isPrivate = false, + this.replyOption = .allow, + this.onSuccess, }); final ScrollController? scrollController; + final String? title; final Pair? topic; + final bool isPrivate; + final ReplyOptionType replyOption; + final ({Object dynId, Object? repostDynId})? editConfig; + final VoidCallback? onSuccess; @override State createState() => _CreateDynPanelState(); - static void onCreateDyn(BuildContext context, {Pair? topic}) => - showModalBottomSheet( - context: context, - useSafeArea: true, - isScrollControlled: true, - builder: (context) => dyn_sheet.DraggableScrollableSheet( - snap: true, - expand: false, - initialChildSize: 1, - minChildSize: 0, - maxChildSize: 1, - snapSizes: const [1], - builder: (context, scrollController) => CreateDynPanel( - scrollController: scrollController, - topic: topic, - ), - ), - ); + static void onCreateDyn( + BuildContext context, { + String? title, + bool isPrivate = false, + ReplyOptionType replyOption = .allow, + List? items, + List? pics, + Pair? topic, + ({Object dynId, Object? repostDynId})? editConfig, + VoidCallback? onSuccess, + }) => showModalBottomSheet( + context: context, + useSafeArea: true, + isScrollControlled: true, + builder: (context) => dyn_sheet.DraggableScrollableSheet( + snap: true, + expand: false, + initialChildSize: 1, + minChildSize: 0, + maxChildSize: 1, + snapSizes: const [1], + builder: (context, scrollController) => CreateDynPanel( + scrollController: scrollController, + title: title, + items: items, + pics: pics, + topic: topic, + isPrivate: isPrivate, + editConfig: editConfig, + replyOption: replyOption, + onSuccess: onSuccess, + ), + ), + ); } class _CreateDynPanelState extends CommonRichTextPubPageState { - final RxBool _isPrivate = false.obs; - final Rx _publishTime = Rx(null); - final Rx _replyOption = ReplyOptionType.allow.obs; - final _titleEditCtr = TextEditingController(); - final Rx?> topic = Rx?>(null); + late final bool _isEdit; + late final RxBool _isPrivate; + late final Rx?> _topic; + late final Rx _replyOption; + late final TextEditingController _titleEditCtr; + late final Rx _publishTime = Rx(null); final Rx _reserveCard = Rx(null); @override void initState() { super.initState(); - topic.value = widget.topic; + _isEdit = widget.editConfig != null; + _isPrivate = widget.isPrivate.obs; + _replyOption = widget.replyOption.obs; + _topic = Rx?>(widget.topic); + _titleEditCtr = TextEditingController(text: widget.title); } @override @@ -110,7 +144,7 @@ class _CreateDynPanelState extends CommonRichTextPubPageState { padding: const EdgeInsets.symmetric(horizontal: 16), child: Obx( () { - final hasTopic = topic.value != null; + final hasTopic = _topic.value != null; return Row( spacing: 10, children: [ @@ -158,7 +192,9 @@ class _CreateDynPanelState extends CommonRichTextPubPageState { ), ), TextSpan( - text: hasTopic ? topic.value!.second : '选择话题', + text: hasTopic + ? _topic.value!.second + : '选择话题', style: TextStyle( color: hasTopic ? null @@ -176,7 +212,7 @@ class _CreateDynPanelState extends CommonRichTextPubPageState { icon: const Icon(Icons.clear), bgColor: theme.colorScheme.onInverseSurface, iconColor: theme.colorScheme.onSurfaceVariant, - onPressed: () => topic.value = null, + onPressed: () => _topic.value = null, ), ], ); @@ -253,10 +289,10 @@ class _CreateDynPanelState extends CommonRichTextPubPageState { crossAxisAlignment: CrossAxisAlignment.start, children: [ ...List.generate( - pathList.length, + imageList.length, (index) => buildImage(index, 100), ), - if (pathList.length != limit) + if (imageList.length != limit) Builder( builder: (context) { const borderRadius = StyleString.mdRadius; @@ -265,7 +301,7 @@ class _CreateDynPanelState extends CommonRichTextPubPageState { child: InkWell( borderRadius: borderRadius, onTap: () => onPickImage(() { - if (pathList.isNotEmpty && !enablePublish.value) { + if (imageList.isNotEmpty && !enablePublish.value) { enablePublish.value = true; } }), @@ -316,10 +352,10 @@ class _CreateDynPanelState extends CommonRichTextPubPageState { ), ), ), - const Center( + Center( child: Text( - '发布动态', - style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold), + _isEdit ? '编辑动态' : '发布动态', + style: const TextStyle(fontSize: 15, fontWeight: FontWeight.bold), ), ), Align( @@ -464,7 +500,7 @@ class _CreateDynPanelState extends CommonRichTextPubPageState { ), visualDensity: VisualDensity.compact, ), - onPressed: _isPrivate.value + onPressed: _isEdit || _isPrivate.value ? null : () async { controller.keepChatPanel(); @@ -538,8 +574,10 @@ class _CreateDynPanelState extends CommonRichTextPubPageState { children: [ emojiBtn, atBtn, - voteBtn, - moreBtn, + if (!_isEdit) ...[ + voteBtn, + moreBtn, + ], // if (kDebugMode) // ToolbarIconButton( // onPressed: editController.clear, @@ -707,8 +745,34 @@ class _CreateDynPanelState extends CommonRichTextPubPageState { SmartDialog.showLoading(msg: '正在发布'); List>? extraContent = getRichContent(); final hasRichText = extraContent != null; + + if (_isEdit) { + final editConfig = widget.editConfig!; + final res = await DynamicsHttp.editDyn( + dynId: editConfig.dynId, + repostDynId: editConfig.repostDynId, + rawText: hasRichText ? null : editController.text, + pics: pictures, + replyOption: _replyOption.value, + privatePub: _isPrivate.value ? 1 : null, + title: _titleEditCtr.text, + topic: _topic.value, + extraContent: extraContent, + ); + SmartDialog.dismiss(); + if (res.isSuccess) { + hasPub = true; + Get.back(); + SmartDialog.showToast('发布成功'); + widget.onSuccess?.call(); + } else { + res.toast(); + } + return; + } + final reserveCard = _reserveCard.value; - final result = await DynamicsHttp.createDynamic( + final res = await DynamicsHttp.createDynamic( mid: Accounts.main.mid, rawText: hasRichText ? null : editController.text, pics: pictures, @@ -718,7 +782,7 @@ class _CreateDynPanelState extends CommonRichTextPubPageState { replyOption: _replyOption.value, privatePub: _isPrivate.value ? 1 : null, title: _titleEditCtr.text, - topic: topic.value, + topic: _topic.value, extraContent: extraContent, attachCard: reserveCard == null ? null @@ -732,11 +796,11 @@ class _CreateDynPanelState extends CommonRichTextPubPageState { }, ); SmartDialog.dismiss(); - if (result['status']) { + if (res case Success(:final response)) { hasPub = true; Get.back(); SmartDialog.showToast('发布成功'); - final id = result['data']?['dyn_id']; + final id = response?['dyn_id']; RequestUtils.insertCreatedDyn(id); if (!_isPrivate.value) { RequestUtils.checkCreatedDyn( @@ -745,8 +809,7 @@ class _CreateDynPanelState extends CommonRichTextPubPageState { ); } } else { - SmartDialog.showToast(result['msg']); - if (kDebugMode) debugPrint('failed to publish: ${result['msg']}'); + res.toast(); } } @@ -759,7 +822,7 @@ class _CreateDynPanelState extends CommonRichTextPubPageState { onCachePos: (offset) => _topicOffset = offset, ); if (res != null) { - topic.value = Pair(first: res.id, second: res.name); + _topic.value = Pair(first: res.id, second: res.name); } controller.restoreChatPanel(); } diff --git a/lib/pages/dynamics_detail/controller.dart b/lib/pages/dynamics_detail/controller.dart index 3737f2eb1..0d5dc6673 100644 --- a/lib/pages/dynamics_detail/controller.dart +++ b/lib/pages/dynamics_detail/controller.dart @@ -1,9 +1,11 @@ import 'package:PiliPlus/http/dynamics.dart'; import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/http/reply.dart'; import 'package:PiliPlus/models/dynamics/result.dart'; import 'package:PiliPlus/pages/common/dyn/common_dyn_controller.dart'; import 'package:PiliPlus/utils/id_utils.dart'; import 'package:PiliPlus/utils/storage_pref.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; class DynamicDetailController extends CommonDynController { @@ -45,4 +47,33 @@ class DynamicDetailController extends CommonDynController { replyType = commentType; queryData(); } + + Future onSetPubSetting(bool isPrivate, Object dynId) async { + final res = await DynamicsHttp.dynPrivatePubSetting( + dynId: dynId, + action: isPrivate ? 'public_pub' : 'private_pub', + ); + if (res.isSuccess) { + dynItem.modules.moduleAuthor?.badgeText = isPrivate ? null : '仅自己可见'; + SmartDialog.showToast('设置成功'); + } else { + res.toast(); + } + return res; + } + + Future onSetReplySubject(int action) async { + final res = await ReplyHttp.replySubjectModify( + oid: oid, + type: replyType, + action: action, + ); + if (res.isSuccess) { + await Future.delayed(const Duration(milliseconds: 500), () { + if (!isClosed) { + onReload(); + } + }); + } + } } diff --git a/lib/pages/dynamics_detail/view.dart b/lib/pages/dynamics_detail/view.dart index 1b673f1a9..b393005fd 100644 --- a/lib/pages/dynamics_detail/view.dart +++ b/lib/pages/dynamics_detail/view.dart @@ -2,11 +2,17 @@ import 'dart:math'; import 'package:PiliPlus/common/widgets/custom_icon.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; +import 'package:PiliPlus/common/widgets/flutter/text_field/controller.dart'; +import 'package:PiliPlus/common/widgets/pair.dart'; import 'package:PiliPlus/http/constants.dart'; +import 'package:PiliPlus/http/dynamics.dart'; +import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models/common/reply/reply_option_type.dart'; import 'package:PiliPlus/models/dynamics/result.dart'; import 'package:PiliPlus/pages/common/dyn/common_dyn_page.dart'; import 'package:PiliPlus/pages/dynamics/widgets/author_panel.dart'; import 'package:PiliPlus/pages/dynamics/widgets/dynamic_panel.dart'; +import 'package:PiliPlus/pages/dynamics_create/view.dart'; import 'package:PiliPlus/pages/dynamics_detail/controller.dart'; import 'package:PiliPlus/pages/dynamics_repost/view.dart'; import 'package:PiliPlus/utils/extension/get_ext.dart'; @@ -14,6 +20,7 @@ import 'package:PiliPlus/utils/grid.dart'; import 'package:PiliPlus/utils/num_utils.dart'; import 'package:PiliPlus/utils/request_utils.dart'; import 'package:PiliPlus/utils/utils.dart'; +import 'package:flutter/foundation.dart' show kDebugMode; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:get/get.dart'; @@ -66,6 +73,142 @@ class _DynamicDetailPageState extends CommonDynPageState { ); } + void _onEdit() { + final item = controller.dynItem; + List? items; + final moduleDynamic = item.modules.moduleDynamic; + final desc = moduleDynamic?.desc; + final opus = moduleDynamic?.major?.opus; + + Pair? topic; + if (moduleDynamic?.topic case final t?) { + try { + topic = Pair(first: t.id!, second: t.name!); + } catch (_) { + if (kDebugMode) rethrow; + } + } + + final richTextNodes = desc?.richTextNodes ?? opus?.summary?.richTextNodes; + if (richTextNodes != null && richTextNodes.isNotEmpty) { + items = []; + final buffer = StringBuffer(); + try { + for (final e in richTextNodes) { + if (e.type == 'RICH_TEXT_NODE_TYPE_EMOJI') { + const placeHolder = '\uFFFC'; + items.add( + RichTextItem( + text: placeHolder, + rawText: e.origText, + type: .emoji, + range: TextRange( + start: buffer.length, + end: buffer.length + placeHolder.length, + ), + emote: Emote( + url: e.emoji!.url!, + width: 22, + ), + ), + ); + buffer.write(placeHolder); + continue; + } + final range = TextRange( + start: buffer.length, + end: buffer.length + e.origText!.length, + ); + final item = switch (e.type) { + 'RICH_TEXT_NODE_TYPE_AT' => RichTextItem( + text: e.origText!, + type: .at, + range: range, + id: e.rid, + ), + 'RICH_TEXT_NODE_TYPE_BV' || + 'RICH_TEXT_NODE_TYPE_TOPIC' || + 'RICH_TEXT_NODE_TYPE_LOTTERY' || + 'RICH_TEXT_NODE_TYPE_VIEW_PICTURE' => RichTextItem( + text: e.origText!, + type: .common, + range: range, + id: e.rid, + ), + 'RICH_TEXT_NODE_TYPE_VOTE' => RichTextItem( + text: e.origText!, + type: .vote, + range: range, + id: e.rid, + ), + _ => RichTextItem( + text: e.origText!, + range: range, + ), + }; + items.add(item); + buffer.write(e.origText!); + } + + bool isValid = true; + int cursor = 0; + for (final e in items) { + final range = e.range; + if (range.start == cursor) { + cursor = range.end; + } else { + isValid = false; + break; + } + } + assert(isValid); + } catch (e) { + if (kDebugMode) rethrow; + } + } else { + final text = desc?.text ?? opus?.summary?.text; + if (text != null && text.isNotEmpty) { + items = [ + RichTextItem.fromStart(text), + ]; + } + } + ReplyOptionType? replyOption; + if (controller.loadingState.value case Error(:final code)) { + if (code == 12061 || code == 12002) { + replyOption = .close; + } + } + CreateDynPanel.onCreateDyn( + context, + title: opus?.title, + items: items, + pics: opus?.pics, + topic: topic, + replyOption: replyOption ?? .allow, + isPrivate: item.modules.moduleAuthor?.badgeText != null, + editConfig: ( + dynId: item.idStr, + repostDynId: item.orig?.idStr, + ), + onSuccess: () { + Future.delayed( + const Duration(milliseconds: 500), + () async { + if (!mounted) return; + final res = await DynamicsHttp.dynamicDetail(id: item.idStr); + if (res case Success(:final response)) { + if (mounted) { + controller.dynItem = response; + setState(() {}); + } + } + }, + ); + }, + ); + } + PreferredSizeWidget _buildAppBar() => AppBar( title: Padding( padding: const EdgeInsets.only(right: 12), @@ -80,6 +223,9 @@ class _DynamicDetailPageState extends CommonDynPageState { child: AuthorPanel( item: controller.dynItem, isDetail: true, + onSetPubSetting: controller.onSetPubSetting, + onEdit: _onEdit, + onSetReplySubject: controller.onSetReplySubject, ), ), ); @@ -88,10 +234,7 @@ class _DynamicDetailPageState extends CommonDynPageState { ), actions: isPortrait ? null - : [ - ratioWidget(maxWidth), - const SizedBox(width: 16), - ], + : [ratioWidget(maxWidth), const SizedBox(width: 16)], ); Widget _buildBody(ThemeData theme) { @@ -110,6 +253,9 @@ class _DynamicDetailPageState extends CommonDynPageState { isDetail: true, maxWidth: maxWidth - this.padding.horizontal - 2 * padding, isDetailPortraitW: isPortrait, + onSetPubSetting: controller.onSetPubSetting, + onEdit: _onEdit, + onSetReplySubject: controller.onSetReplySubject, ), ), buildReplyHeader(theme), @@ -143,6 +289,9 @@ class _DynamicDetailPageState extends CommonDynPageState { (flex / (flex + flex1)) - padding, isDetailPortraitW: isPortrait, + onSetPubSetting: controller.onSetPubSetting, + onEdit: _onEdit, + onSetReplySubject: controller.onSetReplySubject, ), ), ), diff --git a/lib/pages/dynamics_repost/view.dart b/lib/pages/dynamics_repost/view.dart index 2b1601fa8..d72dc3c2b 100644 --- a/lib/pages/dynamics_repost/view.dart +++ b/lib/pages/dynamics_repost/view.dart @@ -3,6 +3,7 @@ import 'package:PiliPlus/common/widgets/flutter/draggable_sheet/draggable_scroll import 'package:PiliPlus/common/widgets/flutter/text_field/text_field.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/http/dynamics.dart'; +import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/common/publish_panel_type.dart'; import 'package:PiliPlus/models/dynamics/result.dart'; import 'package:PiliPlus/pages/common/publish/common_rich_text_pub_page.dart'; @@ -419,7 +420,7 @@ class _RepostPanelState extends CommonRichTextPubPageState { if (hasRichText && repostContent != null) { richContent.addAll(repostContent); } - final result = await DynamicsHttp.createDynamic( + final res = await DynamicsHttp.createDynamic( mid: Accounts.main.mid, dynIdStr: widget.item?.idStr ?? widget.dynIdStr, rid: widget.rid, @@ -428,19 +429,19 @@ class _RepostPanelState extends CommonRichTextPubPageState { extraContent: richContent ?? repostContent, ); SmartDialog.dismiss(); - if (result['status']) { + if (res case Success(:final response)) { hasPub = true; Get.back(); SmartDialog.showToast('转发成功'); widget.onSuccess?.call(); - final id = result['data']?['dyn_id']; + final id = response?['dyn_id']; RequestUtils.insertCreatedDyn(id); RequestUtils.checkCreatedDyn( id: id, dynText: editController.rawText, ); } else { - SmartDialog.showToast(result['msg']); + res.toast(); } } diff --git a/lib/pages/video/reply_new/view.dart b/lib/pages/video/reply_new/view.dart index ef9c64d61..9c6058157 100644 --- a/lib/pages/video/reply_new/view.dart +++ b/lib/pages/video/reply_new/view.dart @@ -12,6 +12,7 @@ import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart' import 'package:PiliPlus/http/video.dart'; import 'package:PiliPlus/main.dart'; import 'package:PiliPlus/models/common/publish_panel_type.dart'; +import 'package:PiliPlus/models/dynamics/result.dart' show FilePicModel; import 'package:PiliPlus/pages/common/publish/common_rich_text_pub_page.dart'; import 'package:PiliPlus/pages/dynamics_mention/controller.dart'; import 'package:PiliPlus/pages/emote/view.dart'; @@ -110,7 +111,7 @@ class _ReplyPageState extends CommonRichTextPubPageState { Widget buildImagePreview() { return Obx( () { - if (pathList.isNotEmpty) { + if (imageList.isNotEmpty) { return SizedBox( height: 85, child: SingleChildScrollView( @@ -120,7 +121,7 @@ class _ReplyPageState extends CommonRichTextPubPageState { spacing: 10, crossAxisAlignment: CrossAxisAlignment.start, children: List.generate( - pathList.length, + imageList.length, (index) => buildImage(index, 75), ), ), @@ -368,7 +369,7 @@ class _ReplyPageState extends CommonRichTextPubPageState { if (isRoot && widget.canUploadPic) item( onTap: () async { - if (pathList.length >= limit) { + if (imageList.length >= limit) { SmartDialog.showToast('最多选择$limit张图片'); return; } @@ -385,7 +386,7 @@ class _ReplyPageState extends CommonRichTextPubPageState { '$tmpDirPath/${Utils.generateRandomString(8)}.png', ); await file.writeAsBytes(res); - pathList.add(file.path); + imageList.add(FilePicModel(path: file.path)); } else { debugPrint('null screenshot'); }