From c2f8f143f88a5a5b8e0bf56bc4cb31bbc691fd3e Mon Sep 17 00:00:00 2001 From: guozhigq Date: Tue, 20 Jun 2023 22:52:47 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=90=9C=E7=B4=A2=E7=9B=B4=E6=92=AD?= =?UTF-8?q?=E9=97=B4=E3=80=81=E7=94=A8=E6=88=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/common/widgets/live_card.dart | 164 ++++++++++++ lib/common/widgets/network_img_layer.dart | 2 +- lib/http/api.dart | 3 + lib/http/search.dart | 45 ++++ lib/models/common/search_type.dart | 28 ++ lib/models/search/result.dart | 265 +++++++++++++++++++ lib/models/search/suggest.dart | 1 - lib/pages/searchPanel/controller.dart | 48 ++++ lib/pages/searchPanel/index.dart | 4 + lib/pages/searchPanel/view.dart | 94 +++++++ lib/pages/searchPanel/widgets/userPanel.dart | 63 +++++ lib/pages/searchResult/controller.dart | 11 +- lib/pages/searchResult/view.dart | 117 ++++---- lib/pages/video/README.md | 10 + lib/utils/utils.dart | 5 +- 15 files changed, 801 insertions(+), 59 deletions(-) create mode 100644 lib/common/widgets/live_card.dart create mode 100644 lib/models/common/search_type.dart create mode 100644 lib/models/search/result.dart create mode 100644 lib/pages/searchPanel/controller.dart create mode 100644 lib/pages/searchPanel/index.dart create mode 100644 lib/pages/searchPanel/view.dart create mode 100644 lib/pages/searchPanel/widgets/userPanel.dart create mode 100644 lib/pages/video/README.md diff --git a/lib/common/widgets/live_card.dart b/lib/common/widgets/live_card.dart new file mode 100644 index 00000000..c82fc791 --- /dev/null +++ b/lib/common/widgets/live_card.dart @@ -0,0 +1,164 @@ +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'; + +class LiveCard extends StatelessWidget { + var liveItem; + + LiveCard({ + Key? key, + required this.liveItem, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + String heroTag = Utils.makeHeroTag(liveItem.roomid); + + return Card( + elevation: 0, + clipBehavior: Clip.hardEdge, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(0), + side: BorderSide( + color: Theme.of(context).dividerColor.withOpacity(0.08), + width: 1, + ), + ), + 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: heroTag, + child: NetworkImgLayer( + // 指定图片尺寸 + // src: videoItem.pic + '@${(maxWidth * 2).toInt()}w', + src: liveItem.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( + // view: liveItem.stat.view, + // danmaku: liveItem.stat.danmaku, + // duration: liveItem.duration, + online: liveItem.online, + ), + ), + ), + ], + ); + }), + ), + LiveContent(liveItem: liveItem) + ], + ), + ), + ); + } +} + +class LiveContent extends StatelessWidget { + final liveItem; + const LiveContent({Key? key, required this.liveItem}) : super(key: key); + @override + Widget build(BuildContext context) { + return Padding( + // 多列 + padding: const EdgeInsets.fromLTRB(8, 8, 6, 7), + // 单列 + // padding: const EdgeInsets.fromLTRB(14, 10, 4, 8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + liveItem.title, + textAlign: TextAlign.start, + style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500), + maxLines: Get.find().crossAxisCount, + overflow: TextOverflow.ellipsis, + ), + 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; + + const LiveStat({Key? key, required this.online}) : 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: [ + Row( + children: [ + // StatView( + // theme: 'white', + // view: view, + // ), + // const SizedBox(width: 8), + // StatDanMu( + // theme: 'white', + // danmu: danmaku, + // ), + ], + ), + Text( + online.toString(), + style: const TextStyle(fontSize: 11, color: Colors.white), + ) + ], + ), + ); + } +} diff --git a/lib/common/widgets/network_img_layer.dart b/lib/common/widgets/network_img_layer.dart index a09ec850..80ed4450 100644 --- a/lib/common/widgets/network_img_layer.dart +++ b/lib/common/widgets/network_img_layer.dart @@ -36,7 +36,7 @@ class NetworkImgLayer extends StatelessWidget { ? 0 : StyleString.imgRadius.x), child: CachedNetworkImage( - imageUrl: src!, + imageUrl: src!.startsWith('//') ? 'https:${src!}' : src!, width: width ?? double.infinity, height: height ?? double.infinity, alignment: Alignment.center, diff --git a/lib/http/api.dart b/lib/http/api.dart index 26ccb2ed..02cf07f9 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -147,4 +147,7 @@ class Api { // 搜索关键词 static const String serachSuggest = 'https://s.search.bilibili.com/main/suggest'; + + // 分类搜索 + static const String searchByType = '/x/web-interface/search/type'; } diff --git a/lib/http/search.dart b/lib/http/search.dart index 50b8e6b3..484f6ae5 100644 --- a/lib/http/search.dart +++ b/lib/http/search.dart @@ -1,5 +1,9 @@ +import 'dart:developer'; + import 'package:pilipala/http/index.dart'; +import 'package:pilipala/models/common/search_type.dart'; import 'package:pilipala/models/search/hot.dart'; +import 'package:pilipala/models/search/result.dart'; import 'package:pilipala/models/search/suggest.dart'; class SearchHttp { @@ -37,4 +41,45 @@ class SearchHttp { }; } } + + // 分类搜索 + static Future searchByType({ + required SearchType searchType, + required String keyword, + required page, + }) async { + var res = await Request().get(Api.searchByType, data: { + 'search_type': searchType.type, + 'keyword': keyword, + 'order_sort': 0, + 'user_type': 0, + 'page': page + }); + if (res.data['code'] == 0) { + var data; + // log(res.data.toString()); + switch (searchType) { + case SearchType.video: + data = SearchVideoModel.fromJson(res.data['data']); + break; + case SearchType.live_room: + data = SearchLiveModel.fromJson(res.data['data']); + break; + case SearchType.bili_user: + data = SearchUserModel.fromJson(res.data['data']); + break; + } + + return { + 'status': true, + 'data': data, + }; + } else { + return { + 'status': false, + 'date': [], + 'msg': '请求错误 🙅', + }; + } + } } diff --git a/lib/models/common/search_type.dart b/lib/models/common/search_type.dart new file mode 100644 index 00000000..60846702 --- /dev/null +++ b/lib/models/common/search_type.dart @@ -0,0 +1,28 @@ +// ignore_for_file: constant_identifier_names +enum SearchType { + // 视频:video + video, + // 番剧:media_bangumi, + // media_bangumi, + // 影视:media_ft + // media_ft, + // 直播间及主播:live + // live, + // 直播间:live_room + live_room, + // 主播:live_user + // live_user, + // 专栏:article + // article, + // 话题:topic + // topic, + // 用户:bili_user + bili_user, + // 相簿:photo + // photo +} + +extension SearchTypeExtension on SearchType { + String get type => ['video', 'live_room', 'bili_user'][index]; + String get label => ['视频', '直播间', '用户'][index]; +} diff --git a/lib/models/search/result.dart b/lib/models/search/result.dart new file mode 100644 index 00000000..3183a675 --- /dev/null +++ b/lib/models/search/result.dart @@ -0,0 +1,265 @@ +class SearchVideoModel { + SearchVideoModel({this.list}); + List? list; + SearchVideoModel.fromJson(Map json) { + list = json['result'] + .map((e) => SearchVideoItemModel.fromJson(e)) + .toList(); + } +} + +class SearchVideoItemModel { + SearchVideoItemModel({ + this.type, + this.id, + this.cid, + // this.author, + // this.mid, + // this.typeid, + // this.typename, + this.arcurl, + this.aid, + this.bvid, + this.title, + this.description, + this.pic, + // this.play, + this.videoReview, + // this.favorites, + this.tag, + // this.review, + this.pubdate, + this.senddate, + this.duration, + // this.viewType, + // this.like, + // this.upic, + // this.danmaku, + this.owner, + this.stat, + this.rcmdReason, + }); + + String? type; + int? id; + int? cid; + // String? author; + // String? mid; + // String? typeid; + // String? typename; + String? arcurl; + int? aid; + String? bvid; + String? title; + String? description; + String? pic; + // String? play; + int? videoReview; + // String? favorites; + String? tag; + // String? review; + int? pubdate; + int? senddate; + String? duration; + // String? viewType; + // String? like; + // String? upic; + // String? danmaku; + Owner? owner; + Stat? stat; + String? rcmdReason; + + SearchVideoItemModel.fromJson(Map json) { + type = json['type']; + id = json['id']; + cid = json['id']; + arcurl = json['arcurl']; + aid = json['aid']; + title = json['title'].replaceAll(RegExp(r'<.*?>'), ''); + description = json['description']; + pic = 'https:${json['pic']}'; + videoReview = json['video_review']; + pubdate = json['pubdate']; + senddate = json['senddate']; + duration = json['duration']; + owner = Owner.fromJson(json); + stat = Stat.fromJson(json); + } +} + +class Stat { + Stat({ + this.view, + this.danmaku, + this.favorite, + this.reply, + this.like, + }); + + // 播放量 + int? view; + // 弹幕数 + int? danmaku; + // 收藏数 + int? favorite; + // 评论数 + int? reply; + // 喜欢 + int? like; + + Stat.fromJson(Map json) { + view = json['play']; + danmaku = json['danmaku']; + favorite = json['favorite']; + reply = json['review']; + like = json['like']; + } +} + +class Owner { + Owner({ + this.mid, + this.name, + this.face, + }); + int? mid; + String? name; + String? face; + + Owner.fromJson(Map json) { + mid = json["mid"]; + name = json["author"]; + face = json['upic']; + } +} + +class SearchUserModel { + SearchUserModel({this.list}); + List? list; + SearchUserModel.fromJson(Map json) { + list = json['result'] + .map((e) => SearchUserItemModel.fromJson(e)) + .toList(); + } +} + +class SearchUserItemModel { + SearchUserItemModel({ + this.type, + this.mid, + this.uname, + this.usign, + this.fans, + this.videos, + this.upic, + this.faceNft, + this.faceNftType, + this.verifyInfo, + this.level, + this.gender, + this.isUpUser, + this.isLive, + this.roomId, + this.officialVerify, + }); + + String? type; + int? mid; + String? uname; + String? usign; + int? fans; + int? videos; + String? upic; + int? faceNft; + int? faceNftType; + String? verifyInfo; + int? level; + int? gender; + int? isUpUser; + int? isLive; + int? roomId; + Map? officialVerify; + + SearchUserItemModel.fromJson(Map json) { + type = json['type']; + mid = json['mid']; + uname = json['uname']; + usign = json['usign']; + fans = json['fans']; + videos = json['videos']; + upic = json['upic']; + faceNft = json['face_nft']; + faceNftType = json['face_nft_type']; + verifyInfo = json['verify_info']; + level = json['level']; + gender = json['gender']; + isUpUser = json['is_upuser']; + isLive = json['is_live']; + roomId = json['room_id']; + officialVerify = json['official_verify']; + } +} + +class SearchLiveModel { + SearchLiveModel({this.list}); + List? list; + SearchLiveModel.fromJson(Map json) { + list = json['result'] + .map((e) => SearchLiveItemModel.fromJson(e)) + .toList(); + } +} + +class SearchLiveItemModel { + SearchLiveItemModel({ + this.rankOffset, + this.uid, + this.tags, + this.liveTime, + this.uname, + this.uface, + this.userCover, + this.type, + this.title, + this.cover, + this.online, + this.rankIndex, + this.rankScore, + this.roomid, + this.attentions, + }); + + int? rankOffset; + int? uid; + String? tags; + String? liveTime; + String? uname; + String? uface; + String? userCover; + String? type; + String? title; + String? cover; + int? online; + int? rankIndex; + int? rankScore; + int? roomid; + int? attentions; + + SearchLiveItemModel.fromJson(Map json) { + rankOffset = json['rank_offset']; + uid = json['uid']; + tags = json['tags']; + liveTime = json['live_time']; + uname = json['uname']; + uface = json['uface']; + userCover = json['user_cover']; + type = json['type']; + title = json['title']; + cover = json['cover']; + online = json['online']; + rankIndex = json['rank_index']; + rankScore = json['rank_score']; + roomid = json['roomid']; + attentions = json['attentions']; + } +} diff --git a/lib/models/search/suggest.dart b/lib/models/search/suggest.dart index 29f5ffe5..ddc8c231 100644 --- a/lib/models/search/suggest.dart +++ b/lib/models/search/suggest.dart @@ -34,7 +34,6 @@ class SearchSuggestItem { String reg = '$inputTerm'; try { if (json['name'].indexOf(inputTerm) != -1) { - print(json['name']); String str = json['name'].replaceAll(reg, '^'); List arr = str.split('^'); arr.insert(arr.length - 1, inputTerm); diff --git a/lib/pages/searchPanel/controller.dart b/lib/pages/searchPanel/controller.dart new file mode 100644 index 00000000..4f31968c --- /dev/null +++ b/lib/pages/searchPanel/controller.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:pilipala/http/search.dart'; +import 'package:pilipala/models/common/search_type.dart'; + +class SearchPanelController extends GetxController { + SearchPanelController({this.keyword, this.searchType}); + ScrollController scrollController = ScrollController(); + String? keyword; + SearchType? searchType; + RxInt page = 1.obs; + RxList resultList = [].obs; + + @override + void onInit() { + super.onInit(); + } + + Future onSearch({type = 'init'}) async { + var result = await SearchHttp.searchByType( + searchType: searchType!, keyword: keyword!, page: page.value); + if (result['status']) { + if (type == 'init') { + page.value++; + resultList.addAll(result['data'].list); + } else { + resultList.value = result['data'].list; + } + } + return result; + } + + Future onRefresh() async { + page.value = 1; + onSearch(type: 'refresh'); + } + + // 返回顶部并刷新 + void animateToTop() async { + if (scrollController.offset >= + MediaQuery.of(Get.context!).size.height * 5) { + scrollController.jumpTo(0); + } else { + await scrollController.animateTo(0, + duration: const Duration(milliseconds: 500), curve: Curves.easeInOut); + } + } +} diff --git a/lib/pages/searchPanel/index.dart b/lib/pages/searchPanel/index.dart new file mode 100644 index 00000000..d1f93324 --- /dev/null +++ b/lib/pages/searchPanel/index.dart @@ -0,0 +1,4 @@ +library searchpanel; + +export './controller.dart'; +export './view.dart'; diff --git a/lib/pages/searchPanel/view.dart b/lib/pages/searchPanel/view.dart new file mode 100644 index 00000000..339f6439 --- /dev/null +++ b/lib/pages/searchPanel/view.dart @@ -0,0 +1,94 @@ +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/live_card.dart'; +import 'package:pilipala/common/widgets/video_card_h.dart'; +import 'package:pilipala/models/common/search_type.dart'; + +import 'controller.dart'; +import 'widgets/userPanel.dart'; + +class SearchPanel extends StatefulWidget { + String? keyword; + SearchType? searchType; + SearchPanel({required this.keyword, required this.searchType, Key? key}) + : super(key: key); + + @override + State createState() => _SearchPanelState(); +} + +class _SearchPanelState extends State + with AutomaticKeepAliveClientMixin { + late SearchPanelController? _searchPanelController; + + @override + bool get wantKeepAlive => true; + + @override + void initState() { + super.initState(); + _searchPanelController = Get.put( + SearchPanelController( + keyword: widget.keyword, + searchType: widget.searchType, + ), + tag: widget.searchType!.type); + } + + @override + Widget build(BuildContext context) { + return RefreshIndicator( + onRefresh: () async { + await _searchPanelController!.onRefresh(); + }, + child: FutureBuilder( + future: _searchPanelController!.onSearch(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + Map data = snapshot.data; + 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(); + } + }, + ), + ); + } else { + return HttpError( + errMsg: data['msg'], + fn: () => setState(() {}), + ); + } + } else { + // 骨架屏 + return ListView.builder( + addAutomaticKeepAlives: false, + addRepaintBoundaries: false, + itemCount: 15, + itemBuilder: (context, index) { + return const VideoCardHSkeleton(); + }, + ); + } + }, + ), + ); + } +} diff --git a/lib/pages/searchPanel/widgets/userPanel.dart b/lib/pages/searchPanel/widgets/userPanel.dart new file mode 100644 index 00000000..32cd6d89 --- /dev/null +++ b/lib/pages/searchPanel/widgets/userPanel.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; +import 'package:pilipala/common/widgets/network_img_layer.dart'; + +class UserPanel extends StatelessWidget { + var userItem; + UserPanel({super.key, this.userItem}); + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: () {}, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10), + child: Row( + children: [ + NetworkImgLayer( + width: 42, + height: 42, + src: userItem.upic, + type: 'avatar', + ), + const SizedBox(width: 10), + Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + children: [ + Text( + userItem!.uname, + style: TextStyle( + // color: replyItem!.isUp! || + // replyItem!.member!.vip!['vipType'] > 0 + // ? Theme.of(context).colorScheme.primary + // : Theme.of(context).colorScheme.outline, + fontSize: + Theme.of(context).textTheme.titleMedium!.fontSize, + ), + ), + const SizedBox(width: 6), + Image.asset( + 'assets/images/lv/lv${userItem!.level}.png', + height: 11, + ), + ], + ), + if (userItem.officialVerify['desc'] != '') + Text( + userItem.officialVerify['desc'], + style: TextStyle( + fontSize: + Theme.of(context).textTheme.labelSmall!.fontSize, + color: Theme.of(context).colorScheme.outline), + ), + ], + ) + ], + ), + ), + ); + } +} diff --git a/lib/pages/searchResult/controller.dart b/lib/pages/searchResult/controller.dart index ed7cdad5..0efc65bd 100644 --- a/lib/pages/searchResult/controller.dart +++ b/lib/pages/searchResult/controller.dart @@ -1,15 +1,10 @@ import 'package:get/get.dart'; +import 'package:pilipala/models/common/search_type.dart'; +import 'package:pilipala/pages/searchPanel/index.dart'; class SearchResultController extends GetxController { String? keyword; - List tabs = [ - {'label': '综合', 'id': ''}, - {'label': '视频', 'id': ''}, - {'label': '番剧', 'id': ''}, - {'label': '直播', 'id': ''}, - {'label': '专栏', 'id': ''}, - {'label': '用户', 'id': ''} - ]; + int tabIndex = 0; @override void onInit() { diff --git a/lib/pages/searchResult/view.dart b/lib/pages/searchResult/view.dart index f62b2352..dec22da1 100644 --- a/lib/pages/searchResult/view.dart +++ b/lib/pages/searchResult/view.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:pilipala/models/common/search_type.dart'; +import 'package:pilipala/pages/searchPanel/index.dart'; import 'controller.dart'; class SearchResultPage extends StatefulWidget { @@ -9,14 +11,32 @@ class SearchResultPage extends StatefulWidget { State createState() => _SearchResultPageState(); } -class _SearchResultPageState extends State { +class _SearchResultPageState extends State + with TickerProviderStateMixin { final SearchResultController _searchResultController = Get.put(SearchResultController()); + late TabController? _tabController; + + @override + void initState() { + super.initState(); + _tabController = TabController( + vsync: this, + length: SearchType.values.length, + initialIndex: _searchResultController.tabIndex, + ); + } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( + shape: Border( + bottom: BorderSide( + color: Theme.of(context).dividerColor.withOpacity(0.08), + width: 1, + ), + ), titleSpacing: 0, centerTitle: false, title: GestureDetector( @@ -30,57 +50,58 @@ class _SearchResultPageState extends State { ), ), ), - body: DefaultTabController( - length: _searchResultController.tabs.length, - child: Column( - children: [ - Theme( - data: ThemeData( - splashColor: Colors.transparent, // 点击时的水波纹颜色设置为透明 - highlightColor: Colors.transparent, // 点击时的背景高亮颜色设置为透明 - ), - child: TabBar( - tabs: _searchResultController.tabs - .map((e) => Tab(text: e['label'])) - .toList(), - isScrollable: true, - indicatorWeight: 0, - indicatorPadding: - const EdgeInsets.symmetric(horizontal: 3, vertical: 8), - indicator: BoxDecoration( - color: Theme.of(context).colorScheme.secondaryContainer, - borderRadius: const BorderRadius.all( - Radius.circular(16), - ), + body: Column( + children: [ + Theme( + data: ThemeData( + splashColor: Colors.transparent, // 点击时的水波纹颜色设置为透明 + highlightColor: Colors.transparent, // 点击时的背景高亮颜色设置为透明 + ), + child: TabBar( + controller: _tabController, + tabs: [ + for (var i in SearchType.values) Tab(text: i.label), + ], + isScrollable: true, + indicatorWeight: 0, + indicatorPadding: + const EdgeInsets.symmetric(horizontal: 3, vertical: 8), + indicator: BoxDecoration( + color: Theme.of(context).colorScheme.secondaryContainer, + borderRadius: const BorderRadius.all( + Radius.circular(16), ), - indicatorSize: TabBarIndicatorSize.tab, - labelColor: Theme.of(context).colorScheme.onSecondaryContainer, - labelStyle: const TextStyle(fontSize: 13), - dividerColor: Colors.transparent, - unselectedLabelColor: Theme.of(context).colorScheme.outline, - onTap: (index) { - print(index); - }, ), + indicatorSize: TabBarIndicatorSize.tab, + labelColor: Theme.of(context).colorScheme.onSecondaryContainer, + labelStyle: const TextStyle(fontSize: 13), + dividerColor: Colors.transparent, + unselectedLabelColor: Theme.of(context).colorScheme.outline, + onTap: (index) { + if (index == _searchResultController.tabIndex) { + Get.find( + tag: SearchType.values[index].type) + .animateToTop(); + } + _searchResultController.tabIndex = index; + }, ), - Expanded( - child: TabBarView( - children: [ - Container( - width: 200, - height: 200, - color: Colors.amber, - ), - Text('1'), - Text('1'), - Text('1'), - Text('1'), - Text('1'), - ], - ), + ), + const SizedBox(height: 4), + Expanded( + child: TabBarView( + controller: _tabController, + children: [ + for (var i in SearchType.values) ...{ + SearchPanel( + keyword: _searchResultController.keyword, + searchType: i, + ) + } + ], ), - ], - ), + ), + ], ), ); } diff --git a/lib/pages/video/README.md b/lib/pages/video/README.md new file mode 100644 index 00000000..2da26fa7 --- /dev/null +++ b/lib/pages/video/README.md @@ -0,0 +1,10 @@ +视频详情页预渲染 ++ videoItem + + title + + stat + + view + + danmaku + + pubdate + + owner + + face + + name \ No newline at end of file diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 3701e1a5..962e18ae 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -26,8 +26,11 @@ class Utils { } } - static String timeFormat(int time) { + static String timeFormat(dynamic time) { // 1小时内 + if (time is String && time.contains(':')) { + return time; + } if (time < 3600) { int minute = time ~/ 60; double res = time / 60;