mod: 评论表情渲染
This commit is contained in:
@ -45,4 +45,4 @@ SPEC CHECKSUMS:
|
|||||||
|
|
||||||
PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3
|
PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3
|
||||||
|
|
||||||
COCOAPODS: 1.11.3
|
COCOAPODS: 1.12.1
|
||||||
|
|||||||
@ -36,6 +36,7 @@ class NetworkImgLayer extends StatelessWidget {
|
|||||||
imageUrl: src!,
|
imageUrl: src!,
|
||||||
width: width ?? double.infinity,
|
width: width ?? double.infinity,
|
||||||
height: height ?? double.infinity,
|
height: height ?? double.infinity,
|
||||||
|
alignment: Alignment.center,
|
||||||
maxWidthDiskCache: ((cacheW ?? width!) * pr).toInt(),
|
maxWidthDiskCache: ((cacheW ?? width!) * pr).toInt(),
|
||||||
// maxHeightDiskCache: (cacheH ?? height!).toInt(),
|
// maxHeightDiskCache: (cacheH ?? height!).toInt(),
|
||||||
memCacheWidth: ((cacheW ?? width!) * pr).toInt(),
|
memCacheWidth: ((cacheW ?? width!) * pr).toInt(),
|
||||||
|
|||||||
@ -1,255 +0,0 @@
|
|||||||
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, 14),
|
|
||||||
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: 34,
|
|
||||||
height: 34,
|
|
||||||
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!,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
maxLines: 1,
|
|
||||||
style: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.titleSmall!
|
|
||||||
.copyWith(
|
|
||||||
color: isUp!
|
|
||||||
? Theme.of(context).colorScheme.primary
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 6),
|
|
||||||
Image.asset(
|
|
||||||
'assets/images/lv/lv${replyItem!.member!.level}.png',
|
|
||||||
height: 13,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
Utils.dateFormat(replyItem!.ctime),
|
|
||||||
style: Theme.of(context).textTheme.labelSmall!.copyWith(
|
|
||||||
color: Theme.of(context).colorScheme.outline),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// SizedBox(
|
|
||||||
// width: 35,
|
|
||||||
// height: 35,
|
|
||||||
// child: IconButton(
|
|
||||||
// padding: const EdgeInsets.all(2.0),
|
|
||||||
// icon: const Icon(Icons.more_horiz_outlined, size: 18.0),
|
|
||||||
// onPressed: () {},
|
|
||||||
// ),
|
|
||||||
// )
|
|
||||||
],
|
|
||||||
),
|
|
||||||
// title
|
|
||||||
Container(
|
|
||||||
margin: const EdgeInsets.only(top: 6, left: 45, right: 8),
|
|
||||||
child: SelectionArea(
|
|
||||||
child: Text(
|
|
||||||
replyItem!.content!.message!,
|
|
||||||
style: const TextStyle(height: 1.8),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
bottonAction(context),
|
|
||||||
// Text(replyItem!.replies!.length.toString()),
|
|
||||||
if (replyItem!.replies!.isNotEmpty)
|
|
||||||
ReplyItemRow(
|
|
||||||
replies: replyItem!.replies,
|
|
||||||
replyControl: replyItem!.replyControl,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 感谢、回复、复制
|
|
||||||
Widget bottonAction(context) {
|
|
||||||
var color = Theme.of(context).colorScheme.outline;
|
|
||||||
return Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const SizedBox(width: 42),
|
|
||||||
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)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ReplyItemRow extends StatelessWidget {
|
|
||||||
ReplyItemRow({super.key, this.replies, this.replyControl});
|
|
||||||
List? replies;
|
|
||||||
var replyControl;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
bool isShow = replyControl.isShow;
|
|
||||||
int extraRow = replyControl != null && isShow ? 1 : 0;
|
|
||||||
return Container(
|
|
||||||
margin: const EdgeInsets.only(left: 45, right: 10),
|
|
||||||
padding: const EdgeInsets.only(top: 4, bottom: 4),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(6),
|
|
||||||
),
|
|
||||||
child: Material(
|
|
||||||
color: Theme.of(context).colorScheme.onInverseSurface.withOpacity(0.7),
|
|
||||||
borderRadius: BorderRadius.circular(6),
|
|
||||||
clipBehavior: Clip.hardEdge,
|
|
||||||
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 ListTile(
|
|
||||||
onTap: () {},
|
|
||||||
dense: true,
|
|
||||||
contentPadding: const EdgeInsets.only(left: 10, right: 6),
|
|
||||||
title: Text.rich(
|
|
||||||
TextSpan(
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
fontSize:
|
|
||||||
Theme.of(context).textTheme.titleSmall!.fontSize,
|
|
||||||
),
|
|
||||||
children: [
|
|
||||||
if (replyControl.upReply) const TextSpan(text: 'up回复了'),
|
|
||||||
if (replyControl.isUpTop) const TextSpan(text: 'up点赞了'),
|
|
||||||
TextSpan(text: replyControl.entryText)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return ListTile(
|
|
||||||
onTap: () {},
|
|
||||||
dense: true,
|
|
||||||
contentPadding: const EdgeInsets.only(left: 10, right: 6),
|
|
||||||
title: Text.rich(
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
maxLines: 2,
|
|
||||||
TextSpan(
|
|
||||||
children: [
|
|
||||||
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('跳转至用户主页')}),
|
|
||||||
TextSpan(
|
|
||||||
text: replies![index].content.message,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize:
|
|
||||||
Theme.of(context).textTheme.titleSmall!.fontSize,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,10 +1,11 @@
|
|||||||
class ReplyContent {
|
class ReplyContent {
|
||||||
ReplyContent({
|
ReplyContent({
|
||||||
this.message,
|
this.message,
|
||||||
this.atNameToMid, // @的用户的mid
|
this.atNameToMid, // @的用户的mid null
|
||||||
this.memebers, // 被@的用户List 如果有的话
|
this.memebers, // 被@的用户List 如果有的话 []
|
||||||
this.emote, // 表情包 如果有的话
|
this.emote, // 表情包 如果有的话 null
|
||||||
this.jumpUrl,
|
this.jumpUrl, // {}
|
||||||
|
this.pictures, // {}
|
||||||
});
|
});
|
||||||
|
|
||||||
String? message;
|
String? message;
|
||||||
@ -12,12 +13,14 @@ class ReplyContent {
|
|||||||
List? memebers;
|
List? memebers;
|
||||||
Map? emote;
|
Map? emote;
|
||||||
Map? jumpUrl;
|
Map? jumpUrl;
|
||||||
|
List? pictures;
|
||||||
|
|
||||||
ReplyContent.fromJson(Map<String, dynamic> json) {
|
ReplyContent.fromJson(Map<String, dynamic> json) {
|
||||||
message = json['message'];
|
message = json['message'];
|
||||||
atNameToMid = json['at_name_to_mid'];
|
atNameToMid = json['at_name_to_mid'] ?? {};
|
||||||
memebers = json['memebers'];
|
memebers = json['memebers'] ?? [];
|
||||||
emote = json['emote'];
|
emote = json['emote'] ?? {};
|
||||||
jumpUrl = json['jumpUrl'];
|
jumpUrl = json['jumpUrl'] ?? {};
|
||||||
|
pictures = json['pictures'] ?? [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,18 +15,22 @@ class ReplyData {
|
|||||||
|
|
||||||
ReplyPage? page;
|
ReplyPage? page;
|
||||||
ReplyConfig? config;
|
ReplyConfig? config;
|
||||||
late List? replies;
|
late List<ReplyItemModel>? replies;
|
||||||
late List? topReplies;
|
late List<ReplyItemModel>? topReplies;
|
||||||
ReplyUpper? upper;
|
ReplyUpper? upper;
|
||||||
|
|
||||||
ReplyData.fromJson(Map<String, dynamic> json) {
|
ReplyData.fromJson(Map<String, dynamic> json) {
|
||||||
page = ReplyPage.fromJson(json['page']);
|
page = ReplyPage.fromJson(json['page']);
|
||||||
config = ReplyConfig.fromJson(json['config']);
|
config = ReplyConfig.fromJson(json['config']);
|
||||||
replies =
|
replies = json['replies']
|
||||||
json['replies'].map((item) => ReplyItemModel.fromJson(item)).toList();
|
.map<ReplyItemModel>(
|
||||||
|
(item) => ReplyItemModel.fromJson(item, json['upper']['mid']))
|
||||||
|
.toList();
|
||||||
topReplies = json['top_replies'] != null
|
topReplies = json['top_replies'] != null
|
||||||
? json['top_replies']
|
? json['top_replies']
|
||||||
.map((item) => ReplyItemModel.fromJson(item))
|
.map<ReplyItemModel>((item) => ReplyItemModel.fromJson(
|
||||||
|
item, json['upper']['mid'],
|
||||||
|
isTopStatus: true))
|
||||||
.toList()
|
.toList()
|
||||||
: [];
|
: [];
|
||||||
upper = ReplyUpper.fromJson(json['upper']);
|
upper = ReplyUpper.fromJson(json['upper']);
|
||||||
|
|||||||
@ -28,6 +28,8 @@ class ReplyItemModel {
|
|||||||
this.upAction,
|
this.upAction,
|
||||||
this.invisible,
|
this.invisible,
|
||||||
this.replyControl,
|
this.replyControl,
|
||||||
|
this.isUp,
|
||||||
|
this.isTop,
|
||||||
});
|
});
|
||||||
|
|
||||||
int? rpid;
|
int? rpid;
|
||||||
@ -55,8 +57,11 @@ class ReplyItemModel {
|
|||||||
UpAction? upAction;
|
UpAction? upAction;
|
||||||
bool? invisible;
|
bool? invisible;
|
||||||
ReplyControl? replyControl;
|
ReplyControl? replyControl;
|
||||||
|
bool? isUp;
|
||||||
|
bool? isTop = false;
|
||||||
|
|
||||||
ReplyItemModel.fromJson(Map<String, dynamic> json) {
|
ReplyItemModel.fromJson(Map<String, dynamic> json, upperMid,
|
||||||
|
{isTopStatus = false}) {
|
||||||
rpid = json['rpid'];
|
rpid = json['rpid'];
|
||||||
oid = json['oid'];
|
oid = json['oid'];
|
||||||
type = json['type'];
|
type = json['type'];
|
||||||
@ -78,7 +83,9 @@ class ReplyItemModel {
|
|||||||
member = ReplyMember.fromJson(json['member']);
|
member = ReplyMember.fromJson(json['member']);
|
||||||
content = ReplyContent.fromJson(json['content']);
|
content = ReplyContent.fromJson(json['content']);
|
||||||
replies = json['replies'] != null
|
replies = json['replies'] != null
|
||||||
? json['replies'].map((item) => ReplyItemModel.fromJson(item)).toList()
|
? json['replies']
|
||||||
|
.map((item) => ReplyItemModel.fromJson(item, upperMid))
|
||||||
|
.toList()
|
||||||
: [];
|
: [];
|
||||||
assist = json['assist'];
|
assist = json['assist'];
|
||||||
upAction = UpAction.fromJson(json['up_action']);
|
upAction = UpAction.fromJson(json['up_action']);
|
||||||
@ -86,6 +93,8 @@ class ReplyItemModel {
|
|||||||
replyControl = json['reply_control'] == null
|
replyControl = json['reply_control'] == null
|
||||||
? null
|
? null
|
||||||
: ReplyControl.fromJson(json['reply_control']);
|
: ReplyControl.fromJson(json['reply_control']);
|
||||||
|
isUp = upperMid.toString() == json['member']['mid'];
|
||||||
|
isTop = isTopStatus;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,7 +135,7 @@ class ReplyControl {
|
|||||||
upReply = json['up_reply'] ?? false;
|
upReply = json['up_reply'] ?? false;
|
||||||
isUpTop = json['is_up_top'] ?? false;
|
isUpTop = json['is_up_top'] ?? false;
|
||||||
upLike = json['up_like'] ?? false;
|
upLike = json['up_like'] ?? false;
|
||||||
if (json['sub_reply_entry_text'] == null) {
|
if (json['sub_reply_entry_text'] != null) {
|
||||||
final RegExp regex = RegExp(r"\d+");
|
final RegExp regex = RegExp(r"\d+");
|
||||||
final RegExpMatch match = regex.firstMatch(
|
final RegExpMatch match = regex.firstMatch(
|
||||||
json['sub_reply_entry_text'] == null
|
json['sub_reply_entry_text'] == null
|
||||||
|
|||||||
@ -12,7 +12,7 @@ class ReplyUpper {
|
|||||||
ReplyUpper.fromJson(Map<String, dynamic> json) {
|
ReplyUpper.fromJson(Map<String, dynamic> json) {
|
||||||
mid = json['mid'];
|
mid = json['mid'];
|
||||||
top = json['top'] != null
|
top = json['top'] != null
|
||||||
? ReplyItemModel.fromJson(json['top'])
|
? ReplyItemModel.fromJson(json['top'], json['mid'])
|
||||||
: ReplyItemModel();
|
: ReplyItemModel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,8 +2,9 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/skeleton/video_card_h.dart';
|
import 'package:pilipala/common/skeleton/video_card_h.dart';
|
||||||
import 'package:pilipala/common/widgets/http_error.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 'controller.dart';
|
||||||
|
import 'widgets/reply_item.dart';
|
||||||
|
|
||||||
class VideoReplyPanel extends StatefulWidget {
|
class VideoReplyPanel extends StatefulWidget {
|
||||||
const VideoReplyPanel({super.key});
|
const VideoReplyPanel({super.key});
|
||||||
@ -28,8 +29,24 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
|||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.connectionState == ConnectionState.done) {
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
if (snapshot.data['status']) {
|
if (snapshot.data['status']) {
|
||||||
List<dynamic> replies = snapshot.data['data'].replies;
|
List<ReplyItemModel> replies = snapshot.data['data'].replies;
|
||||||
replies.addAll(snapshot.data['data'].topReplies);
|
// 添加置顶回复
|
||||||
|
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(
|
return SliverList(
|
||||||
delegate: SliverChildBuilderDelegate((context, index) {
|
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