feat: edit dyn

feat: set pub setting

feat: set reply interaction

Signed-off-by: dom <githubaccount56556@proton.me>
This commit is contained in:
dom
2026-01-13 10:16:45 +08:00
parent 4a2679a589
commit cb58822009
16 changed files with 826 additions and 131 deletions

View File

@@ -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<Object>? onRemove;
final void Function(bool isTop, Object dynId)? onSetTop;
final VoidCallback? onBlock;
final Future<LoadingState> Function(bool isPrivate, Object dynId)?
onSetPubSetting;
final VoidCallback? onEdit;
final ValueChanged<int>? 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<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)
ListTile(
onTap: () {

View File

@@ -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<Object>? onRemove;
final bool isSave;
final void Function(bool isTop, Object dynId)? onSetTop;
final VoidCallback? onBlock;
final VoidCallback? onUnfold;
final bool isDetailPortraitW;
final Future<LoadingState> Function(bool isPrivate, Object dynId)?
onSetPubSetting;
final VoidCallback? onEdit;
final ValueChanged<int>? 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);

View File

@@ -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(