diff --git a/lib/common/skeleton/media_bangumi.dart b/lib/common/skeleton/media_bangumi.dart new file mode 100644 index 00000000..50148f0d --- /dev/null +++ b/lib/common/skeleton/media_bangumi.dart @@ -0,0 +1,79 @@ +import 'package:flutter/material.dart'; +import 'package:pilipala/common/constants.dart'; + +import 'skeleton.dart'; + +class MediaBangumiSkeleton extends StatefulWidget { + const MediaBangumiSkeleton({super.key}); + + @override + State createState() => _MediaBangumiSkeletonState(); +} + +class _MediaBangumiSkeletonState extends State { + @override + Widget build(BuildContext context) { + Color bgColor = Theme.of(context).colorScheme.onInverseSurface; + return Skeleton( + child: Padding( + padding: const EdgeInsets.fromLTRB( + StyleString.safeSpace, 7, StyleString.safeSpace, 7), + child: Row( + children: [ + Container( + width: 111, + height: 148, + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(6)), + color: bgColor), + ), + const SizedBox(width: 10), + SizedBox( + height: 148, + child: Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + color: Theme.of(context).colorScheme.onInverseSurface, + width: 200, + height: 20, + margin: const EdgeInsets.only(bottom: 15), + ), + Container( + color: Theme.of(context).colorScheme.onInverseSurface, + width: 150, + height: 13, + margin: const EdgeInsets.only(bottom: 5), + ), + Container( + color: Theme.of(context).colorScheme.onInverseSurface, + width: 150, + height: 13, + margin: const EdgeInsets.only(bottom: 5), + ), + Container( + color: Theme.of(context).colorScheme.onInverseSurface, + width: 150, + height: 13, + ), + const Spacer(), + Container( + width: 90, + height: 35, + decoration: BoxDecoration( + borderRadius: + const BorderRadius.all(Radius.circular(20)), + color: Theme.of(context).colorScheme.onInverseSurface, + ), + ), + ], + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/common/skeleton/video_card_h.dart b/lib/common/skeleton/video_card_h.dart index 71a659ea..547f836c 100644 --- a/lib/common/skeleton/video_card_h.dart +++ b/lib/common/skeleton/video_card_h.dart @@ -8,103 +8,86 @@ class VideoCardHSkeleton extends StatelessWidget { @override Widget build(BuildContext context) { return Skeleton( - child: Column( - children: [ - Padding( - padding: const EdgeInsets.fromLTRB( - StyleString.cardSpace, 7, StyleString.cardSpace, 7), - child: LayoutBuilder( - builder: (context, boxConstraints) { - double width = - (boxConstraints.maxWidth - StyleString.cardSpace * 6) / 2; - return SizedBox( - height: width / StyleString.aspectRatio, - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - AspectRatio( - aspectRatio: StyleString.aspectRatio, - child: LayoutBuilder( - builder: (context, boxConstraints) { - return Container( - decoration: BoxDecoration( - color: Theme.of(context) - .colorScheme - .onInverseSurface, - borderRadius: BorderRadius.circular( - StyleString.imgRadius.x), - ), - ); - }, + child: Padding( + padding: const EdgeInsets.fromLTRB( + StyleString.safeSpace, 7, StyleString.safeSpace, 7), + child: LayoutBuilder( + builder: (context, boxConstraints) { + double width = + (boxConstraints.maxWidth - StyleString.cardSpace * 9) / 2; + return SizedBox( + height: width / StyleString.aspectRatio, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AspectRatio( + aspectRatio: StyleString.aspectRatio, + child: LayoutBuilder( + builder: (context, boxConstraints) { + return Container( + decoration: BoxDecoration( + color: + Theme.of(context).colorScheme.onInverseSurface, + borderRadius: + BorderRadius.circular(StyleString.imgRadius.x), + ), + ); + }, + ), + ), + // VideoContent(videoItem: videoItem) + Expanded( + child: Padding( + padding: const EdgeInsets.fromLTRB(10, 4, 6, 4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + color: Theme.of(context).colorScheme.onInverseSurface, + width: 200, + height: 11, + margin: const EdgeInsets.only(bottom: 5), ), - ), - // VideoContent(videoItem: videoItem) - Expanded( - child: Padding( - padding: const EdgeInsets.fromLTRB(10, 4, 6, 4), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + Container( + color: Theme.of(context).colorScheme.onInverseSurface, + width: 150, + height: 13, + ), + const Spacer(), + Container( + color: Theme.of(context).colorScheme.onInverseSurface, + width: 100, + height: 13, + margin: const EdgeInsets.only(bottom: 5), + ), + Row( children: [ Container( color: Theme.of(context) .colorScheme .onInverseSurface, - width: 200, - height: 11, - margin: const EdgeInsets.only(bottom: 5), + width: 40, + height: 13, + margin: const EdgeInsets.only(right: 8), ), Container( color: Theme.of(context) .colorScheme .onInverseSurface, - width: 150, + width: 40, height: 13, ), - const Spacer(), - Container( - color: Theme.of(context) - .colorScheme - .onInverseSurface, - width: 100, - height: 13, - margin: const EdgeInsets.only(bottom: 5), - ), - Row( - children: [ - Container( - color: Theme.of(context) - .colorScheme - .onInverseSurface, - width: 40, - height: 13, - margin: const EdgeInsets.only(right: 8), - ), - Container( - color: Theme.of(context) - .colorScheme - .onInverseSurface, - width: 40, - height: 13, - ), - ], - ) ], - ), - )), - ], - ), - ); - }, - ), - ), - Divider( - height: 1, - indent: 8, - endIndent: 12, - color: Theme.of(context).dividerColor.withOpacity(0.08), - ) - ], + ) + ], + ), + )), + ], + ), + ); + }, + ), ), ); } diff --git a/lib/common/widgets/video_card_h.dart b/lib/common/widgets/video_card_h.dart index d5bbfa0a..fed20c7b 100644 --- a/lib/common/widgets/video_card_h.dart +++ b/lib/common/widgets/video_card_h.dart @@ -56,63 +56,53 @@ class VideoCardH extends StatelessWidget { SmartDialog.showToast(err.toString()); } }, - child: Column( - children: [ - Padding( - padding: const EdgeInsets.fromLTRB( - StyleString.safeSpace, 6, StyleString.safeSpace, 6), - child: LayoutBuilder( - builder: (context, boxConstraints) { - double width = - (boxConstraints.maxWidth - StyleString.cardSpace * 9) / 2; - return SizedBox( - height: width / StyleString.aspectRatio, - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - AspectRatio( - aspectRatio: StyleString.aspectRatio, - child: LayoutBuilder( - builder: (context, boxConstraints) { - double maxWidth = boxConstraints.maxWidth; - double maxHeight = boxConstraints.maxHeight; - return Stack( - children: [ - Hero( - tag: heroTag, - child: NetworkImgLayer( - src: videoItem.pic, - width: maxWidth, - height: maxHeight, - ), - ), - pBadge(Utils.timeFormat(videoItem.duration!), - context, null, 6.0, 6.0, null, - type: 'gray'), - if (videoItem.rcmdReason != null && - videoItem.rcmdReason.content != '') - pBadge(videoItem.rcmdReason.content, - context, 6.0, 6.0, null, null), - ], - ); - }, - ), - ), - VideoContent(videoItem: videoItem) - ], + child: Padding( + padding: const EdgeInsets.fromLTRB( + StyleString.safeSpace, 7, StyleString.safeSpace, 7), + child: LayoutBuilder( + builder: (context, boxConstraints) { + double width = + (boxConstraints.maxWidth - StyleString.cardSpace * 9) / 2; + return SizedBox( + height: width / StyleString.aspectRatio, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AspectRatio( + aspectRatio: StyleString.aspectRatio, + child: LayoutBuilder( + builder: (context, boxConstraints) { + double maxWidth = boxConstraints.maxWidth; + double maxHeight = boxConstraints.maxHeight; + return Stack( + children: [ + Hero( + tag: heroTag, + child: NetworkImgLayer( + src: videoItem.pic, + width: maxWidth, + height: maxHeight, + ), + ), + pBadge(Utils.timeFormat(videoItem.duration!), + context, null, 6.0, 6.0, null, + type: 'gray'), + if (videoItem.rcmdReason != null && + videoItem.rcmdReason.content != '') + pBadge(videoItem.rcmdReason.content, context, + 6.0, 6.0, null, null), + ], + ); + }, + ), ), - ); - }, - ), - ), - // Divider( - // height: 1, - // indent: 8, - // endIndent: 12, - // color: Theme.of(context).dividerColor.withOpacity(0.08), - // ) - ], + VideoContent(videoItem: videoItem) + ], + ), + ); + }, + ), ), ), ); diff --git a/lib/http/api.dart b/lib/http/api.dart index d5727bd5..d1dc350a 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -167,6 +167,10 @@ class Api { // 热搜 static const String hotSearchList = 'https://s.search.bilibili.com/main/hotword'; + + // 默认搜索词 + static const String searchDefault = '/x/web-interface/wbi/search/default'; + // 搜索关键词 static const String serachSuggest = 'https://s.search.bilibili.com/main/suggest'; diff --git a/lib/pages/bangumi/controller.dart b/lib/pages/bangumi/controller.dart new file mode 100644 index 00000000..cb02a3f7 --- /dev/null +++ b/lib/pages/bangumi/controller.dart @@ -0,0 +1,3 @@ +import 'package:get/get.dart'; + +class BangumiController extends GetxController {} diff --git a/lib/pages/bangumi/index.dart b/lib/pages/bangumi/index.dart new file mode 100644 index 00000000..0b514b74 --- /dev/null +++ b/lib/pages/bangumi/index.dart @@ -0,0 +1,4 @@ +library bangumi_panel; + +export './controller.dart'; +export './view.dart'; diff --git a/lib/pages/bangumi/view.dart b/lib/pages/bangumi/view.dart new file mode 100644 index 00000000..cb1b1ddb --- /dev/null +++ b/lib/pages/bangumi/view.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; + +class BangumiPage extends StatefulWidget { + const BangumiPage({super.key}); + + @override + State createState() => _BangumiPageState(); +} + +class _BangumiPageState extends State { + @override + Widget build(BuildContext context) { + return const Scaffold( + body: Center( + child: Text('还在开发中'), + ), + ); + } +} diff --git a/lib/pages/dynamics/view.dart b/lib/pages/dynamics/view.dart index 0577fa29..2e01537b 100644 --- a/lib/pages/dynamics/view.dart +++ b/lib/pages/dynamics/view.dart @@ -1,12 +1,16 @@ +import 'dart:async'; + import 'package:custom_sliding_segmented_control/custom_sliding_segmented_control.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'package:get/get.dart'; import 'package:hive/hive.dart'; import 'package:pilipala/common/skeleton/dynamic_card.dart'; import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/models/dynamics/result.dart'; +import 'package:pilipala/pages/main/index.dart'; import 'package:pilipala/pages/mine/index.dart'; import 'package:pilipala/utils/feed_back.dart'; import 'package:pilipala/utils/storage.dart'; @@ -36,18 +40,27 @@ class _DynamicsPageState extends State void initState() { super.initState(); _futureBuilderFuture = _dynamicsController.queryFollowDynamic(); - - _dynamicsController.scrollController.addListener( + ScrollController scrollController = _dynamicsController.scrollController; + StreamController mainStream = + Get.find().bottomBarStream; + scrollController.addListener( () async { - if (_dynamicsController.scrollController.position.pixels >= - _dynamicsController.scrollController.position.maxScrollExtent - - 200) { + if (scrollController.position.pixels >= + scrollController.position.maxScrollExtent - 200) { if (!_isLoadingMore) { _isLoadingMore = true; await _dynamicsController.queryFollowDynamic(type: 'onLoad'); _isLoadingMore = false; } } + + final ScrollDirection direction = + scrollController.position.userScrollDirection; + if (direction == ScrollDirection.forward) { + mainStream.add(true); + } else if (direction == ScrollDirection.reverse) { + mainStream.add(false); + } }, ); } diff --git a/lib/pages/home/controller.dart b/lib/pages/home/controller.dart index e154fc0d..fe185738 100644 --- a/lib/pages/home/controller.dart +++ b/lib/pages/home/controller.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:pilipala/http/index.dart'; +import 'package:pilipala/pages/bangumi/index.dart'; import 'package:pilipala/pages/hot/index.dart'; import 'package:pilipala/pages/live/index.dart'; import 'package:pilipala/pages/rcmd/index.dart'; @@ -31,6 +33,14 @@ class HomeController extends GetxController with GetTickerProviderStateMixin { 'label': '热门', 'type': 'hot' }, + { + 'icon': const Icon( + Icons.play_circle_outlined, + size: 15, + ), + 'label': '番剧', + 'type': 'bangumi' + }, ]; int initialIndex = 1; late TabController tabController; @@ -38,7 +48,9 @@ class HomeController extends GetxController with GetTickerProviderStateMixin { Get.find, Get.find, Get.find, + Get.find, ]; + RxString defaultSearch = '输入关键词搜索'.obs; @override void onInit() { @@ -48,6 +60,7 @@ class HomeController extends GetxController with GetTickerProviderStateMixin { length: tabs.length, vsync: this, ); + searchDefault(); } void onRefresh() { @@ -61,4 +74,11 @@ class HomeController extends GetxController with GetTickerProviderStateMixin { var ctr = ctrList[index]; ctr().animateToTop(); } + + void searchDefault() async { + var res = await Request().get(Api.searchDefault); + if (res.data['code'] == 0) { + defaultSearch.value = res.data['data']['name']; + } + } } diff --git a/lib/pages/home/view.dart b/lib/pages/home/view.dart index 8230246b..56c89abe 100644 --- a/lib/pages/home/view.dart +++ b/lib/pages/home/view.dart @@ -1,10 +1,16 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:hive/hive.dart'; +import 'package:pilipala/common/widgets/network_img_layer.dart'; +import 'package:pilipala/pages/bangumi/index.dart'; import 'package:pilipala/pages/hot/index.dart'; import 'package:pilipala/pages/live/index.dart'; +import 'package:pilipala/pages/main/index.dart'; +import 'package:pilipala/pages/mine/index.dart'; import 'package:pilipala/pages/rcmd/index.dart'; import 'package:pilipala/utils/feed_back.dart'; +import 'package:pilipala/utils/storage.dart'; import './controller.dart'; class HomePage extends StatefulWidget { @@ -18,7 +24,7 @@ class _HomePageState extends State with AutomaticKeepAliveClientMixin, TickerProviderStateMixin { final HomeController _homeController = Get.put(HomeController()); List videoList = []; - // late TabController? _tabController; + Stream stream = Get.find().bottomBarStream.stream; @override bool get wantKeepAlive => true; @@ -27,103 +33,231 @@ class _HomePageState extends State Widget build(BuildContext context) { super.build(context); return Scaffold( - appBar: AppBar( - titleSpacing: 0, - title: Padding( - padding: const EdgeInsets.only(left: 12, right: 12, bottom: 0), - child: Stack( - children: [ - const Align( - alignment: Alignment.centerLeft, - child: Padding( - padding: EdgeInsets.only(left: 8), - child: Text( - 'PLPL', - style: TextStyle( - height: 2.8, - fontSize: 17, - fontWeight: FontWeight.bold, - letterSpacing: 1, - fontFamily: 'Jura-Bold', + extendBody: true, + extendBodyBehindAppBar: true, + appBar: AppBar(toolbarHeight: 0, elevation: 0), + body: Column( + children: [ + CustomAppBar(stream: stream, ctr: _homeController), + Container( + padding: const EdgeInsets.only(left: 12, right: 12, bottom: 4), + child: Stack( + children: [ + Align( + alignment: Alignment.center, + child: Theme( + data: ThemeData( + splashColor: Colors.transparent, // 点击时的水波纹颜色设置为透明 + highlightColor: Colors.transparent, // 点击时的背景高亮颜色设置为透明 ), - ), - ), - ), - Align( - alignment: Alignment.center, - child: Theme( - data: ThemeData( - splashColor: Colors.transparent, // 点击时的水波纹颜色设置为透明 - highlightColor: Colors.transparent, // 点击时的背景高亮颜色设置为透明 - ), - child: Padding( - padding: const EdgeInsets.only(top: 4), - child: TabBar( - controller: _homeController.tabController, - tabs: [ - for (var i in _homeController.tabs) - // Tab(text: i['label']) - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 0, vertical: 11), - child: Row( - children: [ - i['icon'], - const SizedBox(width: 4), - Text(i['label']) - ], + child: Padding( + padding: const EdgeInsets.only(top: 2), + child: TabBar( + controller: _homeController.tabController, + tabs: [ + for (var i in _homeController.tabs) + // Tab(text: i['label']) + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 0, vertical: 11), + child: Row( + children: [ + i['icon'], + const SizedBox(width: 4), + Text(i['label']) + ], + ), ), - ), - ], - isScrollable: true, - indicatorWeight: 0, - indicatorPadding: const EdgeInsets.symmetric( - horizontal: 4, vertical: 5), - indicator: BoxDecoration( - color: Theme.of(context) - .colorScheme - .primaryContainer - .withOpacity(0.8), - borderRadius: - const BorderRadius.all(Radius.circular(20)), + ], + isScrollable: true, + indicatorWeight: 0, + indicatorPadding: const EdgeInsets.symmetric( + horizontal: 4, vertical: 5), + indicator: BoxDecoration( + color: Theme.of(context) + .colorScheme + .primaryContainer + .withOpacity(0.8), + borderRadius: + const BorderRadius.all(Radius.circular(20)), + ), + indicatorSize: TabBarIndicatorSize.tab, + labelColor: Theme.of(context).colorScheme.primary, + labelStyle: const TextStyle(fontSize: 13), + dividerColor: Colors.transparent, + unselectedLabelColor: + Theme.of(context).colorScheme.outline, + onTap: (value) => + {feedBack(), _homeController.initialIndex = value}, ), - indicatorSize: TabBarIndicatorSize.tab, - labelColor: Theme.of(context).colorScheme.primary, - labelStyle: const TextStyle(fontSize: 13), - dividerColor: Colors.transparent, - unselectedLabelColor: - Theme.of(context).colorScheme.outline, - onTap: (value) => - {feedBack(), _homeController.initialIndex = value}, ), ), ), - ), - Align( - alignment: Alignment.centerRight, - child: Hero( - tag: 'searchTag', - child: IconButton( - onPressed: () { - feedBack(); - Get.toNamed('/search'); - }, - icon: const Icon(CupertinoIcons.search, size: 21), - ), - ), - ), - ], + ], + ), + ), + Expanded( + child: TabBarView( + controller: _homeController.tabController, + children: const [ + LivePage(), + RcmdPage(), + HotPage(), + BangumiPage(), + ], + ), ), - ), - ), - body: TabBarView( - controller: _homeController.tabController, - children: const [ - LivePage(), - RcmdPage(), - HotPage(), ], ), ); } } + +class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { + final double height; + final Stream? stream; + final ctr; + + const CustomAppBar({ + super.key, + this.height = kToolbarHeight, + this.stream, + this.ctr, + }); + + @override + Size get preferredSize => Size.fromHeight(height); + + @override + Widget build(BuildContext context) { + Box user = GStrorage.user; + + return StreamBuilder( + stream: stream, + initialData: true, + builder: (context, AsyncSnapshot snapshot) { + return ClipRect( + clipBehavior: Clip.hardEdge, + child: AnimatedOpacity( + opacity: snapshot.data ? 1 : 0, + duration: const Duration(milliseconds: 300), + child: AnimatedContainer( + curve: Curves.linear, + duration: const Duration(milliseconds: 300), + height: snapshot.data ? 94 : MediaQuery.of(context).padding.top, + child: Container( + padding: EdgeInsets.only( + left: 12, + right: 12, + bottom: 4, + top: MediaQuery.of(context).padding.top, + ), + child: Row( + children: [ + const Text( + 'PLPL', + style: TextStyle( + height: 2.8, + fontSize: 17, + fontWeight: FontWeight.bold, + fontFamily: 'Jura-Bold', + ), + ), + const SizedBox(width: 10), + Expanded( + child: Hero( + tag: 'searchWrap', + child: GestureDetector( + onTap: () { + Get.toNamed('/search', parameters: { + 'hintText': ctr.defaultSearch.value + }); + }, + child: Container( + width: 250, + height: 45, + clipBehavior: Clip.hardEdge, + padding: const EdgeInsets.only(left: 12, right: 22), + decoration: BoxDecoration( + borderRadius: + const BorderRadius.all(Radius.circular(25)), + color: Theme.of(context) + .colorScheme + .onInverseSurface, + ), + child: Row( + children: [ + Icon( + Icons.search_outlined, + size: 23, + color: Theme.of(context).colorScheme.outline, + ), + const SizedBox(width: 7), + Expanded( + child: Obx( + () => Text( + ctr.defaultSearch.value, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: Theme.of(context) + .colorScheme + .outline), + ), + ), + ), + ], + ), + ), + ), + ), + ), + const SizedBox(width: 12), + if (user.get(UserBoxKey.userLogin) ?? false) ...[ + GestureDetector( + onTap: () { + feedBack(); + showModalBottomSheet( + context: context, + builder: (_) => const SizedBox( + height: 450, + child: MinePage(), + ), + clipBehavior: Clip.hardEdge, + isScrollControlled: true, + ); + }, + child: NetworkImgLayer( + type: 'avatar', + width: 34, + height: 34, + src: user.get(UserBoxKey.userFace), + ), + ) + ] else ...[ + IconButton( + onPressed: () { + feedBack(); + showModalBottomSheet( + context: context, + builder: (_) => const SizedBox( + height: 450, + child: MinePage(), + ), + clipBehavior: Clip.hardEdge, + isScrollControlled: true, + ); + }, + icon: const Icon(CupertinoIcons.person, size: 22), + ) + ], + ], + ), + ), + ), + ), + ); + }, + ); + } +} diff --git a/lib/pages/hot/view.dart b/lib/pages/hot/view.dart index 826f4e6e..aa990326 100644 --- a/lib/pages/hot/view.dart +++ b/lib/pages/hot/view.dart @@ -1,4 +1,7 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/widgets/animated_dialog.dart'; import 'package:pilipala/common/widgets/overlay_pop.dart'; @@ -6,6 +9,7 @@ import 'package:pilipala/common/skeleton/video_card_h.dart'; import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/common/widgets/video_card_h.dart'; import 'package:pilipala/pages/hot/controller.dart'; +import 'package:pilipala/pages/main/index.dart'; class HotPage extends StatefulWidget { const HotPage({Key? key}) : super(key: key); @@ -26,15 +30,26 @@ class _HotPageState extends State with AutomaticKeepAliveClientMixin { void initState() { super.initState(); _futureBuilderFuture = _hotController.queryHotFeed('init'); - _hotController.scrollController.addListener( + ScrollController scrollController = _hotController.scrollController; + StreamController mainStream = + Get.find().bottomBarStream; + scrollController.addListener( () { - if (_hotController.scrollController.position.pixels >= - _hotController.scrollController.position.maxScrollExtent - 200) { + if (scrollController.position.pixels >= + scrollController.position.maxScrollExtent - 200) { if (!_hotController.isLoadingMore) { _hotController.isLoadingMore = true; _hotController.onLoad(); } } + + final ScrollDirection direction = + scrollController.position.userScrollDirection; + if (direction == ScrollDirection.forward) { + mainStream.add(true); + } else if (direction == ScrollDirection.reverse) { + mainStream.add(false); + } }, ); } diff --git a/lib/pages/live/view.dart b/lib/pages/live/view.dart index 8aba4073..3e26bafc 100644 --- a/lib/pages/live/view.dart +++ b/lib/pages/live/view.dart @@ -1,10 +1,14 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/constants.dart'; import 'package:pilipala/common/skeleton/video_card_v.dart'; import 'package:pilipala/common/widgets/animated_dialog.dart'; import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/common/widgets/overlay_pop.dart'; +import 'package:pilipala/pages/main/index.dart'; import 'controller.dart'; import 'widgets/live_item.dart'; @@ -22,15 +26,26 @@ class _LivePageState extends State { @override void initState() { super.initState(); - _liveController.scrollController.addListener( + ScrollController scrollController = _liveController.scrollController; + StreamController mainStream = + Get.find().bottomBarStream; + scrollController.addListener( () { - if (_liveController.scrollController.position.pixels >= - _liveController.scrollController.position.maxScrollExtent - 200) { + if (scrollController.position.pixels >= + scrollController.position.maxScrollExtent - 200) { if (!_liveController.isLoadingMore) { _liveController.isLoadingMore = true; _liveController.onLoad(); } } + + final ScrollDirection direction = + scrollController.position.userScrollDirection; + if (direction == ScrollDirection.forward) { + mainStream.add(true); + } else if (direction == ScrollDirection.reverse) { + mainStream.add(false); + } }, ); } diff --git a/lib/pages/main/controller.dart b/lib/pages/main/controller.dart index 0c81d155..122edb87 100644 --- a/lib/pages/main/controller.dart +++ b/lib/pages/main/controller.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:get/get.dart'; import 'package:flutter/material.dart'; import 'package:pilipala/pages/dynamics/index.dart'; @@ -33,4 +35,6 @@ class MainController extends GetxController { 'label': "媒体库", } ].obs; + final StreamController bottomBarStream = + StreamController.broadcast(); } diff --git a/lib/pages/main/view.dart b/lib/pages/main/view.dart index 9018c629..67d49c82 100644 --- a/lib/pages/main/view.dart +++ b/lib/pages/main/view.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:hive/hive.dart'; @@ -64,7 +66,6 @@ class _MainAppState extends State with SingleTickerProviderStateMixin { if (DateTime.now().millisecondsSinceEpoch - _lastSelectTime! < 500) { _homeController.onRefresh(); } else { - await Future.delayed(const Duration(microseconds: 300)); _homeController.animateToTop(); } _lastSelectTime = DateTime.now().millisecondsSinceEpoch; @@ -80,7 +81,6 @@ class _MainAppState extends State with SingleTickerProviderStateMixin { if (DateTime.now().millisecondsSinceEpoch - _lastSelectTime! < 500) { _dynamicController.onRefresh(); } else { - await Future.delayed(const Duration(microseconds: 300)); _dynamicController.animateToTop(); } _lastSelectTime = DateTime.now().millisecondsSinceEpoch; @@ -105,6 +105,7 @@ class _MainAppState extends State with SingleTickerProviderStateMixin { localCache.put('sheetHeight', sheetHeight); localCache.put('statusBarHeight', statusBarHeight); return Scaffold( + extendBody: true, body: FadeTransition( opacity: _fadeAnimation!, child: SlideTransition( @@ -129,21 +130,33 @@ class _MainAppState extends State with SingleTickerProviderStateMixin { ), ), ), - bottomNavigationBar: BottomNavigationBar( - currentIndex: selectedIndex, - // type: BottomNavigationBarType.shifting, - selectedItemColor: Theme.of(context).colorScheme.primary, - unselectedItemColor: Theme.of(context).colorScheme.onSurfaceVariant, - selectedFontSize: 12.4, - onTap: (value) => setIndex(value), - items: [ - ..._mainController.navigationBars.map((e) { - return BottomNavigationBarItem( - icon: e['icon'], - label: e['label'], - ); - }).toList(), - ], + bottomNavigationBar: StreamBuilder( + stream: _mainController.bottomBarStream.stream, + initialData: true, + builder: (context, AsyncSnapshot snapshot) { + return AnimatedSlide( + curve: Curves.linear, + duration: const Duration(milliseconds: 300), + offset: Offset(0, snapshot.data ? 0 : 1), + child: BottomNavigationBar( + currentIndex: selectedIndex, + // type: BottomNavigationBarType.shifting, + selectedItemColor: Theme.of(context).colorScheme.primary, + unselectedItemColor: + Theme.of(context).colorScheme.onSurfaceVariant, + selectedFontSize: 12.4, + onTap: (value) => setIndex(value), + items: [ + ..._mainController.navigationBars.map((e) { + return BottomNavigationBarItem( + icon: e['icon'], + label: e['label'], + ); + }).toList(), + ], + ), + ); + }, ), ); } diff --git a/lib/pages/rcmd/view.dart b/lib/pages/rcmd/view.dart index 74814892..1b22a2db 100644 --- a/lib/pages/rcmd/view.dart +++ b/lib/pages/rcmd/view.dart @@ -1,4 +1,7 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/constants.dart'; import 'package:pilipala/common/skeleton/video_card_v.dart'; @@ -6,6 +9,7 @@ import 'package:pilipala/common/widgets/animated_dialog.dart'; import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/common/widgets/overlay_pop.dart'; import 'package:pilipala/common/widgets/video_card_v.dart'; +import 'package:pilipala/pages/main/index.dart'; import 'controller.dart'; @@ -26,15 +30,26 @@ class _RcmdPageState extends State @override void initState() { super.initState(); - _rcmdController.scrollController.addListener( + ScrollController scrollController = _rcmdController.scrollController; + StreamController mainStream = + Get.find().bottomBarStream; + scrollController.addListener( () { - if (_rcmdController.scrollController.position.pixels >= - _rcmdController.scrollController.position.maxScrollExtent - 200) { + if (scrollController.position.pixels >= + scrollController.position.maxScrollExtent - 200) { if (!_rcmdController.isLoadingMore) { _rcmdController.isLoadingMore = true; _rcmdController.onLoad(); } } + + final ScrollDirection direction = + scrollController.position.userScrollDirection; + if (direction == ScrollDirection.forward) { + mainStream.add(true); + } else if (direction == ScrollDirection.reverse) { + mainStream.add(false); + } }, ); } diff --git a/lib/pages/search/controller.dart b/lib/pages/search/controller.dart index 5be168c7..5183c635 100644 --- a/lib/pages/search/controller.dart +++ b/lib/pages/search/controller.dart @@ -19,6 +19,7 @@ class SSearchController extends GetxController { RxList searchSuggestList = [SearchSuggestItem()].obs; final _debouncer = Debouncer(delay: const Duration(milliseconds: 200)); // 设置延迟时间 + String hintText = '搜索'; @override void onInit() { @@ -33,7 +34,13 @@ class SSearchController extends GetxController { } // 其他页面跳转过来 if (Get.parameters.keys.isNotEmpty) { - onClickKeyword(Get.parameters['keyword']!); + if (Get.parameters['keyword'] != null) { + onClickKeyword(Get.parameters['keyword']!); + } + if (Get.parameters['hintText'] != null) { + hintText = Get.parameters['hintText']!; + searchKeyWord.value = hintText; + } } historyCacheList = histiryWord.get('cacheList') ?? []; historyList.value = historyCacheList; diff --git a/lib/pages/search/view.dart b/lib/pages/search/view.dart index 300154a9..766ca080 100644 --- a/lib/pages/search/view.dart +++ b/lib/pages/search/view.dart @@ -17,6 +17,13 @@ class SearchPage extends StatefulWidget { class _SearchPageState extends State with RouteAware { final SSearchController _searchController = Get.put(SSearchController()); + late Future? _futureBuilderFuture; + + @override + void initState() { + super.initState(); + _futureBuilderFuture = _searchController.queryHotSearchList(); + } @override // 返回当前页面时 @@ -53,26 +60,29 @@ class _SearchPageState extends State with RouteAware { ), const SizedBox(width: 10) ], - title: Obx( - () => TextField( - autofocus: true, - focusNode: _searchController.searchFocusNode, - controller: _searchController.controller.value, - textInputAction: TextInputAction.search, - onChanged: (value) => _searchController.onChange(value), - decoration: InputDecoration( - hintText: '搜索', - border: InputBorder.none, - suffixIcon: IconButton( - icon: Icon( - Icons.clear, - size: 22, - color: Theme.of(context).colorScheme.outline, + title: Hero( + tag: 'searchWrap', + child: Obx( + () => TextField( + autofocus: true, + focusNode: _searchController.searchFocusNode, + controller: _searchController.controller.value, + textInputAction: TextInputAction.search, + onChanged: (value) => _searchController.onChange(value), + decoration: InputDecoration( + hintText: _searchController.hintText, + border: InputBorder.none, + suffixIcon: IconButton( + icon: Icon( + Icons.clear, + size: 22, + color: Theme.of(context).colorScheme.outline, + ), + onPressed: () => _searchController.onClear(), ), - onPressed: () => _searchController.onClear(), ), + onSubmitted: (String value) => _searchController.submit(), ), - onSubmitted: (String value) => _searchController.submit(), ), ), ), @@ -159,7 +169,7 @@ class _SearchPageState extends State with RouteAware { builder: (context, boxConstraints) { final double width = boxConstraints.maxWidth; return FutureBuilder( - future: _searchController.queryHotSearchList(), + future: _futureBuilderFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { Map data = snapshot.data as Map; diff --git a/lib/pages/searchPanel/view.dart b/lib/pages/searchPanel/view.dart index 2683e7e9..5dd15b8f 100644 --- a/lib/pages/searchPanel/view.dart +++ b/lib/pages/searchPanel/view.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:pilipala/common/skeleton/media_bangumi.dart'; import 'package:pilipala/common/skeleton/video_card_h.dart'; import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/models/common/search_type.dart'; @@ -104,7 +105,18 @@ class _SearchPanelState extends State addRepaintBoundaries: false, itemCount: 15, itemBuilder: (context, index) { - return const VideoCardHSkeleton(); + switch (widget.searchType) { + case SearchType.video: + return const VideoCardHSkeleton(); + case SearchType.media_bangumi: + return const MediaBangumiSkeleton(); + case SearchType.bili_user: + return const VideoCardHSkeleton(); + case SearchType.live_room: + return const VideoCardHSkeleton(); + default: + return const VideoCardHSkeleton(); + } }, ); } diff --git a/lib/pages/searchPanel/widgets/live_panel.dart b/lib/pages/searchPanel/widgets/live_panel.dart index bb569128..f00660e6 100644 --- a/lib/pages/searchPanel/widgets/live_panel.dart +++ b/lib/pages/searchPanel/widgets/live_panel.dart @@ -6,7 +6,7 @@ import 'package:pilipala/utils/utils.dart'; Widget searchLivePanel(BuildContext context, ctr, list) { return Padding( padding: const EdgeInsets.only( - left: StyleString.cardSpace, right: StyleString.cardSpace), + left: StyleString.safeSpace, right: StyleString.safeSpace), child: GridView.builder( primary: false, controller: ctr!.scrollController, @@ -16,73 +16,82 @@ Widget searchLivePanel(BuildContext context, ctr, list) { mainAxisSpacing: StyleString.cardSpace + 3, mainAxisExtent: MediaQuery.of(context).size.width / 2 / StyleString.aspectRatio + - 65, + 60, ), itemCount: list.length, itemBuilder: (context, index) { - var i = list![index]; - return Card( - elevation: 0, - clipBehavior: Clip.hardEdge, - shape: RoundedRectangleBorder( - borderRadius: StyleString.mdRadius, - ), - margin: EdgeInsets.zero, - child: InkWell( - onTap: () {}, - child: Column( - children: [ - ClipRRect( - borderRadius: const BorderRadius.only( - topLeft: StyleString.imgRadius, - topRight: StyleString.imgRadius, - bottomLeft: StyleString.imgRadius, - bottomRight: StyleString.imgRadius, - ), - child: AspectRatio( - aspectRatio: StyleString.aspectRatio, - child: LayoutBuilder(builder: (context, boxConstraints) { - double maxWidth = boxConstraints.maxWidth; - double maxHeight = boxConstraints.maxHeight; - return Stack( - children: [ - Hero( - tag: Utils.makeHeroTag(i.roomid), - child: NetworkImgLayer( - src: i.cover, - 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) - ], - ), - ), - ); + return LiveItem(liveItem: list![index]); }, ), ); } +class LiveItem extends StatelessWidget { + final dynamic liveItem; + const LiveItem({Key? key, required this.liveItem}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Card( + elevation: 0, + clipBehavior: Clip.hardEdge, + shape: RoundedRectangleBorder( + borderRadius: StyleString.mdRadius, + ), + margin: EdgeInsets.zero, + child: InkWell( + onTap: () {}, + child: Column( + children: [ + ClipRRect( + borderRadius: const BorderRadius.only( + topLeft: StyleString.imgRadius, + topRight: StyleString.imgRadius, + bottomLeft: StyleString.imgRadius, + bottomRight: StyleString.imgRadius, + ), + child: AspectRatio( + aspectRatio: StyleString.aspectRatio, + child: LayoutBuilder(builder: (context, boxConstraints) { + double maxWidth = boxConstraints.maxWidth; + double maxHeight = boxConstraints.maxHeight; + return Stack( + children: [ + Hero( + tag: Utils.makeHeroTag(liveItem.roomid), + child: NetworkImgLayer( + src: liveItem.cover, + 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: liveItem.online, + cateName: liveItem.cateName, + ), + ), + ), + ], + ); + }), + ), + ), + LiveContent(liveItem: liveItem) + ], + ), + ), + ); + } +} + class LiveContent extends StatelessWidget { final dynamic liveItem; const LiveContent({Key? key, required this.liveItem}) : super(key: key); diff --git a/lib/pages/searchPanel/widgets/media_bangumi_panel.dart b/lib/pages/searchPanel/widgets/media_bangumi_panel.dart index 7939a9b3..9dab4699 100644 --- a/lib/pages/searchPanel/widgets/media_bangumi_panel.dart +++ b/lib/pages/searchPanel/widgets/media_bangumi_panel.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; +import 'package:pilipala/common/constants.dart'; import 'package:pilipala/common/widgets/badge.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/http/search.dart'; @@ -28,7 +29,8 @@ Widget searchMbangumiPanel(BuildContext context, ctr, list) { // }); }, child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + padding: const EdgeInsets.fromLTRB( + StyleString.safeSpace, 7, StyleString.safeSpace, 7), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [