评论数据渲染

This commit is contained in:
guozhigq
2023-04-23 15:50:51 +08:00
parent 2fd1cc422b
commit a0441aa589
20 changed files with 525 additions and 3 deletions

BIN
assets/images/lv/lv0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 514 B

BIN
assets/images/lv/lv1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 524 B

BIN
assets/images/lv/lv2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 B

BIN
assets/images/lv/lv3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 541 B

BIN
assets/images/lv/lv4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 498 B

BIN
assets/images/lv/lv5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 539 B

BIN
assets/images/lv/lv6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 B

View File

@ -0,0 +1,178 @@
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 StatefulWidget {
ReplyItem({super.key, this.replyItem, this.isUp});
ReplyItemModel? replyItem;
bool? isUp;
@override
State<ReplyItem> createState() => _ReplyItemState();
}
class _ReplyItemState extends State<ReplyItem> {
ReplyItemModel? replyItem;
bool isUp = false;
@override
void initState() {
super.initState();
replyItem = widget.replyItem;
isUp = widget.isUp!;
}
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () {},
child: Column(
children: [
Padding(
padding: const EdgeInsets.fromLTRB(12, 8, 8, 4),
child: content(context),
),
Divider(
height: 1,
color: Theme.of(context).dividerColor.withOpacity(0.08),
)
],
),
);
}
Widget lfAvtar() {
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(),
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(),
],
);
}
// 感谢、回复、复制
Widget bottonAction() {
var color = Theme.of(context).colorScheme.outline;
return Row(
mainAxisAlignment: MainAxisAlignment.end,
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)
],
);
}
}

View File

