From a0441aa5899b4b7ba59d82a1125e5a7e7f595a8b Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 23 Apr 2023 15:50:51 +0800 Subject: [PATCH] =?UTF-8?q?=E8=AF=84=E8=AE=BA=E6=95=B0=E6=8D=AE=E6=B8=B2?= =?UTF-8?q?=E6=9F=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/images/lv/lv0.png | Bin 0 -> 514 bytes assets/images/lv/lv1.png | Bin 0 -> 524 bytes assets/images/lv/lv2.png | Bin 0 -> 518 bytes assets/images/lv/lv3.png | Bin 0 -> 541 bytes assets/images/lv/lv4.png | Bin 0 -> 498 bytes assets/images/lv/lv5.png | Bin 0 -> 539 bytes assets/images/lv/lv6.png | Bin 0 -> 517 bytes lib/common/widgets/reply_item.dart | 178 +++++++++++++++++++ lib/http/reply.dart | 5 +- lib/models/video/reply/config.dart | 17 ++ lib/models/video/reply/content.dart | 23 +++ lib/models/video/reply/data.dart | 34 ++++ lib/models/video/reply/item.dart | 125 +++++++++++++ lib/models/video/reply/member.dart | 55 ++++++ lib/models/video/reply/page.dart | 20 +++ lib/models/video/reply/top_replies.dart | 1 + lib/models/video/reply/upper.dart | 18 ++ lib/pages/video/detail/reply/controller.dart | 6 + lib/pages/video/detail/reply/view.dart | 45 ++++- pubspec.yaml | 1 + 20 files changed, 525 insertions(+), 3 deletions(-) create mode 100644 assets/images/lv/lv0.png create mode 100644 assets/images/lv/lv1.png create mode 100644 assets/images/lv/lv2.png create mode 100644 assets/images/lv/lv3.png create mode 100644 assets/images/lv/lv4.png create mode 100644 assets/images/lv/lv5.png create mode 100644 assets/images/lv/lv6.png create mode 100644 lib/common/widgets/reply_item.dart create mode 100644 lib/models/video/reply/config.dart create mode 100644 lib/models/video/reply/content.dart create mode 100644 lib/models/video/reply/data.dart create mode 100644 lib/models/video/reply/item.dart create mode 100644 lib/models/video/reply/member.dart create mode 100644 lib/models/video/reply/page.dart create mode 100644 lib/models/video/reply/top_replies.dart create mode 100644 lib/models/video/reply/upper.dart diff --git a/assets/images/lv/lv0.png b/assets/images/lv/lv0.png new file mode 100644 index 0000000000000000000000000000000000000000..3b9999cf730585d4184b92ca9ffd87a1cd4bc024 GIT binary patch literal 514 zcmV+d0{#7oP);Nu2kPew*oUc(brv1yaRN2+n{V<90K8g!HY`%D|L0iXF{v$nz$cUM;S%e0Ds7Vf69Sglx<9yD{vOHv$^8zvWkc}UT;>? z7kDvso~-#gpfR_=+0~?Osa5NnuLHUZ+nE*qn$Q4){eG{hMdgs}q7+!kiU35WCe>_t zUIpkKal^6n-k+6Y7I=ShyLIsJ4!NTt@mGe^JwXHB4{jl%3uVkkUH||907*qoM6N<$ Ef&;MT>Hq)$ literal 0 HcmV?d00001 diff --git a/assets/images/lv/lv1.png b/assets/images/lv/lv1.png new file mode 100644 index 0000000000000000000000000000000000000000..9973e4e7e26d2354e4b6eb18e2ef47f381e70ab3 GIT binary patch literal 524 zcmV+n0`vWeP)_Jh<1iAI49-AcZs9Rb_RBf8vh?0QMHpRBHte$%?v4 zMh6~~EYu%;){7`;PL?dWcI49ywOB$o0&3Jky~rk*;y+qz(WF z%gp)!o-G3|l}}$Kql@L59>6Is6ZqV@{b%rGg50TBRaIU3!-+s=B$IDzma#d<`RND% O0000r1wA}>f3=TAf|siM4nUGTKz{+j8&t2=*Cr_?)}<4^EMHmgOkoZC%H0A;B#C zUIIUjykMD=u2Xd*a_{1{D1k46-cipPoAEkcmWQJPc(Q;3-jNike`wtXM5jKCw80}U zh!v{qLIu7;=+d~#V!T=E*T7`5uL8P?7-isEr>z1&E9VWNjZcAPG4?)xzIM4B&}{}1 zMSz)rb%lT3KYdG+auK5p{MnB`I{2}J+`m?41MhYOIt4!d018W?4)GF+*#H0l07*qo IM6N<$g6IL`%>V!Z literal 0 HcmV?d00001 diff --git a/assets/images/lv/lv3.png b/assets/images/lv/lv3.png new file mode 100644 index 0000000000000000000000000000000000000000..54e08d2d733d5569f51a802308a521fc02e65ce3 GIT binary patch literal 541 zcmV+&0^+y=}C4!WxV zz^ozLx5EyDPa3rCJ3OrB4KLI+a5!O_Ede~i4s8JB2J;pGMrYsvs3p*kjCg@zq1*y^ zD}Eco9_>vhGUEN3TL!)ZumX@6tn~%81pH5rD?roJ z9}d&(0AQ_vri^$wA0J7MuyvSb6WttV#9LMcj|!7>?z}Ge{vxbY;ki9Q7uJ@Y-47Ku zRSIrXj@PPx@V?2h%-_VYfw3C==d|fts7HOxaksBI9`ReGZUYlv6Fjfc)(F6Bh4F(n zLwzE+9NbGPbkJl!-X{1sOta|)d?bKB>(L9kwYh7Kn#XIP~Y2Zzy_fcgaPiM8-z^I3{a#>w?HyM z=?1CF0AzwDn~(|U!_hQ_y8u#1kAPo5$g=$5-wM#$XyHnPQeE&^$~mozsb$5ucj80vEn264T6V5?eTf>C7E4f26PR+ zE7s7dVO`ynp4ELYO>LF>tn}71AkggK2Ov1EtG^cXx5twxu7TG>D;bCV1y{Xjg8w)J zonG@mq)Cs*MN%ePY7(B4L?C2Xf`8H|cKUq7p25Ez?Y%&f8i~GzKqnX1M0l& zEr2$G`z?o=-@50o0eza6CPyd#d`YPuzpfiE3i=+`*}j1Fib2krF1L7QUyta1bIn%FHq`vQn`n-aje z4sanC2I*`Ce00y%g^b%A<6c)|d`4jPwd)EYr>eoA6wp})xuqH=G35^ z^{T}>Lj6*nw;bQVXHz}f&O`->c<{P1xGxzF9u)0r*130Z^^FH0>cZy|_;!!}quAw8 zFEsFS=w|3Rx(psD;O`aC&r9pvQqB2SCh3eW@NG!uw}J7}23qHrrnc+GQvI=xuNePI z27JZZ#9)++t&g6X- createState() => _ReplyItemState(); +} + +class _ReplyItemState extends State { + 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: [ + // 头像、昵称 + Row( + // 两端对齐 + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + // onTap: () => + // Get.toNamed('/member/${reply.userName}', parameters: { + // 'memberAvatar': reply.avatar, + // 'heroTag': reply.userName + heroTag, + // }), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + 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) + ], + ); + } +} diff --git a/lib/http/reply.dart b/lib/http/reply.dart index 1762ee4e..88000996 100644 --- a/lib/http/reply.dart +++ b/lib/http/reply.dart @@ -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: '请求错误', diff --git a/lib/models/video/reply/config.dart b/lib/models/video/reply/config.dart new file mode 100644 index 00000000..90574f7c --- /dev/null +++ b/lib/models/video/reply/config.dart @@ -0,0 +1,17 @@ +class ReplyConfig { + ReplyConfig({ + this.showtopic, + this.showUpFlag, + this.readOnly, + }); + + int? showtopic; + bool? showUpFlag; + bool? readOnly; + + ReplyConfig.fromJson(Map json) { + showtopic = json['showtopic']; + showUpFlag = json['show_up_flag']; + readOnly = json['read_only']; + } +} diff --git a/lib/models/video/reply/content.dart b/lib/models/video/reply/content.dart new file mode 100644 index 00000000..f924d3f2 --- /dev/null +++ b/lib/models/video/reply/content.dart @@ -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 json) { + message = json['message']; + atNameToMid = json['at_name_to_mid']; + memebers = json['memebers']; + emote = json['emote']; + jumpUrl = json['jumpUrl']; + } +} diff --git a/lib/models/video/reply/data.dart b/lib/models/video/reply/data.dart new file mode 100644 index 00000000..b47ff656 --- /dev/null +++ b/lib/models/video/reply/data.dart @@ -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 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']); + } +} diff --git a/lib/models/video/reply/item.dart b/lib/models/video/reply/item.dart new file mode 100644 index 00000000..f8c79187 --- /dev/null +++ b/lib/models/video/reply/item.dart @@ -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 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 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 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']; + } +} diff --git a/lib/models/video/reply/member.dart b/lib/models/video/reply/member.dart new file mode 100644 index 00000000..196f252b --- /dev/null +++ b/lib/models/video/reply/member.dart @@ -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 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 json) { + pid = json['pid']; + name = json['name']; + image = json['image']; + } +} diff --git a/lib/models/video/reply/page.dart b/lib/models/video/reply/page.dart new file mode 100644 index 00000000..771b0515 --- /dev/null +++ b/lib/models/video/reply/page.dart @@ -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 json) { + num = json['num']; + size = json['size']; + count = json['count']; + acount = json['acount']; + } +} diff --git a/lib/models/video/reply/top_replies.dart b/lib/models/video/reply/top_replies.dart new file mode 100644 index 00000000..f769a834 --- /dev/null +++ b/lib/models/video/reply/top_replies.dart @@ -0,0 +1 @@ +class ReplyTop {} diff --git a/lib/models/video/reply/upper.dart b/lib/models/video/reply/upper.dart new file mode 100644 index 00000000..530513aa --- /dev/null +++ b/lib/models/video/reply/upper.dart @@ -0,0 +1,18 @@ +import 'item.dart'; + +class ReplyUpper { + ReplyUpper({ + this.mid, + this.top, + }); + + int? mid; + ReplyItemModel? top; + + ReplyUpper.fromJson(Map json) { + mid = json['mid']; + top = json['top'] != null + ? ReplyItemModel.fromJson(json['top']) + : ReplyItemModel(); + } +} diff --git a/lib/pages/video/detail/reply/controller.dart b/lib/pages/video/detail/reply/controller.dart index d18a74b2..5a3a00e0 100644 --- a/lib/pages/video/detail/reply/controller.dart +++ b/lib/pages/video/detail/reply/controller.dart @@ -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; } } diff --git a/lib/pages/video/detail/reply/view.dart b/lib/pages/video/detail/reply/view.dart index 13661065..ff201b16 100644 --- a/lib/pages/video/detail/reply/view.dart +++ b/lib/pages/video/detail/reply/view.dart @@ -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 { + 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 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), + ); + } + }, ); } } diff --git a/pubspec.yaml b/pubspec.yaml index 6d3b671c..7eaab185 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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