mod: 评论表情渲染
This commit is contained in:
@ -2,8 +2,9 @@ import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/skeleton/video_card_h.dart';
|
||||
import 'package:pilipala/common/widgets/http_error.dart';
|
||||
import 'package:pilipala/common/widgets/reply_item.dart';
|
||||
import 'package:pilipala/models/video/reply/item.dart';
|
||||
import 'controller.dart';
|
||||
import 'widgets/reply_item.dart';
|
||||
|
||||
class VideoReplyPanel extends StatefulWidget {
|
||||
const VideoReplyPanel({super.key});
|
||||
@ -28,8 +29,24 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
if (snapshot.data['status']) {
|
||||
List<dynamic> replies = snapshot.data['data'].replies;
|
||||
replies.addAll(snapshot.data['data'].topReplies);
|
||||
List<ReplyItemModel> replies = snapshot.data['data'].replies;
|
||||
// 添加置顶回复
|
||||
if (snapshot.data['data'].upper.top != null) {
|
||||
bool flag = false;
|
||||
for (var i = 0;
|
||||
i < snapshot.data['data'].topReplies.length;
|
||||
i++) {
|
||||
if (snapshot.data['data'].topReplies[i].rpid ==
|
||||
snapshot.data['data'].upper.top.rpid) {
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
if (!flag) {
|
||||
replies.insert(0, snapshot.data['data'].upper.top);
|
||||
}
|
||||
}
|
||||
|
||||
replies.insertAll(0, snapshot.data['data'].topReplies);
|
||||
// 请求成功
|
||||
return SliverList(
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
|
448
lib/pages/video/detail/reply/widgets/reply_item.dart
Normal file
448
lib/pages/video/detail/reply/widgets/reply_item.dart
Normal file
@ -0,0 +1,448 @@
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
import 'package:pilipala/models/video/reply/item.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
|
||||
class ReplyItem extends StatelessWidget {
|
||||
ReplyItem({super.key, this.replyItem, required this.isUp});
|
||||
ReplyItemModel? replyItem;
|
||||
bool isUp = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: () {},
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 8, 8, 0),
|
||||
child: content(context),
|
||||
),
|
||||
Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.08),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget lfAvtar(context) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(top: 5),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.03)),
|
||||
),
|
||||
child: NetworkImgLayer(
|
||||
src: replyItem!.member!.avatar,
|
||||
width: 30,
|
||||
height: 30,
|
||||
type: 'avatar',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget content(context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
// 头像、昵称
|
||||
Row(
|
||||
// 两端对齐
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
GestureDetector(
|
||||
// onTap: () =>
|
||||
// Get.toNamed('/member/${reply.userName}', parameters: {
|
||||
// 'memberAvatar': reply.avatar,
|
||||
// 'heroTag': reply.userName + heroTag,
|
||||
// }),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
lfAvtar(context),
|
||||
const SizedBox(width: 12),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
replyItem!.member!.uname!,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleSmall!
|
||||
.copyWith(
|
||||
color: replyItem!.isUp!
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Image.asset(
|
||||
'assets/images/lv/lv${replyItem!.member!.level}.png',
|
||||
height: 13,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
// title
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 0, left: 45, right: 6),
|
||||
child: SelectableRegion(
|
||||
magnifierConfiguration: const TextMagnifierConfiguration(),
|
||||
focusNode: FocusNode(),
|
||||
selectionControls: MaterialTextSelectionControls(),
|
||||
child: Text.rich(
|
||||
style: const TextStyle(height: 1.6),
|
||||
TextSpan(
|
||||
children: [
|
||||
buildContent(context, replyItem!.content!),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// 操作区域
|
||||
bottonAction(context, replyItem!.replyControl),
|
||||
if (replyItem!.replies!.isNotEmpty) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 2, bottom: 12),
|
||||
child: ReplyItemRow(
|
||||
replies: replyItem!.replies,
|
||||
replyControl: replyItem!.replyControl,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// 感谢、回复、复制
|
||||
Widget bottonAction(context, replyControl) {
|
||||
var color = Theme.of(context).colorScheme.outline;
|
||||
return Row(
|
||||
children: [
|
||||
const SizedBox(width: 48),
|
||||
Text(
|
||||
Utils.dateFormat(replyItem!.ctime),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelMedium!
|
||||
.copyWith(color: Theme.of(context).colorScheme.outline),
|
||||
),
|
||||
if (replyItem!.isTop!) ...[
|
||||
Text(
|
||||
' • 置顶',
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
if (replyControl!.isUpTop!) ...[
|
||||
Text(
|
||||
' • 超赞',
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
// const SizedBox(width: 4),
|
||||
],
|
||||
const Spacer(),
|
||||
SizedBox(
|
||||
height: 35,
|
||||
child: TextButton(
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.thumb_up_alt_outlined,
|
||||
size: 16,
|
||||
color: color,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
replyItem!.like.toString(),
|
||||
style: TextStyle(
|
||||
color: color,
|
||||
fontSize:
|
||||
Theme.of(context).textTheme.labelSmall!.fontSize),
|
||||
),
|
||||
],
|
||||
),
|
||||
onPressed: () {},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 5)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class ReplyItemRow extends StatelessWidget {
|
||||
ReplyItemRow({
|
||||
super.key,
|
||||
this.replies,
|
||||
this.replyControl,
|
||||
});
|
||||
List? replies;
|
||||
ReplyControl? replyControl;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bool isShow = replyControl!.isShow!;
|
||||
int extraRow = replyControl != null && isShow ? 1 : 0;
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(left: 42, right: 4, top: 0),
|
||||
child: Material(
|
||||
color: Theme.of(context).colorScheme.onInverseSurface.withOpacity(0.7),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
animationDuration: Duration.zero,
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: replies!.length + extraRow,
|
||||
itemBuilder: (context, index) {
|
||||
if (extraRow == 1 && index == replies!.length) {
|
||||
// 有楼中楼回复,在最后显示
|
||||
return InkWell(
|
||||
onTap: () {},
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 8, horizontal: 8),
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
style: TextStyle(
|
||||
fontSize:
|
||||
Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||
),
|
||||
children: [
|
||||
if (replyControl!.upReply!)
|
||||
const TextSpan(text: 'up主等人 '),
|
||||
TextSpan(
|
||||
text: replyControl!.entryText!,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return InkWell(
|
||||
onTap: () {},
|
||||
child: Padding(
|
||||
padding: EdgeInsets.fromLTRB(8, index == 0 ? 8 : 4, 8, 4),
|
||||
child: Text.rich(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 2,
|
||||
TextSpan(
|
||||
children: [
|
||||
if (replies![index].isUp)
|
||||
TextSpan(
|
||||
text: 'UP • ',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: Theme.of(context)
|
||||
.textTheme
|
||||
.labelMedium!
|
||||
.fontSize,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: replies![index].member.uname + ' ',
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context)
|
||||
.textTheme
|
||||
.titleSmall!
|
||||
.fontSize,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () => {
|
||||
print('跳转至用户主页'),
|
||||
},
|
||||
),
|
||||
buildContent(context, replies![index].content),
|
||||
],
|
||||
),
|
||||
),
|
||||
));
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
InlineSpan buildContent(BuildContext context, content) {
|
||||
if (content.emote.isEmpty &&
|
||||
content.atNameToMid.isEmpty &&
|
||||
content.jumpUrl.isEmpty &&
|
||||
content.pictures.isEmpty) {
|
||||
return TextSpan(text: content.message);
|
||||
}
|
||||
List<InlineSpan> spanChilds = [];
|
||||
// if (content.atNameToMid.isNotEmpty) {
|
||||
// print(content.message);
|
||||
// content.atNameToMid.forEach((key, value) {
|
||||
// key = '@' + key;
|
||||
// int lastIndex = content.message.indexOf(key);
|
||||
// int endIndex = (lastIndex + key.length).toInt();
|
||||
// if (lastIndex >= 0) {
|
||||
// spanChilds.add(TextSpan(
|
||||
// text: '@' + key,
|
||||
// style: TextStyle(color: Theme.of(context).colorScheme.primary)));
|
||||
// content.message = content.message.replaceRange(lastIndex, endIndex, '');
|
||||
// spanChilds.add(TextSpan(text: content.message));
|
||||
// }
|
||||
// spanChilds.add(TextSpan(text: content.message.substring(lastIndex)));
|
||||
// });
|
||||
// // return TextSpan(children: spanChilds);
|
||||
// }
|
||||
// if (content.emote.isNotEmpty) {
|
||||
// content.emote.forEach((key, value) {
|
||||
// int lastIndex = content.message.indexOf(key);
|
||||
// int endIndex = content.message.indexOf(key) + key.length;
|
||||
// if (lastIndex >= 0) {
|
||||
// content.message = content.message.replaceRange(lastIndex, endIndex, '');
|
||||
// spanChilds.add(TextSpan(text: content.message.substring(0, lastIndex)));
|
||||
// }
|
||||
// spanChilds.add(WidgetSpan(
|
||||
// child: NetworkImgLayer(
|
||||
// src: value["url"],
|
||||
// width: 20,
|
||||
// height: 20,
|
||||
// )));
|
||||
// });
|
||||
// // return TextSpan(children: spanChilds);
|
||||
// }
|
||||
// if (content.pictures.isNotEmpty) {
|
||||
// spanChilds.add(TextSpan(text: content.message));
|
||||
// spanChilds.add(const WidgetSpan(
|
||||
// child: SizedBox(
|
||||
// height: 4,
|
||||
// )));
|
||||
// for (var i = 0; i < content.pictures.length; i++) {
|
||||
// spanChilds.add(
|
||||
// WidgetSpan(
|
||||
// child: SizedBox(
|
||||
// height: 180,
|
||||
// child: NetworkImgLayer(
|
||||
// src: content.pictures[i]['img_src'],
|
||||
// width: 200,
|
||||
// height: 200 *
|
||||
// content.pictures[i]['img_height'] /
|
||||
// content.pictures[i]['img_width'],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// return TextSpan(children: spanChilds);
|
||||
// }
|
||||
content.message.splitMapJoin(
|
||||
RegExp(r"\[.*?\]"),
|
||||
onMatch: (Match match) {
|
||||
String matchStr = match[0]!;
|
||||
if (content.emote.isNotEmpty) {
|
||||
if (content.emote.keys.contains(matchStr)) {
|
||||
spanChilds.add(
|
||||
WidgetSpan(
|
||||
child: NetworkImgLayer(
|
||||
src: content.emote[matchStr]['url'],
|
||||
width: 20,
|
||||
height: 20,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
spanChilds.add(TextSpan(text: matchStr));
|
||||
return matchStr;
|
||||
}
|
||||
}
|
||||
return matchStr;
|
||||
},
|
||||
onNonMatch: (String str) {
|
||||
try {
|
||||
if (content.atNameToMid.isNotEmpty) {
|
||||
return str.splitMapJoin(
|
||||
RegExp(r"@.*:"),
|
||||
onMatch: (Match match) {
|
||||
if (match[0] != null) {
|
||||
content.atNameToMid.forEach((key, value) {
|
||||
spanChilds.add(
|
||||
TextSpan(
|
||||
text: '@$key ',
|
||||
style: TextStyle(
|
||||
fontSize:
|
||||
Theme.of(context).textTheme.titleSmall!.fontSize,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () => {
|
||||
print('跳转至用户主页'),
|
||||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
return match[0]!;
|
||||
},
|
||||
onNonMatch: (String str) {
|
||||
spanChilds.add(TextSpan(text: str));
|
||||
return str;
|
||||
},
|
||||
);
|
||||
} else {
|
||||
spanChilds.add(TextSpan(text: str));
|
||||
return str;
|
||||
}
|
||||
} catch (e) {
|
||||
spanChilds.add(TextSpan(text: str));
|
||||
return str;
|
||||
}
|
||||
},
|
||||
);
|
||||
if (content.pictures.isNotEmpty) {
|
||||
spanChilds.add(const WidgetSpan(
|
||||
child: SizedBox(
|
||||
height: 4,
|
||||
)));
|
||||
for (var i = 0; i < content.pictures.length; i++) {
|
||||
spanChilds.add(
|
||||
WidgetSpan(
|
||||
child: SizedBox(
|
||||
height: 180,
|
||||
child: NetworkImgLayer(
|
||||
src: content.pictures[i]['img_src'],
|
||||
width: 200,
|
||||
height: 200 *
|
||||
content.pictures[i]['img_height'] /
|
||||
content.pictures[i]['img_width'],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
return TextSpan(children: spanChilds);
|
||||
}
|
Reference in New Issue
Block a user