diff --git a/assets/images/lv/lv0.png b/assets/images/lv/lv0.png new file mode 100644 index 00000000..3b9999cf Binary files /dev/null and b/assets/images/lv/lv0.png differ diff --git a/assets/images/lv/lv1.png b/assets/images/lv/lv1.png new file mode 100644 index 00000000..9973e4e7 Binary files /dev/null and b/assets/images/lv/lv1.png differ diff --git a/assets/images/lv/lv2.png b/assets/images/lv/lv2.png new file mode 100644 index 00000000..895653ec Binary files /dev/null and b/assets/images/lv/lv2.png differ diff --git a/assets/images/lv/lv3.png b/assets/images/lv/lv3.png new file mode 100644 index 00000000..54e08d2d Binary files /dev/null and b/assets/images/lv/lv3.png differ diff --git a/assets/images/lv/lv4.png b/assets/images/lv/lv4.png new file mode 100644 index 00000000..bdb5fd41 Binary files /dev/null and b/assets/images/lv/lv4.png differ diff --git a/assets/images/lv/lv5.png b/assets/images/lv/lv5.png new file mode 100644 index 00000000..6973c0a7 Binary files /dev/null and b/assets/images/lv/lv5.png differ diff --git a/assets/images/lv/lv6.png b/assets/images/lv/lv6.png new file mode 100644 index 00000000..14ba6181 Binary files /dev/null and b/assets/images/lv/lv6.png differ 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/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..42ccaded --- /dev/null +++ b/lib/models/video/reply/content.dart @@ -0,0 +1,26 @@ +class ReplyContent { + ReplyContent({ + this.message, + this.atNameToMid, // @的用户的mid null + this.memebers, // 被@的用户List 如果有的话 [] + this.emote, // 表情包 如果有的话 null + this.jumpUrl, // {} + this.pictures, // {} + }); + + String? message; + Map? atNameToMid; + 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['jump_url'] ?? {}; + pictures = json['pictures'] ?? []; + } +} diff --git a/lib/models/video/reply/data.dart b/lib/models/video/reply/data.dart new file mode 100644 index 00000000..3b94a008 --- /dev/null +++ b/lib/models/video/reply/data.dart @@ -0,0 +1,38 @@ +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, json['upper']['mid'])) + .toList(); + topReplies = json['top_replies'] != null + ? json['top_replies'] + .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 new file mode 100644 index 00000000..53b71b6e --- /dev/null +++ b/lib/models/video/reply/item.dart @@ -0,0 +1,154 @@ +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, + this.isUp, + this.isTop, + }); + + 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; + bool? isUp; + bool? isTop = false; + + ReplyItemModel.fromJson(Map json, upperMid, + {isTopStatus = false}) { + 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'] != null + ? json['replies'] + .map((item) => ReplyItemModel.fromJson(item, upperMid)) + .toList() + : []; + assist = json['assist']; + upAction = UpAction.fromJson(json['up_action']); + invisible = json['invisible']; + replyControl = json['reply_control'] == null + ? null + : ReplyControl.fromJson(json['reply_control']); + isUp = upperMid.toString() == json['member']['mid']; + isTop = isTopStatus; + } +} + +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.upLike, + this.isShow, + this.entryText, + this.titleText, + this.time, + this.location, + }); + + bool? upReply; + bool? isUpTop; + bool? upLike; + bool? isShow; + String? entryText; + String? titleText; + String? time; + String? location; + + ReplyControl.fromJson(Map json) { + 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']; + 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..1d1f6071 --- /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'], json['mid']) + : null; + } +} diff --git a/lib/pages/video/detail/reply/controller.dart b/lib/pages/video/detail/reply/controller.dart index d18a74b2..a823778f 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,9 @@ 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']); + } + return res; } } diff --git a/lib/pages/video/detail/reply/view.dart b/lib/pages/video/detail/reply/view.dart index 13661065..67341100 100644 --- a/lib/pages/video/detail/reply/view.dart +++ b/lib/pages/video/detail/reply/view.dart @@ -1,4 +1,10 @@ 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/models/video/reply/item.dart'; +import 'controller.dart'; +import 'widgets/reply_item.dart'; class VideoReplyPanel extends StatefulWidget { const VideoReplyPanel({super.key}); @@ -7,11 +13,68 @@ 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 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; + // 添加置顶回复 + 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) { + 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/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..27c22287 --- /dev/null +++ b/lib/pages/video/detail/reply/widgets/reply_item.dart @@ -0,0 +1,419 @@ +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, 6, 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), + 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.65), + 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 = []; + // 匹配表情 + String matchEmote = 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 ''; + }, + onNonMatch: (String str) { + // 匹配@用户 + 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, + ), + 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 TextSpan(text: '\n'), + ); + 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'], + ), + ), + ), + ); + } + } + // spanChilds.add(TextSpan(text: matchMember)); + return TextSpan(children: spanChilds); +} 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: 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