@ -14,8 +14,11 @@ class ReplyHttp {
'type': type,
'sort': 1,
});
print(res);
if (res.data['code'] == 0) {
return {
'status': true,
'data': res.data['data'],
};
} else {
Map errMap = {
-400: '请求错误',

View File

@ -0,0 +1,17 @@
class ReplyConfig {
ReplyConfig({
this.showtopic,
this.showUpFlag,
this.readOnly,
});
int? showtopic;
bool? showUpFlag;
bool? readOnly;
ReplyConfig.fromJson(Map<String, dynamic> json) {
showtopic = json['showtopic'];
showUpFlag = json['show_up_flag'];
readOnly = json['read_only'];
}
}

View File

@ -0,0 +1,23 @@
class ReplyContent {
ReplyContent({
this.message,
this.atNameToMid, // @的用户的mid
this.memebers, // 被@的用户List 如果有的话
this.emote, // 表情包 如果有的话
this.jumpUrl,
});
String? message;
Map? atNameToMid;
List? memebers;
Map? emote;
Map? jumpUrl;
ReplyContent.fromJson(Map<String, dynamic> json) {
message = json['message'];
atNameToMid = json['at_name_to_mid'];
memebers = json['memebers'];
emote = json['emote'];
jumpUrl = json['jumpUrl'];
}
}

View File

@ -0,0 +1,34 @@
import 'package:pilipala/models/video/reply/item.dart';
import 'config.dart';
import 'page.dart';
import 'upper.dart';
class ReplyData {
ReplyData({
this.page,
this.config,
this.replies,
this.topReplies,
this.upper,
});
ReplyPage? page;
ReplyConfig? config;
late List? replies;
late List? topReplies;
ReplyUpper? upper;
ReplyData.fromJson(Map<String, dynamic> json) {
page = ReplyPage.fromJson(json['page']);
config = ReplyConfig.fromJson(json['config']);
replies =
json['replies'].map((item) => ReplyItemModel.fromJson(item)).toList();
topReplies = json['top_replies'] != null
? json['top_replies']
.map((item) => ReplyItemModel.fromJson(item))
.toList()
: [];
upper = ReplyUpper.fromJson(json['upper']);
}
}

View File

@ -0,0 +1,125 @@
import 'content.dart';
import 'member.dart';
class ReplyItemModel {
ReplyItemModel({
this.rpid,
this.oid,
this.type,
this.mid,
this.root,
this.parent,
this.dialog,
this.count,
this.floor,
this.state,
this.fansgrade,
this.attr,
this.ctime,
this.rpidStr,
this.rootStr,
this.parentStr,
this.like,
this.action,
this.member,
this.content,
this.replies,
this.assist,
this.upAction,
this.invisible,
this.replyControl,
});
int? rpid;
int? oid;
int? type;
int? mid;
int? root;
int? parent;
int? dialog;
int? count;
int? floor;
int? state;
int? fansgrade;
int? attr;
int? ctime;
String? rpidStr;
String? rootStr;
String? parentStr;
int? like;
int? action;
ReplyMember? member;
ReplyContent? content;
List? replies;
int? assist;
UpAction? upAction;
bool? invisible;
ReplyControl? replyControl;
ReplyItemModel.fromJson(Map<String, dynamic> json) {
rpid = json['rpid'];
oid = json['oid'];
type = json['type'];
mid = json['mid'];
root = json['root'];
parent = json['parent'];
dialog = json['dialog'];
count = json['count'];
floor = json['floor'];
state = json['state'];
fansgrade = json['fansgrade'];
attr = json['attr'];
ctime = json['ctime'];
rpidStr = json['rpid_str'];
rootStr = json['root_str'];
parentStr = json['parent_str'];
like = json['like'];
action = json['action'];
member = ReplyMember.fromJson(json['member']);
content = ReplyContent.fromJson(json['content']);
replies = json['replies'];
assist = json['assist'];
upAction = UpAction.fromJson(json['up_action']);
invisible = json['invisible'];
replyControl = ReplyControl.fromJson(json['reply_control']);
}
}
class UpAction {
UpAction({this.like, this.reply});
bool? like;
bool? reply;
UpAction.fromJson(Map<String, dynamic> json) {
like = json['like'];
reply = json['reply'];
}
}
class ReplyControl {
ReplyControl({
this.upReply,
this.isUpTop,
this.entryText,
this.titleText,
this.time,
this.location,
});
bool? upReply;
bool? isUpTop;
String? entryText;
String? titleText;
String? time;
String? location;
ReplyControl.fromJson(Map<String, dynamic> json) {
upReply = json['up_reply'];
isUpTop = json['is_up_top'];
entryText = json['sub_reply_entry_text'];
titleText = json['sub_reply_title_text'];
time = json['time_desc'];
location = json['location'];
}
}

View File

@ -0,0 +1,55 @@
import 'dart:convert' as convert;
class ReplyMember {
ReplyMember({
this.mid,
this.uname,
this.sign,
this.avatar,
this.level,
this.pendant,
this.officialVerify,
this.vip,
this.fansDetail,
});
String? mid;
String? uname;
String? sign;
String? avatar;
int? level;
Pendant? pendant;
Map? officialVerify;
Map? vip;
Map? fansDetail;
ReplyMember.fromJson(Map<String, dynamic> json) {
mid = json['mid'];
uname = json['uname'];
sign = json['sign'];
avatar = json['avatar'];
level = json['level_info']['current_level'];
pendant = Pendant.fromJson(json['pendant']);
officialVerify = json['officia_vVerify'];
vip = json['vip'];
fansDetail = json['fans_detail'];
}
}
class Pendant {
Pendant({
this.pid,
this.name,
this.image,
});
int? pid;
String? name;
String? image;
Pendant.fromJson(Map<String, dynamic> json) {
pid = json['pid'];
name = json['name'];
image = json['image'];
}
}

View File

@ -0,0 +1,20 @@
class ReplyPage {
ReplyPage({
this.num,
this.size,
this.count,
this.acount,
});
int? num;
int? size;
int? count;
int? acount;
ReplyPage.fromJson(Map<String, dynamic> json) {
num = json['num'];
size = json['size'];
count = json['count'];
acount = json['acount'];
}
}

View File

@ -0,0 +1 @@
class ReplyTop {}

View File

@ -0,0 +1,18 @@
import 'item.dart';
class ReplyUpper {
ReplyUpper({
this.mid,
this.top,
});
int? mid;
ReplyItemModel? top;
ReplyUpper.fromJson(Map<String, dynamic> json) {
mid = json['mid'];
top = json['top'] != null
? ReplyItemModel.fromJson(json['top'])
: ReplyItemModel();
}
}

View File

@ -1,5 +1,6 @@
import 'package:get/get.dart';
import 'package:pilipala/http/reply.dart';
import 'package:pilipala/models/video/reply/data.dart';
class VideoReplyController extends GetxController {
// 视频aid
@ -13,5 +14,10 @@ class VideoReplyController extends GetxController {
Future queryReplyList() async {
var res = await ReplyHttp.replyList(oid: aid, pageNum: 1, type: 1);
if (res['status']) {
res['data'] = ReplyData.fromJson(res['data']);
print(res['data'].replies);
}
return res;
}
}

View File

@ -1,4 +1,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 'controller.dart';
class VideoReplyPanel extends StatefulWidget {
const VideoReplyPanel({super.key});
@ -8,10 +13,46 @@ class VideoReplyPanel extends StatefulWidget {
}
class _VideoReplyPanelState extends State<VideoReplyPanel> {
final VideoReplyController _videoReplyController =
Get.put(VideoReplyController(), tag: Get.arguments['heroTag']);
@override
Widget build(BuildContext context) {
return const SliverToBoxAdapter(
child: Text('评论'),
return FutureBuilder(
future: _videoReplyController.queryReplyList(),
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);
// 请求成功
return SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
if (index == replies.length) {
return SizedBox(height: MediaQuery.of(context).padding.bottom);
} else {
return ReplyItem(
replyItem: replies[index],
isUp:
replies[index].mid == snapshot.data['data'].upper.mid);
}
}, childCount: replies.length + 1));
} else {
// 请求错误
return HttpError(
errMsg: snapshot.data['msg'],
fn: () => setState(() {}),
);
}
} else {
// 骨架屏
return SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
return const VideoCardHSkeleton();
}, childCount: 5),
);
}
},
);
}
}

View File

@ -76,6 +76,7 @@ flutter:
# To add assets to your application, add an assets section, like this:
assets:
- assets/images/
- assets/images/lv/
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware