diff --git a/lib/common/widgets/video_card_h.dart b/lib/common/widgets/video_card_h.dart index 1467c5e6..7d825f14 100644 --- a/lib/common/widgets/video_card_h.dart +++ b/lib/common/widgets/video_card_h.dart @@ -139,15 +139,36 @@ class VideoContent extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - videoItem.title, - textAlign: TextAlign.start, - style: TextStyle( - fontSize: Theme.of(context).textTheme.titleSmall!.fontSize, - fontWeight: FontWeight.w500), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), + if (videoItem.title is String) ...[ + Text( + videoItem.title, + textAlign: TextAlign.start, + style: TextStyle( + fontSize: Theme.of(context).textTheme.titleSmall!.fontSize, + fontWeight: FontWeight.w500), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ] else ...[ + RichText( + text: TextSpan( + children: [ + for (var i in videoItem.title) ...[ + TextSpan( + text: i['text'], + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w500, + color: i['type'] == 'em' + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.onSurface, + ), + ), + ] + ], + ), + ), + ], const Spacer(), if (videoItem.rcmdReason != null && videoItem.rcmdReason.content != '') diff --git a/lib/models/search/result.dart b/lib/models/search/result.dart index 94eafec8..8400b567 100644 --- a/lib/models/search/result.dart +++ b/lib/models/search/result.dart @@ -1,3 +1,5 @@ +import 'package:pilipala/utils/em.dart'; + class SearchVideoModel { SearchVideoModel({this.list}); List? list; @@ -50,7 +52,7 @@ class SearchVideoItemModel { String? arcurl; int? aid; String? bvid; - String? title; + List? title; String? description; String? pic; // String? play; @@ -76,7 +78,7 @@ class SearchVideoItemModel { arcurl = json['arcurl']; aid = json['aid']; bvid = json['bvid']; - title = json['title'].replaceAll(RegExp(r'<.*?>'), ''); + title = Em.regTitle(json['title']); description = json['description']; pic = 'https:${json['pic']}'; videoReview = json['video_review']; @@ -241,6 +243,7 @@ class SearchLiveItemModel { this.rankScore, this.roomid, this.attentions, + this.cateName, }); int? rankOffset; @@ -251,13 +254,14 @@ class SearchLiveItemModel { String? uface; String? userCover; String? type; - String? title; + List? title; String? cover; int? online; int? rankIndex; int? rankScore; int? roomid; int? attentions; + String? cateName; SearchLiveItemModel.fromJson(Map json) { rankOffset = json['rank_offset']; @@ -268,12 +272,13 @@ class SearchLiveItemModel { uface = json['uface']; userCover = json['user_cover']; type = json['type']; - title = json['title']; + title = Em.regTitle(json['title']); cover = json['cover']; online = json['online']; rankIndex = json['rank_index']; rankScore = json['rank_score']; roomid = json['roomid']; attentions = json['attentions']; + cateName = Em.regCate(json['cate_name']) ?? ''; } } diff --git a/lib/pages/searchPanel/view.dart b/lib/pages/searchPanel/view.dart index 1c004324..4da2396b 100644 --- a/lib/pages/searchPanel/view.dart +++ b/lib/pages/searchPanel/view.dart @@ -7,6 +7,9 @@ import 'package:pilipala/common/widgets/video_card_h.dart'; import 'package:pilipala/models/common/search_type.dart'; import 'controller.dart'; +import 'widgets/live_panerl.dart'; +import 'widgets/user_panel.dart'; +import 'widgets/video_panel.dart'; import 'widgets/userPanel.dart'; class SearchPanel extends StatefulWidget { @@ -63,28 +66,21 @@ class _SearchPanelState extends State builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { Map data = snapshot.data; + var ctr = _searchPanelController; + List list = ctr!.resultList; if (data['status']) { - return Obx( - () => ListView.builder( - controller: _searchPanelController!.scrollController, - addAutomaticKeepAlives: false, - addRepaintBoundaries: false, - itemCount: _searchPanelController!.resultList.length, - itemBuilder: (context, index) { - var i = _searchPanelController!.resultList[index]; - switch (widget.searchType) { - case SearchType.video: - return VideoCardH(videoItem: i); - case SearchType.bili_user: - return UserPanel(userItem: i); - case SearchType.live_room: - return LiveCard(liveItem: i); - default: - return const SizedBox(); - } - }, - ), - ); + return Obx(() { + switch (widget.searchType) { + case SearchType.video: + return searchVideoPanel(context, ctr, list); + case SearchType.bili_user: + return searchUserPanel(context, ctr, list); + case SearchType.live_room: + return searchLivePanel(context, ctr, list); + default: + return const SizedBox(); + } + }); } else { return CustomScrollView( physics: const NeverScrollableScrollPhysics(), diff --git a/lib/pages/searchPanel/widgets/live_panerl.dart b/lib/pages/searchPanel/widgets/live_panerl.dart new file mode 100644 index 00000000..96fd69ad --- /dev/null +++ b/lib/pages/searchPanel/widgets/live_panerl.dart @@ -0,0 +1,169 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:pilipala/common/constants.dart'; +import 'package:pilipala/common/widgets/network_img_layer.dart'; +import 'package:pilipala/pages/home/index.dart'; +import 'package:pilipala/utils/utils.dart'; + +Widget searchLivePanel(BuildContext context, ctr, list) { + return Padding( + padding: const EdgeInsets.only( + left: StyleString.cardSpace, right: StyleString.cardSpace), + child: GridView.builder( + primary: false, + controller: ctr!.scrollController, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: StyleString.cardSpace, + mainAxisSpacing: StyleString.cardSpace, + mainAxisExtent: + MediaQuery.of(context).size.width / 2 / StyleString.aspectRatio + + 65, + ), + itemCount: list.length, + itemBuilder: (context, index) { + var i = list![index]; + return Card( + elevation: 0.8, + clipBehavior: Clip.hardEdge, + shape: RoundedRectangleBorder( + borderRadius: StyleString.mdRadius, + ), + margin: EdgeInsets.zero, + child: InkWell( + onTap: () {}, + child: Column( + children: [ + AspectRatio( + aspectRatio: StyleString.aspectRatio, + child: LayoutBuilder(builder: (context, boxConstraints) { + double maxWidth = boxConstraints.maxWidth; + double maxHeight = boxConstraints.maxHeight; + double PR = MediaQuery.of(context).devicePixelRatio; + return Stack( + children: [ + Hero( + tag: Utils.makeHeroTag(i.roomid), + child: NetworkImgLayer( + // 指定图片尺寸 + // src: videoItem.pic + '@${(maxWidth * 2).toInt()}w', + src: i.cover + '@.webp', + type: 'emote', + width: maxWidth, + height: maxHeight, + ), + ), + Positioned( + left: 0, + right: 0, + bottom: 0, + child: AnimatedOpacity( + opacity: 1, + duration: const Duration(milliseconds: 200), + child: LiveStat( + online: i.online, + cateName: i.cateName, + ), + ), + ), + ], + ); + }), + ), + LiveContent(liveItem: i) + ], + ), + ), + ); + }, + ), + ); +} + +class LiveContent extends StatelessWidget { + final liveItem; + const LiveContent({Key? key, required this.liveItem}) : super(key: key); + @override + Widget build(BuildContext context) { + return Expanded( + child: Padding( + padding: const EdgeInsets.fromLTRB(8, 8, 6, 7), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + RichText( + text: TextSpan( + children: [ + for (var i in liveItem.title) ...[ + TextSpan( + text: i['text'], + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w500, + color: i['type'] == 'em' + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.onSurface, + ), + ), + ] + ], + ), + ), + SizedBox( + width: double.infinity, + child: Text( + liveItem.uname, + maxLines: 1, + style: TextStyle( + fontSize: Theme.of(context).textTheme.labelMedium!.fontSize, + color: Theme.of(context).colorScheme.outline, + ), + ), + ), + ], + ), + ), + ); + } +} + +class LiveStat extends StatelessWidget { + final int? online; + final String? cateName; + + const LiveStat({Key? key, required this.online, this.cateName}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + height: 45, + padding: const EdgeInsets.only(top: 22, left: 8, right: 8), + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.transparent, + Colors.black54, + ], + tileMode: TileMode.mirror, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + cateName!, + style: const TextStyle(fontSize: 11, color: Colors.white), + ), + Text( + '围观:${online.toString()}', + style: const TextStyle(fontSize: 11, color: Colors.white), + ) + ], + ), + ); + } +} diff --git a/lib/pages/searchPanel/widgets/user_panel.dart b/lib/pages/searchPanel/widgets/user_panel.dart new file mode 100644 index 00000000..fa81a98a --- /dev/null +++ b/lib/pages/searchPanel/widgets/user_panel.dart @@ -0,0 +1,69 @@ +import 'package:flutter/material.dart'; +import 'package:pilipala/common/widgets/network_img_layer.dart'; + +Widget searchUserPanel(BuildContext context, ctr, list) { + TextStyle style = TextStyle( + fontSize: Theme.of(context).textTheme.labelSmall!.fontSize, + color: Theme.of(context).colorScheme.outline); + return ListView.builder( + controller: ctr!.scrollController, + addAutomaticKeepAlives: false, + addRepaintBoundaries: false, + itemCount: list!.length, + itemBuilder: (context, index) { + var i = list![index]; + return InkWell( + onTap: () {}, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10), + child: Row( + children: [ + NetworkImgLayer( + width: 42, + height: 42, + src: i.upic, + type: 'avatar', + ), + const SizedBox(width: 10), + Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + children: [ + Text( + i!.uname, + style: TextStyle( + fontSize: + Theme.of(context).textTheme.titleMedium!.fontSize, + ), + ), + const SizedBox(width: 6), + Image.asset( + 'assets/images/lv/lv${i!.level}.png', + height: 11, + ), + ], + ), + Row( + children: [ + Text('粉丝:${i.fans} ', style: style), + Text(' 视频:${i.videos}', style: style) + ], + ), + if (i.officialVerify['desc'] != '') + Text( + i.officialVerify['desc'], + style: style, + ), + ], + ) + ], + ), + ), + ); + ; + }, + ); +} diff --git a/lib/pages/searchPanel/widgets/video_panel.dart b/lib/pages/searchPanel/widgets/video_panel.dart new file mode 100644 index 00000000..ca6b09fb --- /dev/null +++ b/lib/pages/searchPanel/widgets/video_panel.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; +import 'package:pilipala/common/widgets/video_card_h.dart'; + +Widget searchVideoPanel(BuildContext context, ctr, list) { + return ListView.builder( + controller: ctr!.scrollController, + addAutomaticKeepAlives: false, + addRepaintBoundaries: false, + itemCount: list!.length, + itemBuilder: (context, index) { + var i = list![index]; + return VideoCardH(videoItem: i); + }, + ); +} diff --git a/lib/pages/searchResult/view.dart b/lib/pages/searchResult/view.dart index dec22da1..783934c9 100644 --- a/lib/pages/searchResult/view.dart +++ b/lib/pages/searchResult/view.dart @@ -52,6 +52,7 @@ class _SearchResultPageState extends State ), body: Column( children: [ + const SizedBox(height: 4), Theme( data: ThemeData( splashColor: Colors.transparent, // 点击时的水波纹颜色设置为透明 @@ -68,9 +69,7 @@ class _SearchResultPageState extends State const EdgeInsets.symmetric(horizontal: 3, vertical: 8), indicator: BoxDecoration( color: Theme.of(context).colorScheme.secondaryContainer, - borderRadius: const BorderRadius.all( - Radius.circular(16), - ), + borderRadius: const BorderRadius.all(Radius.circular(20)), ), indicatorSize: TabBarIndicatorSize.tab, labelColor: Theme.of(context).colorScheme.onSecondaryContainer, @@ -87,7 +86,6 @@ class _SearchResultPageState extends State }, ), ), - const SizedBox(height: 4), Expanded( child: TabBarView( controller: _tabController, diff --git a/lib/utils/em.dart b/lib/utils/em.dart new file mode 100644 index 00000000..68eed977 --- /dev/null +++ b/lib/utils/em.dart @@ -0,0 +1,29 @@ +class Em { + static regCate(String origin) { + String str = origin; + RegExp exp = RegExp('<[^>]*>([^<]*)]*>'); + Iterable matches = exp.allMatches(origin); + for (Match match in matches) { + str = match.group(1)!; + } + return str; + } + + static regTitle(String origin) { + RegExp exp = RegExp('<[^>]*>([^<]*)]*>'); + List res = []; + origin.splitMapJoin(exp, onMatch: (Match match) { + String matchStr = match[0]!; + Map map = {'type': 'em', 'text': regCate(matchStr)}; + res.add(map); + return regCate(matchStr); + }, onNonMatch: (String str) { + if (str != '') { + Map map = {'type': 'text', 'text': str}; + res.add(map); + } + return str; + }); + return res; + } +}