From a0441aa5899b4b7ba59d82a1125e5a7e7f595a8b Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 23 Apr 2023 15:50:51 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E8=AF=84=E8=AE=BA=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E6=B8=B2=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 From 1d97d1848d9ff469287aa0b962cc90d5af91b5d4 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 24 Apr 2023 21:27:36 +0800 Subject: [PATCH 2/4] =?UTF-8?q?mod:=20=E6=A5=BC=E4=B8=AD=E6=A5=BC=E5=9B=9E?= =?UTF-8?q?=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/common/widgets/reply_item.dart | 127 +++++++++++++---- lib/models/video/reply/item.dart | 28 +++- lib/pages/video/detail/reply/controller.dart | 1 - lib/pages/video/detail/reply/view.dart | 7 +- pubspec.lock | 140 +++++++++---------- 5 files changed, 202 insertions(+), 101 deletions(-) diff --git a/lib/common/widgets/reply_item.dart b/lib/common/widgets/reply_item.dart index 893bae18..f7b65ff5 100644 --- a/lib/common/widgets/reply_item.dart +++ b/lib/common/widgets/reply_item.dart @@ -1,28 +1,14 @@ +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 StatefulWidget { - ReplyItem({super.key, this.replyItem, this.isUp}); - ReplyItemModel? replyItem; - bool? isUp; - - @override - State createState() => _ReplyItemState(); -} - -class _ReplyItemState extends State { +class ReplyItem extends StatelessWidget { + ReplyItem({super.key, this.replyItem, required this.isUp}); ReplyItemModel? replyItem; bool isUp = false; - @override - void initState() { - super.initState(); - replyItem = widget.replyItem; - isUp = widget.isUp!; - } - @override Widget build(BuildContext context) { return InkWell( @@ -30,7 +16,7 @@ class _ReplyItemState extends State { child: Column( children: [ Padding( - padding: const EdgeInsets.fromLTRB(12, 8, 8, 4), + padding: const EdgeInsets.fromLTRB(12, 8, 8, 14), child: content(context), ), Divider( @@ -42,7 +28,7 @@ class _ReplyItemState extends State { ); } - Widget lfAvtar() { + Widget lfAvtar(context) { return Container( margin: const EdgeInsets.only(top: 5), clipBehavior: Clip.hardEdge, @@ -78,7 +64,7 @@ class _ReplyItemState extends State { crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ - lfAvtar(), + lfAvtar(context), const SizedBox(width: 12), Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -93,7 +79,7 @@ class _ReplyItemState extends State { .textTheme .titleSmall! .copyWith( - color: isUp + color: isUp! ? Theme.of(context).colorScheme.primary : null, ), @@ -136,18 +122,24 @@ class _ReplyItemState extends State { ), ), ), - bottonAction(), + bottonAction(context), + // Text(replyItem!.replies!.length.toString()), + if (replyItem!.replies!.isNotEmpty) + ReplyItemRow( + replies: replyItem!.replies, + replyControl: replyItem!.replyControl, + ) ], ); } // 感谢、回复、复制 - Widget bottonAction() { + Widget bottonAction(context) { var color = Theme.of(context).colorScheme.outline; return Row( - mainAxisAlignment: MainAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.start, children: [ - // const SizedBox(width: 42), + const SizedBox(width: 42), SizedBox( height: 35, child: TextButton( @@ -176,3 +168,88 @@ class _ReplyItemState extends State { ); } } + +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, + ), + ) + ], + ), + ), + ); + } + }, + ), + ), + ); + } +} diff --git a/lib/models/video/reply/item.dart b/lib/models/video/reply/item.dart index f8c79187..c012f73d 100644 --- a/lib/models/video/reply/item.dart +++ b/lib/models/video/reply/item.dart @@ -77,11 +77,15 @@ class ReplyItemModel { action = json['action']; member = ReplyMember.fromJson(json['member']); content = ReplyContent.fromJson(json['content']); - replies = json['replies']; + replies = json['replies'] != null + ? json['replies'].map((item) => ReplyItemModel.fromJson(item)).toList() + : []; assist = json['assist']; upAction = UpAction.fromJson(json['up_action']); invisible = json['invisible']; - replyControl = ReplyControl.fromJson(json['reply_control']); + replyControl = json['reply_control'] == null + ? null + : ReplyControl.fromJson(json['reply_control']); } } @@ -101,6 +105,8 @@ class ReplyControl { ReplyControl({ this.upReply, this.isUpTop, + this.upLike, + this.isShow, this.entryText, this.titleText, this.time, @@ -109,14 +115,28 @@ class ReplyControl { bool? upReply; bool? isUpTop; + bool? upLike; + bool? isShow; String? entryText; String? titleText; String? time; String? location; ReplyControl.fromJson(Map json) { - upReply = json['up_reply']; - isUpTop = json['is_up_top']; + upReply = json['up_reply'] ?? false; + isUpTop = json['is_up_top'] ?? false; + upLike = json['up_like'] ?? false; + if (json['sub_reply_entry_text'] == null) { + final RegExp regex = RegExp(r"\d+"); + final RegExpMatch match = regex.firstMatch( + json['sub_reply_entry_text'] == null + ? '' + : json['sub_reply_entry_text']!)!; + isShow = int.parse(match.group(0)!) >= 3; + } else { + isShow = false; + } + entryText = json['sub_reply_entry_text']; titleText = json['sub_reply_title_text']; time = json['time_desc']; diff --git a/lib/pages/video/detail/reply/controller.dart b/lib/pages/video/detail/reply/controller.dart index 5a3a00e0..a823778f 100644 --- a/lib/pages/video/detail/reply/controller.dart +++ b/lib/pages/video/detail/reply/controller.dart @@ -16,7 +16,6 @@ class VideoReplyController extends GetxController { 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 ff201b16..dd45ef11 100644 --- a/lib/pages/video/detail/reply/view.dart +++ b/lib/pages/video/detail/reply/view.dart @@ -12,10 +12,15 @@ class VideoReplyPanel extends StatefulWidget { State createState() => _VideoReplyPanelState(); } -class _VideoReplyPanelState extends State { +class _VideoReplyPanelState extends State + with AutomaticKeepAliveClientMixin { final VideoReplyController _videoReplyController = Get.put(VideoReplyController(), tag: Get.arguments['heroTag']); + // 添加页面缓存 + @override + bool get wantKeepAlive => true; + @override Widget build(BuildContext context) { return FutureBuilder( diff --git a/pubspec.lock b/pubspec.lock index cc2b687d..af1e0e5e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -6,7 +6,7 @@ packages: description: name: args sha256: "4cab82a83ffef80b262ddedf47a0a8e56ee6fbf7fe21e6e768b02792034dd440" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.4.0" async: @@ -14,7 +14,7 @@ packages: description: name: async sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.10.0" boolean_selector: @@ -22,7 +22,7 @@ packages: description: name: boolean_selector sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.1" cached_network_image: @@ -30,7 +30,7 @@ packages: description: name: cached_network_image sha256: fd3d0dc1d451f9a252b32d95d3f0c3c487bc41a75eba2e6097cb0b9c71491b15 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.2.3" cached_network_image_platform_interface: @@ -38,7 +38,7 @@ packages: description: name: cached_network_image_platform_interface sha256: bb2b8403b4ccdc60ef5f25c70dead1f3d32d24b9d6117cfc087f496b178594a7 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.0.0" cached_network_image_web: @@ -46,7 +46,7 @@ packages: description: name: cached_network_image_web sha256: b8eb814ebfcb4dea049680f8c1ffb2df399e4d03bf7a352c775e26fa06e02fa0 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.2" characters: @@ -54,7 +54,7 @@ packages: description: name: characters sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.2.1" clock: @@ -62,7 +62,7 @@ packages: description: name: clock sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.1" collection: @@ -70,7 +70,7 @@ packages: description: name: collection sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.17.0" connectivity_plus: @@ -78,7 +78,7 @@ packages: description: name: connectivity_plus sha256: d73575bb66216738db892f72ba67dc478bd3b5490fbbcf43644b57645eabc822 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.4" connectivity_plus_platform_interface: @@ -86,7 +86,7 @@ packages: description: name: connectivity_plus_platform_interface sha256: cf1d1c28f4416f8c654d7dc3cd638ec586076255d407cef3ddbdaf178272a71a - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.2.4" cookie_jar: @@ -94,7 +94,7 @@ packages: description: name: cookie_jar sha256: d1cc6516a190ba667941f722b6365d202caff3dacb38de24268b8d6ff1ec8a1d - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.1" crypto: @@ -102,7 +102,7 @@ packages: description: name: crypto sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.2" cupertino_icons: @@ -110,7 +110,7 @@ packages: description: name: cupertino_icons sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.5" dbus: @@ -118,7 +118,7 @@ packages: description: name: dbus sha256: "6f07cba3f7b3448d42d015bfd3d53fe12e5b36da2423f23838efc1d5fb31a263" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.7.8" dio: @@ -126,7 +126,7 @@ packages: description: name: dio sha256: "0894a098594263fe1caaba3520e3016d8a855caeb010a882273189cca10f11e9" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "5.1.1" dio_cookie_manager: @@ -134,7 +134,7 @@ packages: description: name: dio_cookie_manager sha256: b45f11c2fcbccf39c5952ab68910b3a155486c4fa730ceb4ce867c4943169ea1 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.4" dio_http2_adapter: @@ -142,7 +142,7 @@ packages: description: name: dio_http2_adapter sha256: b06a02faaff972c4809c4ada7a2f71f6c74ce21f0feee79b357f2a9590c049d4 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.2.0" dynamic_color: @@ -150,7 +150,7 @@ packages: description: name: dynamic_color sha256: bbebb1b7ebed819e0ec83d4abdc2a8482d934f6a85289ffc1c6acf7589fa2aad - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.6.3" fake_async: @@ -158,7 +158,7 @@ packages: description: name: fake_async sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.3.1" ffi: @@ -166,7 +166,7 @@ packages: description: name: ffi sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.0.1" file: @@ -174,7 +174,7 @@ packages: description: name: file sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.1.4" flutter: @@ -187,7 +187,7 @@ packages: description: name: flutter_blurhash sha256: "05001537bd3fac7644fa6558b09ec8c0a3f2eba78c0765f88912882b1331a5c6" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.7.0" flutter_cache_manager: @@ -195,7 +195,7 @@ packages: description: name: flutter_cache_manager sha256: "32cd900555219333326a2d0653aaaf8671264c29befa65bbd9856d204a4c9fb3" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.3.0" flutter_lints: @@ -203,7 +203,7 @@ packages: description: name: flutter_lints sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.0.1" flutter_test: @@ -221,7 +221,7 @@ packages: description: name: get sha256: "2ba20a47c8f1f233bed775ba2dd0d3ac97b4cf32fc17731b3dfc672b06b0e92a" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.6.5" http: @@ -229,7 +229,7 @@ packages: description: name: http sha256: "6aa2946395183537c8b880962d935877325d6a09a2867c3970c05c0fed6ac482" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.13.5" http2: @@ -237,7 +237,7 @@ packages: description: name: http2 sha256: "58805ebc6513eed3b98ee0a455a8357e61d187bf2e0fdc1e53120770f78de258" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.0.1" http_parser: @@ -245,7 +245,7 @@ packages: description: name: http_parser sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.0.2" js: @@ -253,7 +253,7 @@ packages: description: name: js sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.6.5" lints: @@ -261,7 +261,7 @@ packages: description: name: lints sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.0.1" matcher: @@ -269,7 +269,7 @@ packages: description: name: matcher sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.12.13" material_color_utilities: @@ -277,7 +277,7 @@ packages: description: name: material_color_utilities sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.2.0" meta: @@ -285,7 +285,7 @@ packages: description: name: meta sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.8.0" nm: @@ -293,7 +293,7 @@ packages: description: name: nm sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.5.0" octo_image: @@ -301,7 +301,7 @@ packages: description: name: octo_image sha256: "107f3ed1330006a3bea63615e81cf637433f5135a52466c7caa0e7152bca9143" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.2" path: @@ -309,7 +309,7 @@ packages: description: name: path sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.8.2" path_provider: @@ -317,7 +317,7 @@ packages: description: name: path_provider sha256: c7edf82217d4b2952b2129a61d3ad60f1075b9299e629e149a8d2e39c2e6aad4 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.0.14" path_provider_android: @@ -325,7 +325,7 @@ packages: description: name: path_provider_android sha256: da97262be945a72270513700a92b39dd2f4a54dad55d061687e2e37a6390366a - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.0.25" path_provider_foundation: @@ -333,7 +333,7 @@ packages: description: name: path_provider_foundation sha256: ad4c4d011830462633f03eb34445a45345673dfd4faf1ab0b4735fbd93b19183 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.2.2" path_provider_linux: @@ -341,7 +341,7 @@ packages: description: name: path_provider_linux sha256: "2ae08f2216225427e64ad224a24354221c2c7907e448e6e0e8b57b1eb9f10ad1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.10" path_provider_platform_interface: @@ -349,7 +349,7 @@ packages: description: name: path_provider_platform_interface sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.0.6" path_provider_windows: @@ -357,7 +357,7 @@ packages: description: name: path_provider_windows sha256: f53720498d5a543f9607db4b0e997c4b5438884de25b0f73098cc2671a51b130 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.5" pedantic: @@ -365,7 +365,7 @@ packages: description: name: pedantic sha256: "67fc27ed9639506c856c840ccce7594d0bdcd91bc8d53d6e52359449a1d50602" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.11.1" petitparser: @@ -373,7 +373,7 @@ packages: description: name: petitparser sha256: "49392a45ced973e8d94a85fdb21293fbb40ba805fc49f2965101ae748a3683b4" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "5.1.0" platform: @@ -381,7 +381,7 @@ packages: description: name: platform sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.1.0" plugin_platform_interface: @@ -389,7 +389,7 @@ packages: description: name: plugin_platform_interface sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.4" process: @@ -397,7 +397,7 @@ packages: description: name: process sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.2.4" rxdart: @@ -405,7 +405,7 @@ packages: description: name: rxdart sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.27.7" sky_engine: @@ -418,31 +418,31 @@ packages: description: name: source_span sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.9.1" sqflite: dependency: transitive description: name: sqflite - sha256: "500d6fec583d2c021f2d25a056d96654f910662c64f836cd2063167b8f1fa758" - url: "https://pub.flutter-io.cn" + sha256: e7dfb6482d5d02b661d0b2399efa72b98909e5aa7b8336e1fb37e226264ade00 + url: "https://pub.dev" source: hosted - version: "2.2.6" + version: "2.2.7" sqflite_common: dependency: transitive description: name: sqflite_common - sha256: "963dad8c4aa2f814ce7d2d5b1da2f36f31bd1a439d8f27e3dc189bb9d26bc684" - url: "https://pub.flutter-io.cn" + sha256: "220831bf0bd5333ff2445eee35ec131553b804e6b5d47a4a37ca6f5eb66e282c" + url: "https://pub.dev" source: hosted - version: "2.4.3" + version: "2.4.4" stack_trace: dependency: transitive description: name: stack_trace sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.11.0" stream_channel: @@ -450,7 +450,7 @@ packages: description: name: stream_channel sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.1" string_scanner: @@ -458,23 +458,23 @@ packages: description: name: string_scanner sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.2.0" synchronized: dependency: transitive description: name: synchronized - sha256: "33b31b6beb98100bf9add464a36a8dd03eb10c7a8cf15aeec535e9b054aaf04b" - url: "https://pub.flutter-io.cn" + sha256: "5fcbd27688af6082f5abd611af56ee575342c30e87541d0245f7ff99faa02c60" + url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.1.0" term_glyph: dependency: transitive description: name: term_glyph sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.2.1" test_api: @@ -482,7 +482,7 @@ packages: description: name: test_api sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.4.16" typed_data: @@ -490,7 +490,7 @@ packages: description: name: typed_data sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.3.1" uuid: @@ -498,7 +498,7 @@ packages: description: name: uuid sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.7" vector_math: @@ -506,7 +506,7 @@ packages: description: name: vector_math sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.4" win32: @@ -514,7 +514,7 @@ packages: description: name: win32 sha256: a6f0236dbda0f63aa9a25ad1ff9a9d8a4eaaa5012da0dc59d21afdb1dc361ca4 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.1.4" xdg_directories: @@ -522,7 +522,7 @@ packages: description: name: xdg_directories sha256: ee1505df1426458f7f60aac270645098d318a8b4766d85fde75f76f2e21807d1 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.0" xml: @@ -530,7 +530,7 @@ packages: description: name: xml sha256: "979ee37d622dec6365e2efa4d906c37470995871fe9ae080d967e192d88286b5" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.2.2" sdks: From 0ec926839c231895459a592b76886a9f258d93c2 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Wed, 26 Apr 2023 22:09:39 +0800 Subject: [PATCH 3/4] =?UTF-8?q?mod:=20=E8=AF=84=E8=AE=BA=E8=A1=A8=E6=83=85?= =?UTF-8?q?=E6=B8=B2=E6=9F=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ios/Podfile.lock | 2 +- lib/common/widgets/network_img_layer.dart | 1 + lib/common/widgets/reply_item.dart | 255 ---------- lib/models/video/reply/content.dart | 19 +- lib/models/video/reply/data.dart | 14 +- lib/models/video/reply/item.dart | 15 +- lib/models/video/reply/upper.dart | 2 +- lib/pages/video/detail/reply/view.dart | 23 +- .../detail/reply/widgets/reply_item.dart | 448 ++++++++++++++++++ 9 files changed, 503 insertions(+), 276 deletions(-) delete mode 100644 lib/common/widgets/reply_item.dart create mode 100644 lib/pages/video/detail/reply/widgets/reply_item.dart diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 14e569a5..e467820a 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -45,4 +45,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 -COCOAPODS: 1.11.3 +COCOAPODS: 1.12.1 diff --git a/lib/common/widgets/network_img_layer.dart b/lib/common/widgets/network_img_layer.dart index e8249b80..ce98c9a9 100644 --- a/lib/common/widgets/network_img_layer.dart +++ b/lib/common/widgets/network_img_layer.dart @@ -36,6 +36,7 @@ class NetworkImgLayer extends StatelessWidget { imageUrl: src!, width: width ?? double.infinity, height: height ?? double.infinity, + alignment: Alignment.center, maxWidthDiskCache: ((cacheW ?? width!) * pr).toInt(), // maxHeightDiskCache: (cacheH ?? height!).toInt(), memCacheWidth: ((cacheW ?? width!) * pr).toInt(), diff --git a/lib/common/widgets/reply_item.dart b/lib/common/widgets/reply_item.dart deleted file mode 100644 index f7b65ff5..00000000 --- a/lib/common/widgets/reply_item.dart +++ /dev/null @@ -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: [ - // 头像、昵称 - 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(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, - ), - ) - ], - ), - ), - ); - } - }, - ), - ), - ); - } -} diff --git a/lib/models/video/reply/content.dart b/lib/models/video/reply/content.dart index f924d3f2..83dbd556 100644 --- a/lib/models/video/reply/content.dart +++ b/lib/models/video/reply/content.dart @@ -1,10 +1,11 @@ class ReplyContent { ReplyContent({ this.message, - this.atNameToMid, // @的用户的mid - this.memebers, // 被@的用户List 如果有的话 - this.emote, // 表情包 如果有的话 - this.jumpUrl, + this.atNameToMid, // @的用户的mid null + this.memebers, // 被@的用户List 如果有的话 [] + this.emote, // 表情包 如果有的话 null + this.jumpUrl, // {} + this.pictures, // {} }); String? message; @@ -12,12 +13,14 @@ class ReplyContent { List? memebers; Map? emote; Map? jumpUrl; + List? pictures; ReplyContent.fromJson(Map json) { message = json['message']; - atNameToMid = json['at_name_to_mid']; - memebers = json['memebers']; - emote = json['emote']; - jumpUrl = json['jumpUrl']; + atNameToMid = json['at_name_to_mid'] ?? {}; + memebers = json['memebers'] ?? []; + emote = json['emote'] ?? {}; + jumpUrl = json['jumpUrl'] ?? {}; + pictures = json['pictures'] ?? []; } } diff --git a/lib/models/video/reply/data.dart b/lib/models/video/reply/data.dart index b47ff656..3b94a008 100644 --- a/lib/models/video/reply/data.dart +++ b/lib/models/video/reply/data.dart @@ -15,18 +15,22 @@ class ReplyData { ReplyPage? page; ReplyConfig? config; - late List? replies; - late List? topReplies; + 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(); + replies = json['replies'] + .map( + (item) => ReplyItemModel.fromJson(item, json['upper']['mid'])) + .toList(); topReplies = json['top_replies'] != null ? json['top_replies'] - .map((item) => ReplyItemModel.fromJson(item)) + .map((item) => ReplyItemModel.fromJson( + item, json['upper']['mid'], + isTopStatus: true)) .toList() : []; upper = ReplyUpper.fromJson(json['upper']); diff --git a/lib/models/video/reply/item.dart b/lib/models/video/reply/item.dart index c012f73d..53b71b6e 100644 --- a/lib/models/video/reply/item.dart +++ b/lib/models/video/reply/item.dart @@ -28,6 +28,8 @@ class ReplyItemModel { this.upAction, this.invisible, this.replyControl, + this.isUp, + this.isTop, }); int? rpid; @@ -55,8 +57,11 @@ class ReplyItemModel { UpAction? upAction; bool? invisible; ReplyControl? replyControl; + bool? isUp; + bool? isTop = false; - ReplyItemModel.fromJson(Map json) { + ReplyItemModel.fromJson(Map json, upperMid, + {isTopStatus = false}) { rpid = json['rpid']; oid = json['oid']; type = json['type']; @@ -78,7 +83,9 @@ class ReplyItemModel { member = ReplyMember.fromJson(json['member']); content = ReplyContent.fromJson(json['content']); replies = json['replies'] != null - ? json['replies'].map((item) => ReplyItemModel.fromJson(item)).toList() + ? json['replies'] + .map((item) => ReplyItemModel.fromJson(item, upperMid)) + .toList() : []; assist = json['assist']; upAction = UpAction.fromJson(json['up_action']); @@ -86,6 +93,8 @@ class ReplyItemModel { replyControl = json['reply_control'] == null ? null : ReplyControl.fromJson(json['reply_control']); + isUp = upperMid.toString() == json['member']['mid']; + isTop = isTopStatus; } } @@ -126,7 +135,7 @@ class ReplyControl { upReply = json['up_reply'] ?? false; isUpTop = json['is_up_top'] ?? 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 RegExpMatch match = regex.firstMatch( json['sub_reply_entry_text'] == null diff --git a/lib/models/video/reply/upper.dart b/lib/models/video/reply/upper.dart index 530513aa..4bdb62aa 100644 --- a/lib/models/video/reply/upper.dart +++ b/lib/models/video/reply/upper.dart @@ -12,7 +12,7 @@ class ReplyUpper { ReplyUpper.fromJson(Map json) { mid = json['mid']; top = json['top'] != null - ? ReplyItemModel.fromJson(json['top']) + ? ReplyItemModel.fromJson(json['top'], json['mid']) : ReplyItemModel(); } } diff --git a/lib/pages/video/detail/reply/view.dart b/lib/pages/video/detail/reply/view.dart index dd45ef11..67341100 100644 --- a/lib/pages/video/detail/reply/view.dart +++ b/lib/pages/video/detail/reply/view.dart @@ -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 builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { if (snapshot.data['status']) { - List replies = snapshot.data['data'].replies; - replies.addAll(snapshot.data['data'].topReplies); + List 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) { diff --git a/lib/pages/video/detail/reply/widgets/reply_item.dart b/lib/pages/video/detail/reply/widgets/reply_item.dart new file mode 100644 index 00000000..184ced82 --- /dev/null +++ b/lib/pages/video/detail/reply/widgets/reply_item.dart @@ -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: [ + // 头像、昵称 + 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(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 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); +} From 0aba791e2c68e3314bd9bce77815fc5b966af3b8 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Thu, 27 Apr 2023 13:54:55 +0800 Subject: [PATCH 4/4] =?UTF-8?q?mod:=20@=E7=94=A8=E6=88=B7=E6=B8=B2?= =?UTF-8?q?=E6=9F=93=E3=80=81jumpUrl=E6=B8=B2=E6=9F=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/models/video/reply/content.dart | 2 +- lib/models/video/reply/upper.dart | 2 +- .../detail/reply/widgets/reply_item.dart | 185 ++++++++---------- 3 files changed, 80 insertions(+), 109 deletions(-) diff --git a/lib/models/video/reply/content.dart b/lib/models/video/reply/content.dart index 83dbd556..42ccaded 100644 --- a/lib/models/video/reply/content.dart +++ b/lib/models/video/reply/content.dart @@ -20,7 +20,7 @@ class ReplyContent { atNameToMid = json['at_name_to_mid'] ?? {}; memebers = json['memebers'] ?? []; emote = json['emote'] ?? {}; - jumpUrl = json['jumpUrl'] ?? {}; + jumpUrl = json['jump_url'] ?? {}; pictures = json['pictures'] ?? []; } } diff --git a/lib/models/video/reply/upper.dart b/lib/models/video/reply/upper.dart index 4bdb62aa..1d1f6071 100644 --- a/lib/models/video/reply/upper.dart +++ b/lib/models/video/reply/upper.dart @@ -13,6 +13,6 @@ class ReplyUpper { mid = json['mid']; top = json['top'] != null ? ReplyItemModel.fromJson(json['top'], json['mid']) - : ReplyItemModel(); + : null; } } diff --git a/lib/pages/video/detail/reply/widgets/reply_item.dart b/lib/pages/video/detail/reply/widgets/reply_item.dart index 184ced82..27c22287 100644 --- a/lib/pages/video/detail/reply/widgets/reply_item.dart +++ b/lib/pages/video/detail/reply/widgets/reply_item.dart @@ -16,7 +16,7 @@ class ReplyItem extends StatelessWidget { child: Column( children: [ Padding( - padding: const EdgeInsets.fromLTRB(12, 8, 8, 0), + padding: const EdgeInsets.fromLTRB(12, 6, 8, 0), child: content(context), ), Divider( @@ -31,11 +31,6 @@ class ReplyItem extends StatelessWidget { 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, @@ -104,7 +99,7 @@ class ReplyItem extends StatelessWidget { focusNode: FocusNode(), selectionControls: MaterialTextSelectionControls(), child: Text.rich( - style: const TextStyle(height: 1.6), + style: const TextStyle(height: 1.65), TextSpan( children: [ buildContent(context, replyItem!.content!), @@ -301,65 +296,8 @@ InlineSpan buildContent(BuildContext context, content) { return TextSpan(text: content.message); } List 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( + // 匹配表情 + String matchEmote = content.message.splitMapJoin( RegExp(r"\[.*?\]"), onMatch: (Match match) { String matchStr = match[0]!; @@ -379,54 +317,86 @@ InlineSpan buildContent(BuildContext context, content) { return matchStr; } } - return matchStr; + return ''; }, 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('跳转至用户主页'), - }, + // 匹配@用户 + String matchMember = str; + if (content.atNameToMid.isNotEmpty) { + matchMember = 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, ), - ); - }); - } - 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; + recognizer: TapGestureRecognizer() + ..onTap = () => { + print('跳转至用户主页'), + }, + ), + ); + }); + } + return ''; + }, + onNonMatch: (String str) { + spanChilds.add(TextSpan(text: str)); + return str; + }, + ); + } else { + matchMember = str; } + + // 匹配 jumpUrl + String matchUrl = matchMember; + if (content.jumpUrl.isNotEmpty) { + List urlKeys = content.jumpUrl.keys.toList(); + matchUrl = matchMember.splitMapJoin( + RegExp("(?:${urlKeys.join("|")})"), + onMatch: (Match match) { + String matchStr = match[0]!; + // spanChilds.add(TextSpan(text: matchStr)); + spanChilds.add( + TextSpan( + text: content.jumpUrl[matchStr]['title'], + style: TextStyle( + color: Theme.of(context).colorScheme.primary, + ), + recognizer: TapGestureRecognizer() + ..onTap = () => { + print('Url 点击'), + }, + ), + ); + return ''; + }, + onNonMatch: (String str) { + spanChilds.add(TextSpan(text: str)); + return str; + }, + ); + } + + if (content.atNameToMid.isEmpty && content.jumpUrl.isEmpty) { + spanChilds.add(TextSpan(text: str)); + } + return str; }, ); + + // 图片渲染 if (content.pictures.isNotEmpty) { - spanChilds.add(const WidgetSpan( - child: SizedBox( - height: 4, - ))); + spanChilds.add( + const TextSpan(text: '\n'), + ); for (var i = 0; i < content.pictures.length; i++) { spanChilds.add( WidgetSpan( @@ -444,5 +414,6 @@ InlineSpan buildContent(BuildContext context, content) { ); } } + // spanChilds.add(TextSpan(text: matchMember)); return TextSpan(children: spanChilds); }