mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-08-06 10:40:09 +08:00
feat: edit dyn
feat: set pub setting feat: set reply interaction Signed-off-by: dom <githubaccount56556@proton.me>
This commit is contained in:
@@ -43,6 +43,7 @@
|
|||||||
|
|
||||||
## feat
|
## feat
|
||||||
|
|
||||||
|
- [x] 编辑动态
|
||||||
- [x] DLNA 投屏
|
- [x] DLNA 投屏
|
||||||
- [x] 离线缓存/播放
|
- [x] 离线缓存/播放
|
||||||
- [x] 移动端支持点击弹幕悬停,点赞、复制、举报 by [@My-Responsitories](https://github.com/My-Responsitories)
|
- [x] 移动端支持点击弹幕悬停,点赞、复制、举报 by [@My-Responsitories](https://github.com/My-Responsitories)
|
||||||
|
|||||||
@@ -986,4 +986,14 @@ abstract final class Api {
|
|||||||
'${HttpString.liveBaseUrl}/av/v1/SuperChat/report';
|
'${HttpString.liveBaseUrl}/av/v1/SuperChat/report';
|
||||||
|
|
||||||
static const String imMsgReport = '${HttpString.tUrl}/x/bplus/im/report/add';
|
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';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:PiliPlus/common/widgets/pair.dart';
|
import 'package:PiliPlus/common/widgets/pair.dart';
|
||||||
import 'package:PiliPlus/http/api.dart';
|
import 'package:PiliPlus/http/api.dart';
|
||||||
import 'package:PiliPlus/http/constants.dart';
|
import 'package:PiliPlus/http/constants.dart';
|
||||||
@@ -151,7 +153,7 @@ abstract final class DynamicsHttp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future createDynamic({
|
static Future<LoadingState<Map?>> createDynamic({
|
||||||
dynamic mid,
|
dynamic mid,
|
||||||
dynamic dynIdStr, // repost dyn
|
dynamic dynIdStr, // repost dyn
|
||||||
dynamic rid, // repost video
|
dynamic rid, // repost video
|
||||||
@@ -231,15 +233,9 @@ abstract final class DynamicsHttp {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {
|
return Success(res.data['data']);
|
||||||
'status': true,
|
|
||||||
'data': res.data['data'],
|
|
||||||
};
|
|
||||||
} else {
|
} else {
|
||||||
return {
|
return Error(res.data['message']);
|
||||||
'status': false,
|
|
||||||
'msg': res.data['message'],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -675,4 +671,110 @@ abstract final class DynamicsHttp {
|
|||||||
return Error(res.data['message']);
|
return Error(res.data['message']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<LoadingState<Null>> 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<LoadingState<Null>> editDyn({
|
||||||
|
required Object dynId,
|
||||||
|
Object? repostDynId,
|
||||||
|
dynamic rawText,
|
||||||
|
List? pics,
|
||||||
|
ReplyOptionType? replyOption,
|
||||||
|
int? privatePub,
|
||||||
|
List<Map<String, dynamic>>? extraContent,
|
||||||
|
Pair<int, String>? 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']);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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/emote/package.dart';
|
||||||
import 'package:PiliPlus/models_new/reply/data.dart';
|
import 'package:PiliPlus/models_new/reply/data.dart';
|
||||||
import 'package:PiliPlus/models_new/reply2reply/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.dart';
|
||||||
import 'package:PiliPlus/utils/accounts/account.dart';
|
import 'package:PiliPlus/utils/accounts/account.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
|
|
||||||
abstract final class ReplyHttp {
|
abstract final class ReplyHttp {
|
||||||
static final Options options = Options(
|
static final Options options = Options(
|
||||||
@@ -206,4 +208,53 @@ abstract final class ReplyHttp {
|
|||||||
return Error(res.data['message']);
|
return Error(res.data['message']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<LoadingState<ReplyInteractData>> 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<LoadingState<Null>> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -410,6 +410,8 @@ class ModuleAuthorModel extends Avatar {
|
|||||||
int? pubTs;
|
int? pubTs;
|
||||||
String? type;
|
String? type;
|
||||||
Decorate? decorate;
|
Decorate? decorate;
|
||||||
|
bool? isTop;
|
||||||
|
String? badgeText;
|
||||||
|
|
||||||
ModuleAuthorModel.fromJson(Map<String, dynamic> json) : super.fromJson(json) {
|
ModuleAuthorModel.fromJson(Map<String, dynamic> json) : super.fromJson(json) {
|
||||||
if (json['official'] != null) {
|
if (json['official'] != null) {
|
||||||
@@ -426,6 +428,8 @@ class ModuleAuthorModel extends Avatar {
|
|||||||
} else {
|
} else {
|
||||||
pendant = null;
|
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({
|
OpusPicModel({
|
||||||
this.width,
|
this.width,
|
||||||
this.height,
|
this.height,
|
||||||
this.src,
|
this.src,
|
||||||
this.url,
|
this.url,
|
||||||
|
this.size,
|
||||||
});
|
});
|
||||||
|
|
||||||
int? width;
|
int? width;
|
||||||
@@ -1199,6 +1214,7 @@ class OpusPicModel {
|
|||||||
String? src;
|
String? src;
|
||||||
String? url;
|
String? url;
|
||||||
String? liveUrl;
|
String? liveUrl;
|
||||||
|
num? size;
|
||||||
|
|
||||||
OpusPicModel.fromJson(Map<String, dynamic> json) {
|
OpusPicModel.fromJson(Map<String, dynamic> json) {
|
||||||
width = Utils.safeToInt(json['width']);
|
width = Utils.safeToInt(json['width']);
|
||||||
@@ -1206,7 +1222,15 @@ class OpusPicModel {
|
|||||||
src = json['src'];
|
src = json['src'];
|
||||||
url = json['url'];
|
url = json['url'];
|
||||||
liveUrl = json['live_url'];
|
liveUrl = json['live_url'];
|
||||||
|
size = json['size'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'img_width': width,
|
||||||
|
'img_height': height,
|
||||||
|
'img_size': size,
|
||||||
|
'img_src': url,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
class DynamicLiveModel {
|
class DynamicLiveModel {
|
||||||
@@ -1269,7 +1293,7 @@ class ModuleTag {
|
|||||||
String? text;
|
String? text;
|
||||||
|
|
||||||
ModuleTag.fromJson(Map<String, dynamic> json) {
|
ModuleTag.fromJson(Map<String, dynamic> json) {
|
||||||
text = json['text'];
|
text = _parseString(json['text']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
20
lib/models_new/reply_interaction/data.dart
Normal file
20
lib/models_new/reply_interaction/data.dart
Normal file
@@ -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<String, dynamic> json) =>
|
||||||
|
ReplyInteractData(
|
||||||
|
upReplySelection: InteractStatus.fromJson(json["up_reply_selection"]),
|
||||||
|
upReply: InteractStatus.fromJson(json["up_reply"]),
|
||||||
|
// upDm: InteractStatus.fromJson(json["up_dm"]),
|
||||||
|
);
|
||||||
|
}
|
||||||
14
lib/models_new/reply_interaction/interact_status.dart
Normal file
14
lib/models_new/reply_interaction/interact_status.dart
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
class InteractStatus {
|
||||||
|
bool canModify;
|
||||||
|
int status;
|
||||||
|
|
||||||
|
InteractStatus({
|
||||||
|
required this.canModify,
|
||||||
|
required this.status,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory InteractStatus.fromJson(Map<String, dynamic> json) => InteractStatus(
|
||||||
|
canModify: json["can_modify"],
|
||||||
|
status: json["status"],
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -7,20 +7,28 @@ import 'package:PiliPlus/common/widgets/flutter/text_field/text_field.dart';
|
|||||||
import 'package:PiliPlus/http/msg.dart';
|
import 'package:PiliPlus/http/msg.dart';
|
||||||
import 'package:PiliPlus/models/common/image_preview_type.dart';
|
import 'package:PiliPlus/models/common/image_preview_type.dart';
|
||||||
import 'package:PiliPlus/models/common/publish_panel_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/dynamic/dyn_mention/item.dart';
|
||||||
import 'package:PiliPlus/models_new/emote/emote.dart' as e;
|
import 'package:PiliPlus/models_new/emote/emote.dart' as e;
|
||||||
import 'package:PiliPlus/models_new/live/live_emote/emoticon.dart';
|
import 'package:PiliPlus/models_new/live/live_emote/emoticon.dart';
|
||||||
import 'package:PiliPlus/pages/common/publish/common_publish_page.dart';
|
import 'package:PiliPlus/pages/common/publish/common_publish_page.dart';
|
||||||
import 'package:PiliPlus/pages/dynamics_mention/view.dart';
|
import 'package:PiliPlus/pages/dynamics_mention/view.dart';
|
||||||
import 'package:PiliPlus/utils/extension/file_ext.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/extension/theme_ext.dart';
|
||||||
import 'package:PiliPlus/utils/feed_back.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/page_utils.dart';
|
||||||
import 'package:PiliPlus/utils/platform_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:dio/dio.dart' show CancelToken;
|
||||||
import 'package:easy_debounce/easy_throttle.dart';
|
import 'package:easy_debounce/easy_throttle.dart';
|
||||||
|
import 'package:flutter/foundation.dart' show kDebugMode;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.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:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:image_cropper/image_cropper.dart';
|
import 'package:image_cropper/image_cropper.dart';
|
||||||
@@ -31,19 +39,21 @@ abstract class CommonRichTextPubPage
|
|||||||
const CommonRichTextPubPage({
|
const CommonRichTextPubPage({
|
||||||
super.key,
|
super.key,
|
||||||
this.items,
|
this.items,
|
||||||
|
this.pics,
|
||||||
super.onSave,
|
super.onSave,
|
||||||
super.autofocus,
|
super.autofocus,
|
||||||
super.imageLengthLimit,
|
super.imageLengthLimit,
|
||||||
});
|
});
|
||||||
|
|
||||||
final List<RichTextItem>? items;
|
final List<RichTextItem>? items;
|
||||||
|
final List<PicModel>? pics;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class CommonRichTextPubPageState<T extends CommonRichTextPubPage>
|
abstract class CommonRichTextPubPageState<T extends CommonRichTextPubPage>
|
||||||
extends CommonPublishPageState<T> {
|
extends CommonPublishPageState<T> {
|
||||||
final key = GlobalKey<RichTextFieldState>();
|
final key = GlobalKey<RichTextFieldState>();
|
||||||
late final imagePicker = ImagePicker();
|
late final imagePicker = ImagePicker();
|
||||||
late final RxList<String> pathList = <String>[].obs;
|
late final RxList<PicModel> imageList;
|
||||||
int get limit => widget.imageLengthLimit ?? 9;
|
int get limit => widget.imageLengthLimit ?? 9;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -58,13 +68,16 @@ abstract class CommonRichTextPubPageState<T extends CommonRichTextPubPage>
|
|||||||
if (editController.rawText.trim().isNotEmpty) {
|
if (editController.rawText.trim().isNotEmpty) {
|
||||||
enablePublish.value = true;
|
enablePublish.value = true;
|
||||||
}
|
}
|
||||||
|
imageList = RxList<PicModel>(widget.pics ?? <PicModel>[]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
if (PlatformUtils.isMobile) {
|
if (PlatformUtils.isMobile) {
|
||||||
for (final i in pathList) {
|
for (final img in imageList) {
|
||||||
File(i).tryDel();
|
if (img is FilePicModel) {
|
||||||
|
File(img.path).tryDel();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
super.dispose();
|
super.dispose();
|
||||||
@@ -82,15 +95,18 @@ abstract class CommonRichTextPubPageState<T extends CommonRichTextPubPage>
|
|||||||
).colorScheme.secondaryContainer.withValues(alpha: 0.5);
|
).colorScheme.secondaryContainer.withValues(alpha: 0.5);
|
||||||
|
|
||||||
void onClear() {
|
void onClear() {
|
||||||
final path = pathList.removeAt(index);
|
final image = imageList.removeAt(index);
|
||||||
if (PlatformUtils.isMobile) {
|
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;
|
enablePublish.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final image = imageList[index];
|
||||||
return Stack(
|
return Stack(
|
||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
children: [
|
children: [
|
||||||
@@ -98,12 +114,18 @@ abstract class CommonRichTextPubPageState<T extends CommonRichTextPubPage>
|
|||||||
onTap: () async {
|
onTap: () async {
|
||||||
controller.keepChatPanel();
|
controller.keepChatPanel();
|
||||||
await PageUtils.imageView(
|
await PageUtils.imageView(
|
||||||
imgList: pathList
|
imgList: imageList
|
||||||
.map(
|
.map(
|
||||||
(path) => SourceModel(
|
(img) => switch (img) {
|
||||||
url: path,
|
FilePicModel e => SourceModel(
|
||||||
sourceType: SourceType.fileImage,
|
url: e.path,
|
||||||
),
|
sourceType: .fileImage,
|
||||||
|
),
|
||||||
|
OpusPicModel e => SourceModel(
|
||||||
|
url: e.url!,
|
||||||
|
sourceType: .networkImage,
|
||||||
|
),
|
||||||
|
},
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
initialPage: index,
|
initialPage: index,
|
||||||
@@ -117,21 +139,35 @@ abstract class CommonRichTextPubPageState<T extends CommonRichTextPubPage>
|
|||||||
onSecondaryTap: PlatformUtils.isMobile ? null : onClear,
|
onSecondaryTap: PlatformUtils.isMobile ? null : onClear,
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(4)),
|
borderRadius: const BorderRadius.all(Radius.circular(4)),
|
||||||
child: Image(
|
child: ConstrainedBox(
|
||||||
height: height,
|
constraints: const BoxConstraints(minWidth: 42),
|
||||||
fit: BoxFit.fitHeight,
|
child: switch (image) {
|
||||||
filterQuality: FilterQuality.low,
|
FilePicModel e => Image.file(
|
||||||
image: FileImage(File(pathList[index])),
|
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(
|
Positioned(
|
||||||
top: 34,
|
top: 34,
|
||||||
right: 5,
|
right: 5,
|
||||||
child: iconButton(
|
child: iconButton(
|
||||||
icon: const Icon(Icons.edit),
|
icon: const Icon(Icons.edit),
|
||||||
onPressed: () => onCropImage(index),
|
onPressed: () => onCropImage(index, image),
|
||||||
size: 24,
|
size: 24,
|
||||||
iconSize: 14,
|
iconSize: 14,
|
||||||
bgColor: color,
|
bgColor: color,
|
||||||
@@ -152,11 +188,23 @@ abstract class CommonRichTextPubPageState<T extends CommonRichTextPubPage>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> onCropImage(int index) async {
|
Future<void> 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);
|
late final colorScheme = ColorScheme.of(context);
|
||||||
final sourcePath = pathList[index];
|
|
||||||
final croppedFile = await ImageCropper.platform.cropImage(
|
final croppedFile = await ImageCropper.platform.cropImage(
|
||||||
sourcePath: sourcePath,
|
sourcePath: path,
|
||||||
uiSettings: [
|
uiSettings: [
|
||||||
AndroidUiSettings(
|
AndroidUiSettings(
|
||||||
toolbarTitle: '裁剪',
|
toolbarTitle: '裁剪',
|
||||||
@@ -168,8 +216,10 @@ abstract class CommonRichTextPubPageState<T extends CommonRichTextPubPage>
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
if (croppedFile != null) {
|
if (croppedFile != null) {
|
||||||
File(sourcePath).tryDel();
|
if (image is FilePicModel) {
|
||||||
pathList[index] = croppedFile.path;
|
File(image.path).tryDel();
|
||||||
|
}
|
||||||
|
imageList[index] = FilePicModel(path: croppedFile.path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,11 +235,11 @@ abstract class CommonRichTextPubPageState<T extends CommonRichTextPubPage>
|
|||||||
);
|
);
|
||||||
if (pickedFiles.isNotEmpty) {
|
if (pickedFiles.isNotEmpty) {
|
||||||
for (int i = 0; i < pickedFiles.length; i++) {
|
for (int i = 0; i < pickedFiles.length; i++) {
|
||||||
if (pathList.length == limit) {
|
if (imageList.length == limit) {
|
||||||
SmartDialog.showToast('最多选择$limit张图片');
|
SmartDialog.showToast('最多选择$limit张图片');
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
pathList.add(pickedFiles[i].path);
|
imageList.add(FilePicModel(path: pickedFiles[i].path));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
callback?.call();
|
callback?.call();
|
||||||
@@ -465,25 +515,30 @@ abstract class CommonRichTextPubPageState<T extends CommonRichTextPubPage>
|
|||||||
Future<void> onPublish() async {
|
Future<void> onPublish() async {
|
||||||
feedBack();
|
feedBack();
|
||||||
List<Map<String, dynamic>>? pictures;
|
List<Map<String, dynamic>>? pictures;
|
||||||
if (pathList.isNotEmpty) {
|
if (imageList.isNotEmpty) {
|
||||||
SmartDialog.showLoading(msg: '正在上传图片...');
|
SmartDialog.showLoading(msg: '正在上传图片...');
|
||||||
final cancelToken = CancelToken();
|
final cancelToken = CancelToken();
|
||||||
try {
|
try {
|
||||||
pictures = await Future.wait<Map<String, dynamic>>(
|
pictures = await Future.wait<Map<String, dynamic>>(
|
||||||
pathList.map((path) async {
|
imageList.map((img) async {
|
||||||
final result = await MsgHttp.uploadBfs(
|
switch (img) {
|
||||||
path: path,
|
case FilePicModel e:
|
||||||
category: 'daily',
|
final result = await MsgHttp.uploadBfs(
|
||||||
biz: 'new_dyn',
|
path: e.path,
|
||||||
cancelToken: cancelToken,
|
category: 'daily',
|
||||||
);
|
biz: 'new_dyn',
|
||||||
final data = result.data;
|
cancelToken: cancelToken,
|
||||||
return {
|
);
|
||||||
'img_width': data.imageWidth,
|
final data = result.data;
|
||||||
'img_height': data.imageHeight,
|
return {
|
||||||
'img_size': data.imgSize,
|
'img_width': data.imageWidth,
|
||||||
'img_src': data.imageUrl,
|
'img_height': data.imageHeight,
|
||||||
};
|
'img_size': data.imgSize,
|
||||||
|
'img_src': data.imageUrl,
|
||||||
|
};
|
||||||
|
case OpusPicModel e:
|
||||||
|
return e.toJson();
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
eagerError: true,
|
eagerError: true,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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/flutter/dyn/ink_well.dart';
|
||||||
import 'package:PiliPlus/common/widgets/pendant_avatar.dart';
|
import 'package:PiliPlus/common/widgets/pendant_avatar.dart';
|
||||||
import 'package:PiliPlus/http/constants.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/user.dart';
|
||||||
import 'package:PiliPlus/http/video.dart';
|
import 'package:PiliPlus/http/video.dart';
|
||||||
import 'package:PiliPlus/models/dynamics/result.dart';
|
import 'package:PiliPlus/models/dynamics/result.dart';
|
||||||
@@ -28,22 +30,27 @@ import 'package:get/get.dart';
|
|||||||
|
|
||||||
class AuthorPanel extends StatelessWidget {
|
class AuthorPanel extends StatelessWidget {
|
||||||
final DynamicItemModel item;
|
final DynamicItemModel item;
|
||||||
final Function? addBannedList;
|
|
||||||
final bool isSave;
|
final bool isSave;
|
||||||
final bool isDetail;
|
final bool isDetail;
|
||||||
final ValueChanged? onRemove;
|
final ValueChanged<Object>? onRemove;
|
||||||
final void Function(bool isTop, Object dynId)? onSetTop;
|
final void Function(bool isTop, Object dynId)? onSetTop;
|
||||||
final VoidCallback? onBlock;
|
final VoidCallback? onBlock;
|
||||||
|
final Future<LoadingState> Function(bool isPrivate, Object dynId)?
|
||||||
|
onSetPubSetting;
|
||||||
|
final VoidCallback? onEdit;
|
||||||
|
final ValueChanged<int>? onSetReplySubject;
|
||||||
|
|
||||||
const AuthorPanel({
|
const AuthorPanel({
|
||||||
super.key,
|
super.key,
|
||||||
required this.item,
|
required this.item,
|
||||||
this.addBannedList,
|
|
||||||
this.isDetail = false,
|
this.isDetail = false,
|
||||||
this.onRemove,
|
this.onRemove,
|
||||||
this.isSave = false,
|
this.isSave = false,
|
||||||
this.onSetTop,
|
this.onSetTop,
|
||||||
this.onBlock,
|
this.onBlock,
|
||||||
|
this.onSetPubSetting,
|
||||||
|
this.onEdit,
|
||||||
|
this.onSetReplySubject,
|
||||||
});
|
});
|
||||||
|
|
||||||
Widget _buildAvatar(ModuleAuthorModel moduleAuthor) {
|
Widget _buildAvatar(ModuleAuthorModel moduleAuthor) {
|
||||||
@@ -73,6 +80,33 @@ class AuthorPanel extends StatelessWidget {
|
|||||||
)
|
)
|
||||||
: DateFormatUtils.dateFormat(moduleAuthor.pubTs)
|
: DateFormatUtils.dateFormat(moduleAuthor.pubTs)
|
||||||
: moduleAuthor.pubTime;
|
: 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(
|
return Stack(
|
||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
@@ -88,10 +122,10 @@ class AuthorPanel extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
child: Row(
|
child: Row(
|
||||||
|
spacing: 10,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
_buildAvatar(moduleAuthor),
|
_buildAvatar(moduleAuthor),
|
||||||
const SizedBox(width: 10),
|
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@@ -107,14 +141,7 @@ class AuthorPanel extends StatelessWidget {
|
|||||||
fontSize: theme.textTheme.titleSmall!.fontSize,
|
fontSize: theme.textTheme.titleSmall!.fontSize,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (pubTime != null)
|
?pubTs,
|
||||||
Text(
|
|
||||||
'$pubTime${moduleAuthor.pubAction != null ? ' ${moduleAuthor.pubAction}' : ''}',
|
|
||||||
style: TextStyle(
|
|
||||||
color: theme.colorScheme.outline,
|
|
||||||
fontSize: theme.textTheme.labelSmall!.fontSize,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -123,7 +150,7 @@ class AuthorPanel extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: !isDetail && item.modules.moduleTag?.text != null
|
child: moduleTagText != null
|
||||||
? Row(
|
? Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
@@ -142,7 +169,7 @@ class AuthorPanel extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
item.modules.moduleTag!.text!,
|
moduleTagText,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
height: 1,
|
height: 1,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
@@ -399,18 +426,150 @@ class AuthorPanel extends StatelessWidget {
|
|||||||
ListTile(
|
ListTile(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Get.back();
|
Get.back();
|
||||||
onSetTop!(
|
onSetTop!(moduleAuthor.isTop ?? false, item.idStr);
|
||||||
item.modules.moduleTag?.text != null,
|
|
||||||
item.idStr,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
minLeadingWidth: 0,
|
minLeadingWidth: 0,
|
||||||
leading: const Icon(Icons.vertical_align_top, size: 19),
|
leading: const Icon(Icons.vertical_align_top, size: 19),
|
||||||
title: Text(
|
title: Text(
|
||||||
'${item.modules.moduleTag?.text != null ? '取消' : ''}置顶',
|
'${moduleAuthor.isTop == true ? '取消' : ''}置顶',
|
||||||
style: theme.textTheme.titleSmall!,
|
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<void> 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)
|
if (onRemove != null)
|
||||||
ListTile(
|
ListTile(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:PiliPlus/common/widgets/avatars.dart';
|
import 'package:PiliPlus/common/widgets/avatars.dart';
|
||||||
import 'package:PiliPlus/common/widgets/flutter/dyn/ink_well.dart';
|
import 'package:PiliPlus/common/widgets/flutter/dyn/ink_well.dart';
|
||||||
import 'package:PiliPlus/common/widgets/image/image_save.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/models/dynamics/result.dart';
|
||||||
import 'package:PiliPlus/pages/dynamics/widgets/action_panel.dart';
|
import 'package:PiliPlus/pages/dynamics/widgets/action_panel.dart';
|
||||||
import 'package:PiliPlus/pages/dynamics/widgets/author_panel.dart';
|
import 'package:PiliPlus/pages/dynamics/widgets/author_panel.dart';
|
||||||
@@ -15,12 +16,16 @@ class DynamicPanel extends StatelessWidget {
|
|||||||
final DynamicItemModel item;
|
final DynamicItemModel item;
|
||||||
final double maxWidth;
|
final double maxWidth;
|
||||||
final bool isDetail;
|
final bool isDetail;
|
||||||
final ValueChanged? onRemove;
|
final ValueChanged<Object>? onRemove;
|
||||||
final bool isSave;
|
final bool isSave;
|
||||||
final void Function(bool isTop, Object dynId)? onSetTop;
|
final void Function(bool isTop, Object dynId)? onSetTop;
|
||||||
final VoidCallback? onBlock;
|
final VoidCallback? onBlock;
|
||||||
final VoidCallback? onUnfold;
|
final VoidCallback? onUnfold;
|
||||||
final bool isDetailPortraitW;
|
final bool isDetailPortraitW;
|
||||||
|
final Future<LoadingState> Function(bool isPrivate, Object dynId)?
|
||||||
|
onSetPubSetting;
|
||||||
|
final VoidCallback? onEdit;
|
||||||
|
final ValueChanged<int>? onSetReplySubject;
|
||||||
|
|
||||||
const DynamicPanel({
|
const DynamicPanel({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -33,6 +38,9 @@ class DynamicPanel extends StatelessWidget {
|
|||||||
this.onBlock,
|
this.onBlock,
|
||||||
this.onUnfold,
|
this.onUnfold,
|
||||||
this.isDetailPortraitW = true,
|
this.isDetailPortraitW = true,
|
||||||
|
this.onSetPubSetting,
|
||||||
|
this.onEdit,
|
||||||
|
this.onSetReplySubject,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -48,6 +56,9 @@ class DynamicPanel extends StatelessWidget {
|
|||||||
isSave: isSave,
|
isSave: isSave,
|
||||||
onSetTop: onSetTop,
|
onSetTop: onSetTop,
|
||||||
onBlock: onBlock,
|
onBlock: onBlock,
|
||||||
|
onSetPubSetting: onSetPubSetting,
|
||||||
|
onEdit: onEdit,
|
||||||
|
onSetReplySubject: onSetReplySubject,
|
||||||
);
|
);
|
||||||
|
|
||||||
void showMore() => _imageSaveDialog(context, authorWidget.morePanel);
|
void showMore() => _imageSaveDialog(context, authorWidget.morePanel);
|
||||||
|
|||||||
@@ -35,10 +35,13 @@ TextSpan? richNode(
|
|||||||
if (richTextNodes == null || richTextNodes.isEmpty) {
|
if (richTextNodes == null || richTextNodes.isEmpty) {
|
||||||
return TextSpan(text: desc.text);
|
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 层级可能与主页动态层级不同
|
||||||
richTextNodes = opus.summary?.richTextNodes;
|
richTextNodes = summary?.richTextNodes;
|
||||||
if (opus.title case final title?) {
|
if (title != null && title.isNotEmpty) {
|
||||||
spanChildren.add(
|
spanChildren.add(
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: '$title\n',
|
text: '$title\n',
|
||||||
@@ -76,7 +79,7 @@ TextSpan? richNode(
|
|||||||
case 'RICH_TEXT_NODE_TYPE_TOPIC':
|
case 'RICH_TEXT_NODE_TYPE_TOPIC':
|
||||||
spanChildren.add(
|
spanChildren.add(
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: i.origText!,
|
text: i.origText,
|
||||||
style: style,
|
style: style,
|
||||||
recognizer: TapGestureRecognizer()
|
recognizer: TapGestureRecognizer()
|
||||||
..onTap = () => Get.toNamed(
|
..onTap = () => Get.toNamed(
|
||||||
|
|||||||
@@ -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/flutter/text_field/text_field.dart';
|
||||||
import 'package:PiliPlus/common/widgets/pair.dart';
|
import 'package:PiliPlus/common/widgets/pair.dart';
|
||||||
import 'package:PiliPlus/http/dynamics.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/publish_panel_type.dart';
|
||||||
import 'package:PiliPlus/models/common/reply/reply_option_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/dynamics/vote_model.dart';
|
||||||
import 'package:PiliPlus/models_new/dynamic/dyn_reserve_info/data.dart';
|
import 'package:PiliPlus/models_new/dynamic/dyn_reserve_info/data.dart';
|
||||||
import 'package:PiliPlus/models_new/dynamic/dyn_topic_top/topic_item.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/extension/iterable_ext.dart';
|
||||||
import 'package:PiliPlus/utils/grid.dart';
|
import 'package:PiliPlus/utils/grid.dart';
|
||||||
import 'package:PiliPlus/utils/request_utils.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/material.dart' hide DraggableScrollableSheet;
|
||||||
import 'package:flutter/services.dart' show LengthLimitingTextInputFormatter;
|
import 'package:flutter/services.dart' show LengthLimitingTextInputFormatter;
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
@@ -39,48 +40,81 @@ class CreateDynPanel extends CommonRichTextPubPage {
|
|||||||
const CreateDynPanel({
|
const CreateDynPanel({
|
||||||
super.key,
|
super.key,
|
||||||
super.imageLengthLimit = 18,
|
super.imageLengthLimit = 18,
|
||||||
|
super.items,
|
||||||
|
super.pics,
|
||||||
this.scrollController,
|
this.scrollController,
|
||||||
this.topic,
|
this.topic,
|
||||||
|
this.editConfig,
|
||||||
|
this.title,
|
||||||
|
this.isPrivate = false,
|
||||||
|
this.replyOption = .allow,
|
||||||
|
this.onSuccess,
|
||||||
});
|
});
|
||||||
|
|
||||||
final ScrollController? scrollController;
|
final ScrollController? scrollController;
|
||||||
|
final String? title;
|
||||||
final Pair<int, String>? topic;
|
final Pair<int, String>? topic;
|
||||||
|
final bool isPrivate;
|
||||||
|
final ReplyOptionType replyOption;
|
||||||
|
final ({Object dynId, Object? repostDynId})? editConfig;
|
||||||
|
final VoidCallback? onSuccess;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<CreateDynPanel> createState() => _CreateDynPanelState();
|
State<CreateDynPanel> createState() => _CreateDynPanelState();
|
||||||
|
|
||||||
static void onCreateDyn(BuildContext context, {Pair<int, String>? topic}) =>
|
static void onCreateDyn(
|
||||||
showModalBottomSheet(
|
BuildContext context, {
|
||||||
context: context,
|
String? title,
|
||||||
useSafeArea: true,
|
bool isPrivate = false,
|
||||||
isScrollControlled: true,
|
ReplyOptionType replyOption = .allow,
|
||||||
builder: (context) => dyn_sheet.DraggableScrollableSheet(
|
List<RichTextItem>? items,
|
||||||
snap: true,
|
List<PicModel>? pics,
|
||||||
expand: false,
|
Pair<int, String>? topic,
|
||||||
initialChildSize: 1,
|
({Object dynId, Object? repostDynId})? editConfig,
|
||||||
minChildSize: 0,
|
VoidCallback? onSuccess,
|
||||||
maxChildSize: 1,
|
}) => showModalBottomSheet(
|
||||||
snapSizes: const [1],
|
context: context,
|
||||||
builder: (context, scrollController) => CreateDynPanel(
|
useSafeArea: true,
|
||||||
scrollController: scrollController,
|
isScrollControlled: true,
|
||||||
topic: topic,
|
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<CreateDynPanel> {
|
class _CreateDynPanelState extends CommonRichTextPubPageState<CreateDynPanel> {
|
||||||
final RxBool _isPrivate = false.obs;
|
late final bool _isEdit;
|
||||||
final Rx<DateTime?> _publishTime = Rx<DateTime?>(null);
|
late final RxBool _isPrivate;
|
||||||
final Rx<ReplyOptionType> _replyOption = ReplyOptionType.allow.obs;
|
late final Rx<Pair<int, String>?> _topic;
|
||||||
final _titleEditCtr = TextEditingController();
|
late final Rx<ReplyOptionType> _replyOption;
|
||||||
final Rx<Pair<int, String>?> topic = Rx<Pair<int, String>?>(null);
|
late final TextEditingController _titleEditCtr;
|
||||||
|
late final Rx<DateTime?> _publishTime = Rx<DateTime?>(null);
|
||||||
final Rx<ReserveInfoData?> _reserveCard = Rx<ReserveInfoData?>(null);
|
final Rx<ReserveInfoData?> _reserveCard = Rx<ReserveInfoData?>(null);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
topic.value = widget.topic;
|
_isEdit = widget.editConfig != null;
|
||||||
|
_isPrivate = widget.isPrivate.obs;
|
||||||
|
_replyOption = widget.replyOption.obs;
|
||||||
|
_topic = Rx<Pair<int, String>?>(widget.topic);
|
||||||
|
_titleEditCtr = TextEditingController(text: widget.title);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -110,7 +144,7 @@ class _CreateDynPanelState extends CommonRichTextPubPageState<CreateDynPanel> {
|
|||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
child: Obx(
|
child: Obx(
|
||||||
() {
|
() {
|
||||||
final hasTopic = topic.value != null;
|
final hasTopic = _topic.value != null;
|
||||||
return Row(
|
return Row(
|
||||||
spacing: 10,
|
spacing: 10,
|
||||||
children: [
|
children: [
|
||||||
@@ -158,7 +192,9 @@ class _CreateDynPanelState extends CommonRichTextPubPageState<CreateDynPanel> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: hasTopic ? topic.value!.second : '选择话题',
|
text: hasTopic
|
||||||
|
? _topic.value!.second
|
||||||
|
: '选择话题',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: hasTopic
|
color: hasTopic
|
||||||
? null
|
? null
|
||||||
@@ -176,7 +212,7 @@ class _CreateDynPanelState extends CommonRichTextPubPageState<CreateDynPanel> {
|
|||||||
icon: const Icon(Icons.clear),
|
icon: const Icon(Icons.clear),
|
||||||
bgColor: theme.colorScheme.onInverseSurface,
|
bgColor: theme.colorScheme.onInverseSurface,
|
||||||
iconColor: theme.colorScheme.onSurfaceVariant,
|
iconColor: theme.colorScheme.onSurfaceVariant,
|
||||||
onPressed: () => topic.value = null,
|
onPressed: () => _topic.value = null,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -253,10 +289,10 @@ class _CreateDynPanelState extends CommonRichTextPubPageState<CreateDynPanel> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
...List.generate(
|
...List.generate(
|
||||||
pathList.length,
|
imageList.length,
|
||||||
(index) => buildImage(index, 100),
|
(index) => buildImage(index, 100),
|
||||||
),
|
),
|
||||||
if (pathList.length != limit)
|
if (imageList.length != limit)
|
||||||
Builder(
|
Builder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
const borderRadius = StyleString.mdRadius;
|
const borderRadius = StyleString.mdRadius;
|
||||||
@@ -265,7 +301,7 @@ class _CreateDynPanelState extends CommonRichTextPubPageState<CreateDynPanel> {
|
|||||||
child: InkWell(
|
child: InkWell(
|
||||||
borderRadius: borderRadius,
|
borderRadius: borderRadius,
|
||||||
onTap: () => onPickImage(() {
|
onTap: () => onPickImage(() {
|
||||||
if (pathList.isNotEmpty && !enablePublish.value) {
|
if (imageList.isNotEmpty && !enablePublish.value) {
|
||||||
enablePublish.value = true;
|
enablePublish.value = true;
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
@@ -316,10 +352,10 @@ class _CreateDynPanelState extends CommonRichTextPubPageState<CreateDynPanel> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Center(
|
Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
'发布动态',
|
_isEdit ? '编辑动态' : '发布动态',
|
||||||
style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
|
style: const TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Align(
|
Align(
|
||||||
@@ -464,7 +500,7 @@ class _CreateDynPanelState extends CommonRichTextPubPageState<CreateDynPanel> {
|
|||||||
),
|
),
|
||||||
visualDensity: VisualDensity.compact,
|
visualDensity: VisualDensity.compact,
|
||||||
),
|
),
|
||||||
onPressed: _isPrivate.value
|
onPressed: _isEdit || _isPrivate.value
|
||||||
? null
|
? null
|
||||||
: () async {
|
: () async {
|
||||||
controller.keepChatPanel();
|
controller.keepChatPanel();
|
||||||
@@ -538,8 +574,10 @@ class _CreateDynPanelState extends CommonRichTextPubPageState<CreateDynPanel> {
|
|||||||
children: [
|
children: [
|
||||||
emojiBtn,
|
emojiBtn,
|
||||||
atBtn,
|
atBtn,
|
||||||
voteBtn,
|
if (!_isEdit) ...[
|
||||||
moreBtn,
|
voteBtn,
|
||||||
|
moreBtn,
|
||||||
|
],
|
||||||
// if (kDebugMode)
|
// if (kDebugMode)
|
||||||
// ToolbarIconButton(
|
// ToolbarIconButton(
|
||||||
// onPressed: editController.clear,
|
// onPressed: editController.clear,
|
||||||
@@ -707,8 +745,34 @@ class _CreateDynPanelState extends CommonRichTextPubPageState<CreateDynPanel> {
|
|||||||
SmartDialog.showLoading(msg: '正在发布');
|
SmartDialog.showLoading(msg: '正在发布');
|
||||||
List<Map<String, dynamic>>? extraContent = getRichContent();
|
List<Map<String, dynamic>>? extraContent = getRichContent();
|
||||||
final hasRichText = extraContent != null;
|
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 reserveCard = _reserveCard.value;
|
||||||
final result = await DynamicsHttp.createDynamic(
|
final res = await DynamicsHttp.createDynamic(
|
||||||
mid: Accounts.main.mid,
|
mid: Accounts.main.mid,
|
||||||
rawText: hasRichText ? null : editController.text,
|
rawText: hasRichText ? null : editController.text,
|
||||||
pics: pictures,
|
pics: pictures,
|
||||||
@@ -718,7 +782,7 @@ class _CreateDynPanelState extends CommonRichTextPubPageState<CreateDynPanel> {
|
|||||||
replyOption: _replyOption.value,
|
replyOption: _replyOption.value,
|
||||||
privatePub: _isPrivate.value ? 1 : null,
|
privatePub: _isPrivate.value ? 1 : null,
|
||||||
title: _titleEditCtr.text,
|
title: _titleEditCtr.text,
|
||||||
topic: topic.value,
|
topic: _topic.value,
|
||||||
extraContent: extraContent,
|
extraContent: extraContent,
|
||||||
attachCard: reserveCard == null
|
attachCard: reserveCard == null
|
||||||
? null
|
? null
|
||||||
@@ -732,11 +796,11 @@ class _CreateDynPanelState extends CommonRichTextPubPageState<CreateDynPanel> {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
SmartDialog.dismiss();
|
SmartDialog.dismiss();
|
||||||
if (result['status']) {
|
if (res case Success(:final response)) {
|
||||||
hasPub = true;
|
hasPub = true;
|
||||||
Get.back();
|
Get.back();
|
||||||
SmartDialog.showToast('发布成功');
|
SmartDialog.showToast('发布成功');
|
||||||
final id = result['data']?['dyn_id'];
|
final id = response?['dyn_id'];
|
||||||
RequestUtils.insertCreatedDyn(id);
|
RequestUtils.insertCreatedDyn(id);
|
||||||
if (!_isPrivate.value) {
|
if (!_isPrivate.value) {
|
||||||
RequestUtils.checkCreatedDyn(
|
RequestUtils.checkCreatedDyn(
|
||||||
@@ -745,8 +809,7 @@ class _CreateDynPanelState extends CommonRichTextPubPageState<CreateDynPanel> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
SmartDialog.showToast(result['msg']);
|
res.toast();
|
||||||
if (kDebugMode) debugPrint('failed to publish: ${result['msg']}');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -759,7 +822,7 @@ class _CreateDynPanelState extends CommonRichTextPubPageState<CreateDynPanel> {
|
|||||||
onCachePos: (offset) => _topicOffset = offset,
|
onCachePos: (offset) => _topicOffset = offset,
|
||||||
);
|
);
|
||||||
if (res != null) {
|
if (res != null) {
|
||||||
topic.value = Pair(first: res.id, second: res.name);
|
_topic.value = Pair(first: res.id, second: res.name);
|
||||||
}
|
}
|
||||||
controller.restoreChatPanel();
|
controller.restoreChatPanel();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import 'package:PiliPlus/http/dynamics.dart';
|
import 'package:PiliPlus/http/dynamics.dart';
|
||||||
import 'package:PiliPlus/http/loading_state.dart';
|
import 'package:PiliPlus/http/loading_state.dart';
|
||||||
|
import 'package:PiliPlus/http/reply.dart';
|
||||||
import 'package:PiliPlus/models/dynamics/result.dart';
|
import 'package:PiliPlus/models/dynamics/result.dart';
|
||||||
import 'package:PiliPlus/pages/common/dyn/common_dyn_controller.dart';
|
import 'package:PiliPlus/pages/common/dyn/common_dyn_controller.dart';
|
||||||
import 'package:PiliPlus/utils/id_utils.dart';
|
import 'package:PiliPlus/utils/id_utils.dart';
|
||||||
import 'package:PiliPlus/utils/storage_pref.dart';
|
import 'package:PiliPlus/utils/storage_pref.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
class DynamicDetailController extends CommonDynController {
|
class DynamicDetailController extends CommonDynController {
|
||||||
@@ -45,4 +47,33 @@ class DynamicDetailController extends CommonDynController {
|
|||||||
replyType = commentType;
|
replyType = commentType;
|
||||||
queryData();
|
queryData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<LoadingState> 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<void> 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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,17 @@ import 'dart:math';
|
|||||||
|
|
||||||
import 'package:PiliPlus/common/widgets/custom_icon.dart';
|
import 'package:PiliPlus/common/widgets/custom_icon.dart';
|
||||||
import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.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/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/models/dynamics/result.dart';
|
||||||
import 'package:PiliPlus/pages/common/dyn/common_dyn_page.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/author_panel.dart';
|
||||||
import 'package:PiliPlus/pages/dynamics/widgets/dynamic_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_detail/controller.dart';
|
||||||
import 'package:PiliPlus/pages/dynamics_repost/view.dart';
|
import 'package:PiliPlus/pages/dynamics_repost/view.dart';
|
||||||
import 'package:PiliPlus/utils/extension/get_ext.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/num_utils.dart';
|
||||||
import 'package:PiliPlus/utils/request_utils.dart';
|
import 'package:PiliPlus/utils/request_utils.dart';
|
||||||
import 'package:PiliPlus/utils/utils.dart';
|
import 'package:PiliPlus/utils/utils.dart';
|
||||||
|
import 'package:flutter/foundation.dart' show kDebugMode;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@@ -66,6 +73,142 @@ class _DynamicDetailPageState extends CommonDynPageState<DynamicDetailPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onEdit() {
|
||||||
|
final item = controller.dynItem;
|
||||||
|
List<RichTextItem>? items;
|
||||||
|
final moduleDynamic = item.modules.moduleDynamic;
|
||||||
|
final desc = moduleDynamic?.desc;
|
||||||
|
final opus = moduleDynamic?.major?.opus;
|
||||||
|
|
||||||
|
Pair<int, String>? 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 = <RichTextItem>[];
|
||||||
|
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(
|
PreferredSizeWidget _buildAppBar() => AppBar(
|
||||||
title: Padding(
|
title: Padding(
|
||||||
padding: const EdgeInsets.only(right: 12),
|
padding: const EdgeInsets.only(right: 12),
|
||||||
@@ -80,6 +223,9 @@ class _DynamicDetailPageState extends CommonDynPageState<DynamicDetailPage> {
|
|||||||
child: AuthorPanel(
|
child: AuthorPanel(
|
||||||
item: controller.dynItem,
|
item: controller.dynItem,
|
||||||
isDetail: true,
|
isDetail: true,
|
||||||
|
onSetPubSetting: controller.onSetPubSetting,
|
||||||
|
onEdit: _onEdit,
|
||||||
|
onSetReplySubject: controller.onSetReplySubject,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -88,10 +234,7 @@ class _DynamicDetailPageState extends CommonDynPageState<DynamicDetailPage> {
|
|||||||
),
|
),
|
||||||
actions: isPortrait
|
actions: isPortrait
|
||||||
? null
|
? null
|
||||||
: [
|
: [ratioWidget(maxWidth), const SizedBox(width: 16)],
|
||||||
ratioWidget(maxWidth),
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildBody(ThemeData theme) {
|
Widget _buildBody(ThemeData theme) {
|
||||||
@@ -110,6 +253,9 @@ class _DynamicDetailPageState extends CommonDynPageState<DynamicDetailPage> {
|
|||||||
isDetail: true,
|
isDetail: true,
|
||||||
maxWidth: maxWidth - this.padding.horizontal - 2 * padding,
|
maxWidth: maxWidth - this.padding.horizontal - 2 * padding,
|
||||||
isDetailPortraitW: isPortrait,
|
isDetailPortraitW: isPortrait,
|
||||||
|
onSetPubSetting: controller.onSetPubSetting,
|
||||||
|
onEdit: _onEdit,
|
||||||
|
onSetReplySubject: controller.onSetReplySubject,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
buildReplyHeader(theme),
|
buildReplyHeader(theme),
|
||||||
@@ -143,6 +289,9 @@ class _DynamicDetailPageState extends CommonDynPageState<DynamicDetailPage> {
|
|||||||
(flex / (flex + flex1)) -
|
(flex / (flex + flex1)) -
|
||||||
padding,
|
padding,
|
||||||
isDetailPortraitW: isPortrait,
|
isDetailPortraitW: isPortrait,
|
||||||
|
onSetPubSetting: controller.onSetPubSetting,
|
||||||
|
onEdit: _onEdit,
|
||||||
|
onSetReplySubject: controller.onSetReplySubject,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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/flutter/text_field/text_field.dart';
|
||||||
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
||||||
import 'package:PiliPlus/http/dynamics.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/publish_panel_type.dart';
|
||||||
import 'package:PiliPlus/models/dynamics/result.dart';
|
import 'package:PiliPlus/models/dynamics/result.dart';
|
||||||
import 'package:PiliPlus/pages/common/publish/common_rich_text_pub_page.dart';
|
import 'package:PiliPlus/pages/common/publish/common_rich_text_pub_page.dart';
|
||||||
@@ -419,7 +420,7 @@ class _RepostPanelState extends CommonRichTextPubPageState<RepostPanel> {
|
|||||||
if (hasRichText && repostContent != null) {
|
if (hasRichText && repostContent != null) {
|
||||||
richContent.addAll(repostContent);
|
richContent.addAll(repostContent);
|
||||||
}
|
}
|
||||||
final result = await DynamicsHttp.createDynamic(
|
final res = await DynamicsHttp.createDynamic(
|
||||||
mid: Accounts.main.mid,
|
mid: Accounts.main.mid,
|
||||||
dynIdStr: widget.item?.idStr ?? widget.dynIdStr,
|
dynIdStr: widget.item?.idStr ?? widget.dynIdStr,
|
||||||
rid: widget.rid,
|
rid: widget.rid,
|
||||||
@@ -428,19 +429,19 @@ class _RepostPanelState extends CommonRichTextPubPageState<RepostPanel> {
|
|||||||
extraContent: richContent ?? repostContent,
|
extraContent: richContent ?? repostContent,
|
||||||
);
|
);
|
||||||
SmartDialog.dismiss();
|
SmartDialog.dismiss();
|
||||||
if (result['status']) {
|
if (res case Success(:final response)) {
|
||||||
hasPub = true;
|
hasPub = true;
|
||||||
Get.back();
|
Get.back();
|
||||||
SmartDialog.showToast('转发成功');
|
SmartDialog.showToast('转发成功');
|
||||||
widget.onSuccess?.call();
|
widget.onSuccess?.call();
|
||||||
final id = result['data']?['dyn_id'];
|
final id = response?['dyn_id'];
|
||||||
RequestUtils.insertCreatedDyn(id);
|
RequestUtils.insertCreatedDyn(id);
|
||||||
RequestUtils.checkCreatedDyn(
|
RequestUtils.checkCreatedDyn(
|
||||||
id: id,
|
id: id,
|
||||||
dynText: editController.rawText,
|
dynText: editController.rawText,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
SmartDialog.showToast(result['msg']);
|
res.toast();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart'
|
|||||||
import 'package:PiliPlus/http/video.dart';
|
import 'package:PiliPlus/http/video.dart';
|
||||||
import 'package:PiliPlus/main.dart';
|
import 'package:PiliPlus/main.dart';
|
||||||
import 'package:PiliPlus/models/common/publish_panel_type.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/common/publish/common_rich_text_pub_page.dart';
|
||||||
import 'package:PiliPlus/pages/dynamics_mention/controller.dart';
|
import 'package:PiliPlus/pages/dynamics_mention/controller.dart';
|
||||||
import 'package:PiliPlus/pages/emote/view.dart';
|
import 'package:PiliPlus/pages/emote/view.dart';
|
||||||
@@ -110,7 +111,7 @@ class _ReplyPageState extends CommonRichTextPubPageState<ReplyPage> {
|
|||||||
Widget buildImagePreview() {
|
Widget buildImagePreview() {
|
||||||
return Obx(
|
return Obx(
|
||||||
() {
|
() {
|
||||||
if (pathList.isNotEmpty) {
|
if (imageList.isNotEmpty) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: 85,
|
height: 85,
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
@@ -120,7 +121,7 @@ class _ReplyPageState extends CommonRichTextPubPageState<ReplyPage> {
|
|||||||
spacing: 10,
|
spacing: 10,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: List.generate(
|
children: List.generate(
|
||||||
pathList.length,
|
imageList.length,
|
||||||
(index) => buildImage(index, 75),
|
(index) => buildImage(index, 75),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -368,7 +369,7 @@ class _ReplyPageState extends CommonRichTextPubPageState<ReplyPage> {
|
|||||||
if (isRoot && widget.canUploadPic)
|
if (isRoot && widget.canUploadPic)
|
||||||
item(
|
item(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
if (pathList.length >= limit) {
|
if (imageList.length >= limit) {
|
||||||
SmartDialog.showToast('最多选择$limit张图片');
|
SmartDialog.showToast('最多选择$limit张图片');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -385,7 +386,7 @@ class _ReplyPageState extends CommonRichTextPubPageState<ReplyPage> {
|
|||||||
'$tmpDirPath/${Utils.generateRandomString(8)}.png',
|
'$tmpDirPath/${Utils.generateRandomString(8)}.png',
|
||||||
);
|
);
|
||||||
await file.writeAsBytes(res);
|
await file.writeAsBytes(res);
|
||||||
pathList.add(file.path);
|
imageList.add(FilePicModel(path: file.path));
|
||||||
} else {
|
} else {
|
||||||
debugPrint('null screenshot');
|
debugPrint('null screenshot');
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user