From f50e6d18ddca611056087ac8cd5501be22bc17e6 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 13 Aug 2023 19:40:22 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=E5=85=A8=E5=B1=8F=E6=96=B9?= =?UTF-8?q?=E5=BC=8F=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/setting/play_setting.dart | 37 ++++++++++++++-- .../pl_player/models/fullscreen_mode.dart | 17 ++++++++ lib/plugin/pl_player/view.dart | 43 +++++++++++++++---- lib/utils/storage.dart | 1 + 4 files changed, 87 insertions(+), 11 deletions(-) diff --git a/lib/pages/setting/play_setting.dart b/lib/pages/setting/play_setting.dart index d8fc7413..62f3be55 100644 --- a/lib/pages/setting/play_setting.dart +++ b/lib/pages/setting/play_setting.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:hive/hive.dart'; import 'package:pilipala/models/video/play/quality.dart'; +import 'package:pilipala/plugin/pl_player/models/fullscreen_mode.dart'; import 'package:pilipala/utils/storage.dart'; import 'widgets/switch_item.dart'; @@ -17,6 +18,7 @@ class _PlaySettingState extends State { late dynamic defaultVideoQa; late dynamic defaultAudioQa; late dynamic defaultDecode; + late int defaultFullScreenMode; @override void initState() { @@ -27,6 +29,8 @@ class _PlaySettingState extends State { defaultValue: AudioQuality.values.last.code); defaultDecode = setting.get(SettingBoxKey.defaultDecode, defaultValue: VideoDecodeFormats.values.last.code); + defaultFullScreenMode = setting.get(SettingBoxKey.fullScreenMode, + defaultValue: FullScreenMode.values.first.code); } @override @@ -68,7 +72,7 @@ class _PlaySettingState extends State { ), trailing: PopupMenuButton( initialValue: defaultVideoQa, - icon: const Icon(Icons.arrow_forward_rounded, size: 22), + icon: const Icon(Icons.more_vert_outlined, size: 22), onSelected: (item) { defaultVideoQa = item; setting.put(SettingBoxKey.defaultVideoQa, item); @@ -93,7 +97,7 @@ class _PlaySettingState extends State { ), trailing: PopupMenuButton( initialValue: defaultAudioQa, - icon: const Icon(Icons.arrow_forward_rounded, size: 22), + icon: const Icon(Icons.more_vert_outlined, size: 22), onSelected: (item) { defaultAudioQa = item; setting.put(SettingBoxKey.defaultAudioQa, item); @@ -119,7 +123,7 @@ class _PlaySettingState extends State { ), trailing: PopupMenuButton( initialValue: defaultDecode, - icon: const Icon(Icons.arrow_forward_rounded, size: 22), + icon: const Icon(Icons.more_vert_outlined, size: 22), onSelected: (item) { defaultDecode = item; setting.put(SettingBoxKey.defaultDecode, item); @@ -135,6 +139,33 @@ class _PlaySettingState extends State { ], ), ), + ListTile( + dense: false, + title: Text('默认全屏方式', style: titleStyle), + subtitle: Text( + '当前全屏方式:' + + FullScreenModeCode.fromCode(defaultFullScreenMode)! + .description, + style: subTitleStyle, + ), + trailing: PopupMenuButton( + initialValue: defaultFullScreenMode, + icon: const Icon(Icons.more_vert_outlined, size: 22), + onSelected: (item) { + defaultFullScreenMode = item; + setting.put(SettingBoxKey.fullScreenMode, item); + setState(() {}); + }, + itemBuilder: (BuildContext context) => [ + for (var i in FullScreenMode.values) ...[ + PopupMenuItem( + value: i.code, + child: Text(i.description), + ), + ] + ], + ), + ), ], ), ); diff --git a/lib/plugin/pl_player/models/fullscreen_mode.dart b/lib/plugin/pl_player/models/fullscreen_mode.dart index 1080b6c6..9b5028e7 100644 --- a/lib/plugin/pl_player/models/fullscreen_mode.dart +++ b/lib/plugin/pl_player/models/fullscreen_mode.dart @@ -7,3 +7,20 @@ enum FullScreenMode { // 始终横屏 horizontal } + +extension FullScreenModeDesc on FullScreenMode { + String get description => ['自适应', '始终竖屏', '始终横屏'][index]; +} + +extension FullScreenModeCode on FullScreenMode { + static final List _codeList = [0, 1, 2]; + int get code => _codeList[index]; + + static FullScreenMode? fromCode(int code) { + final index = _codeList.indexOf(code); + if (index != -1) { + return FullScreenMode.values[index]; + } + return null; + } +} diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index 6df96967..e70bec30 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -5,14 +5,17 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:get/get.dart'; +import 'package:hive/hive.dart'; import 'package:media_kit/media_kit.dart'; import 'package:media_kit_video/media_kit_video.dart'; import 'package:pilipala/common/widgets/app_bar_ani.dart'; import 'package:pilipala/plugin/pl_player/controller.dart'; import 'package:pilipala/plugin/pl_player/models/duration.dart'; +import 'package:pilipala/plugin/pl_player/models/fullscreen_mode.dart'; import 'package:pilipala/plugin/pl_player/models/play_status.dart'; import 'package:pilipala/plugin/pl_player/utils.dart'; import 'package:pilipala/utils/feed_back.dart'; +import 'package:pilipala/utils/storage.dart'; import 'package:screen_brightness/screen_brightness.dart'; import 'package:volume_controller/volume_controller.dart'; @@ -62,6 +65,9 @@ class _PLVideoPlayerState extends State bool _volumeInterceptEventStream = false; + Box setting = GStrorage.setting; + late FullScreenMode mode; + void onDoubleTapSeekBackward() { setState(() { _mountSeekBackwardButton = true; @@ -149,16 +155,37 @@ class _PLVideoPlayerState extends State Future triggerFullScreen() async { PlPlayerController _ = widget.controller; + mode = FullScreenModeCode.fromCode( + setting.get(SettingBoxKey.fullScreenMode, defaultValue: 0))!; + if (!_.isFullScreen.value) { /// 按照视频宽高比决定全屏方向 - if (_.direction.value == 'horizontal') { - /// 进入全屏 - await enterFullScreen(); - // 横屏 - // await landScape(); - } else { - // 竖屏 - await verticalScreen(); + switch (mode) { + case FullScreenMode.auto: + if (_.direction.value == 'horizontal') { + /// 进入全屏 + await enterFullScreen(); + // 横屏 + // await landScape(); + } else { + // 竖屏 + await verticalScreen(); + } + break; + case FullScreenMode.vertical: + + /// 进入全屏 + await enterFullScreen(); + // 横屏 + // await landScape(); + break; + case FullScreenMode.horizontal: + + /// 进入全屏 + await enterFullScreen(); + // 横屏 + // await landScape(); + break; } _.toggleFullScreen(true); diff --git a/lib/utils/storage.dart b/lib/utils/storage.dart index f1a25f49..e7efc34b 100644 --- a/lib/utils/storage.dart +++ b/lib/utils/storage.dart @@ -79,6 +79,7 @@ class SettingBoxKey { static const String defaultPicQa = 'defaultPicQa'; static const String danmakuEnable = 'danmakuEnable'; + static const String fullScreenMode = 'fullScreenMode'; } class LocalCacheKey { From cf81897d27815e124fd4775f466f1634850bcee4 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 14 Aug 2023 10:14:59 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20=E9=A1=B5=E9=9D=A2=E4=BC=98?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/common/skeleton/video_card_h.dart | 2 +- lib/common/widgets/video_card_h.dart | 68 ++++-- lib/common/widgets/video_card_v.dart | 99 +++++--- lib/pages/bangumi/view.dart | 212 +++++++++--------- lib/pages/home/controller.dart | 10 - lib/pages/home/view.dart | 168 +++++--------- lib/pages/hot/view.dart | 80 ++++--- lib/pages/live/view.dart | 9 +- lib/pages/live/widgets/live_item.dart | 4 +- lib/pages/main/controller.dart | 20 +- lib/pages/main/view.dart | 20 +- lib/pages/rcmd/view.dart | 8 +- lib/pages/search/controller.dart | 11 + lib/pages/search/view.dart | 159 ++++++++----- .../introduction/widgets/fav_panel.dart | 9 +- lib/utils/storage.dart | 8 +- pubspec.lock | 8 + pubspec.yaml | 1 + 18 files changed, 494 insertions(+), 402 deletions(-) diff --git a/lib/common/skeleton/video_card_h.dart b/lib/common/skeleton/video_card_h.dart index 547f836c..efd86ff9 100644 --- a/lib/common/skeleton/video_card_h.dart +++ b/lib/common/skeleton/video_card_h.dart @@ -14,7 +14,7 @@ class VideoCardHSkeleton extends StatelessWidget { child: LayoutBuilder( builder: (context, boxConstraints) { double width = - (boxConstraints.maxWidth - StyleString.cardSpace * 9) / 2; + (boxConstraints.maxWidth - StyleString.cardSpace * 6) / 2; return SizedBox( height: width / StyleString.aspectRatio, child: Row( diff --git a/lib/common/widgets/video_card_h.dart b/lib/common/widgets/video_card_h.dart index 02ec260f..37e227b6 100644 --- a/lib/common/widgets/video_card_h.dart +++ b/lib/common/widgets/video_card_h.dart @@ -57,7 +57,7 @@ class VideoCardH extends StatelessWidget { child: LayoutBuilder( builder: (context, boxConstraints) { double width = - (boxConstraints.maxWidth - StyleString.cardSpace * 9) / 2; + (boxConstraints.maxWidth - StyleString.cardSpace * 6) / 2; return SizedBox( height: width / StyleString.aspectRatio, child: Row( @@ -83,10 +83,10 @@ class VideoCardH extends StatelessWidget { 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), + // if (videoItem.rcmdReason != null && + // videoItem.rcmdReason.content != '') + // pBadge(videoItem.rcmdReason.content, context, + // 6.0, 6.0, null, null), ], ); }, @@ -124,7 +124,6 @@ class VideoContent extends StatelessWidget { style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w500, - letterSpacing: 0.3, ), maxLines: 2, overflow: TextOverflow.ellipsis, @@ -198,24 +197,59 @@ class VideoContent extends StatelessWidget { // color: Theme.of(context).colorScheme.outline), // ) const Spacer(), + // SizedBox( + // width: 20, + // height: 20, + // child: IconButton( + // tooltip: '稍后再看', + // style: ButtonStyle( + // padding: MaterialStateProperty.all(EdgeInsets.zero), + // ), + // onPressed: () async { + // var res = + // await UserHttp.toViewLater(bvid: videoItem.bvid); + // SmartDialog.showToast(res['msg']); + // }, + // icon: Icon( + // Icons.more_vert_outlined, + // color: Theme.of(context).colorScheme.outline, + // size: 14, + // ), + // ), + // ), SizedBox( - width: 20, - height: 20, - child: IconButton( + width: 24, + height: 24, + child: PopupMenuButton( + padding: EdgeInsets.zero, tooltip: '稍后再看', - style: ButtonStyle( - padding: MaterialStateProperty.all(EdgeInsets.zero), - ), - onPressed: () async { - var res = - await UserHttp.toViewLater(bvid: videoItem.bvid); - SmartDialog.showToast(res['msg']); - }, icon: Icon( Icons.more_vert_outlined, color: Theme.of(context).colorScheme.outline, size: 14, ), + position: PopupMenuPosition.under, + // constraints: const BoxConstraints(maxHeight: 35), + onSelected: (String type) {}, + itemBuilder: (BuildContext context) => + >[ + PopupMenuItem( + onTap: () async { + var res = + await UserHttp.toViewLater(bvid: videoItem.bvid); + SmartDialog.showToast(res['msg']); + }, + value: 'pause', + height: 35, + child: const Row( + children: [ + Icon(Icons.watch_later_outlined, size: 16), + SizedBox(width: 6), + Text('稍后再看', style: TextStyle(fontSize: 13)) + ], + ), + ), + ], ), ), ], diff --git a/lib/common/widgets/video_card_v.dart b/lib/common/widgets/video_card_v.dart index 97e1077c..bf5cbfca 100644 --- a/lib/common/widgets/video_card_v.dart +++ b/lib/common/widgets/video_card_v.dart @@ -5,7 +5,6 @@ import 'package:pilipala/common/constants.dart'; import 'package:pilipala/common/widgets/stat/danmu.dart'; import 'package:pilipala/common/widgets/stat/view.dart'; import 'package:pilipala/http/user.dart'; -import 'package:pilipala/pages/rcmd/index.dart'; import 'package:pilipala/utils/id_utils.dart'; import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; @@ -106,17 +105,13 @@ class VideoCardV extends StatelessWidget { } class VideoContent extends StatelessWidget { - // ignore: prefer_typing_uninitialized_variables - final videoItem; + final dynamic videoItem; const VideoContent({Key? key, required this.videoItem}) : super(key: key); @override Widget build(BuildContext context) { return Expanded( child: Padding( - // 多列 - padding: const EdgeInsets.fromLTRB(4, 5, 0, 3), - // 单列 - // padding: const EdgeInsets.fromLTRB(14, 10, 4, 8), + padding: const EdgeInsets.fromLTRB(4, 8, 0, 3), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -124,12 +119,8 @@ class VideoContent extends StatelessWidget { Text( videoItem.title, textAlign: TextAlign.start, - style: const TextStyle( - fontSize: 13, - fontWeight: FontWeight.w500, - letterSpacing: 0.3, - ), - maxLines: Get.find().crossAxisCount, + style: const TextStyle(fontSize: 13), + maxLines: 2, overflow: TextOverflow.ellipsis, ), @@ -181,38 +172,90 @@ class VideoContent extends StatelessWidget { }), ), SizedBox( - width: 20, - height: 20, - child: IconButton( + width: 24, + height: 24, + child: PopupMenuButton( + padding: EdgeInsets.zero, tooltip: '稍后再看', - style: ButtonStyle( - padding: MaterialStateProperty.all(EdgeInsets.zero), - ), - onPressed: () async { - var res = - await UserHttp.toViewLater(bvid: videoItem.bvid); - SmartDialog.showToast(res['msg']); - }, icon: Icon( Icons.more_vert_outlined, color: Theme.of(context).colorScheme.outline, size: 14, ), + position: PopupMenuPosition.under, + // constraints: const BoxConstraints(maxHeight: 35), + onSelected: (String type) {}, + itemBuilder: (BuildContext context) => + >[ + PopupMenuItem( + onTap: () async { + var res = + await UserHttp.toViewLater(bvid: videoItem.bvid); + SmartDialog.showToast(res['msg']); + }, + value: 'pause', + height: 35, + child: const Row( + children: [ + Icon(Icons.watch_later_outlined, size: 16), + SizedBox(width: 6), + Text('稍后再看', style: TextStyle(fontSize: 13)) + ], + ), + ), + ], ), ), ], ), // Row( // children: [ + // const SizedBox(width: 1), // StatView( - // theme: 'black', + // theme: 'gray', // view: videoItem.stat.view, // ), - // const SizedBox(width: 6), + // const SizedBox(width: 10), // StatDanMu( - // theme: 'black', + // theme: 'gray', // danmu: videoItem.stat.danmaku, // ), + // const Spacer(), + // SizedBox( + // width: 24, + // height: 24, + // child: PopupMenuButton( + // padding: EdgeInsets.zero, + // tooltip: '稍后再看', + // icon: Icon( + // Icons.more_vert_outlined, + // color: Theme.of(context).colorScheme.outline, + // size: 14, + // ), + // position: PopupMenuPosition.under, + // // constraints: const BoxConstraints(maxHeight: 35), + // onSelected: (String type) {}, + // itemBuilder: (BuildContext context) => + // >[ + // PopupMenuItem( + // onTap: () async { + // var res = + // await UserHttp.toViewLater(bvid: videoItem.bvid); + // SmartDialog.showToast(res['msg']); + // }, + // value: 'pause', + // height: 35, + // child: const Row( + // children: [ + // Icon(Icons.watch_later_outlined, size: 16), + // SizedBox(width: 6), + // Text('稍后再看', style: TextStyle(fontSize: 13)) + // ], + // ), + // ), + // ], + // ), + // ), // ], // ), ], @@ -237,7 +280,7 @@ class VideoStat extends StatelessWidget { @override Widget build(BuildContext context) { return Container( - height: 45, + height: 48, padding: const EdgeInsets.only(top: 22, left: 6, right: 6), decoration: const BoxDecoration( gradient: LinearGradient( diff --git a/lib/pages/bangumi/view.dart b/lib/pages/bangumi/view.dart index d357c7cd..cf68a194 100644 --- a/lib/pages/bangumi/view.dart +++ b/lib/pages/bangumi/view.dart @@ -58,127 +58,121 @@ class _BangumiPageState extends State @override Widget build(BuildContext context) { super.build(context); - return Container( - clipBehavior: Clip.hardEdge, - margin: const EdgeInsets.only( - left: StyleString.safeSpace, right: StyleString.safeSpace), - decoration: const BoxDecoration( - borderRadius: BorderRadius.all(StyleString.imgRadius), - ), - child: RefreshIndicator( - onRefresh: () async { - await _bangumidController.queryBangumiListFeed(type: 'init'); - return _bangumidController.queryBangumiFollow(); - }, - child: CustomScrollView( - controller: _bangumidController.scrollController, - slivers: [ - SliverToBoxAdapter( - child: Obx( - () => Visibility( - visible: _bangumidController.userLogin.value, - child: Column( - children: [ - Padding( - padding: - const EdgeInsets.only(top: 10, bottom: 10, left: 6), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - '最近追番', - style: Theme.of(context).textTheme.titleMedium, - ), - ], - ), + return RefreshIndicator( + onRefresh: () async { + await _bangumidController.queryBangumiListFeed(type: 'init'); + return _bangumidController.queryBangumiFollow(); + }, + child: CustomScrollView( + controller: _bangumidController.scrollController, + slivers: [ + SliverToBoxAdapter( + child: Obx( + () => Visibility( + visible: _bangumidController.userLogin.value, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only( + top: StyleString.safeSpace, bottom: 10, left: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '最近追番', + style: Theme.of(context).textTheme.titleMedium, + ), + ], ), - SizedBox( - height: 254, - child: FutureBuilder( - future: _bangumidController.queryBangumiFollow(), - builder: (context, snapshot) { - if (snapshot.connectionState == - ConnectionState.done) { - Map data = snapshot.data as Map; - if (data['status']) { - return Obx( - () => ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: _bangumidController - .bangumiFollowList.length, - itemBuilder: (context, index) { - return Container( - width: Get.size.width / 3, - height: 254, - margin: EdgeInsets.only( - right: index < - _bangumidController - .bangumiFollowList - .length - - 1 - ? StyleString.safeSpace - : 0), - child: BangumiCardV( - bangumiItem: _bangumidController - .bangumiFollowList[index], - ), - ); - }, - ), - ); - } else { - return SizedBox(); - } + ), + SizedBox( + height: 258, + child: FutureBuilder( + future: _bangumidController.queryBangumiFollow(), + builder: (context, snapshot) { + if (snapshot.connectionState == + ConnectionState.done) { + Map data = snapshot.data as Map; + if (data['status']) { + return Obx( + () => ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: _bangumidController + .bangumiFollowList.length, + itemBuilder: (context, index) { + return Container( + width: Get.size.width / 3, + height: 254, + margin: EdgeInsets.only( + left: StyleString.safeSpace, + right: index == + _bangumidController + .bangumiFollowList + .length - + 1 + ? StyleString.safeSpace + : 0), + child: BangumiCardV( + bangumiItem: _bangumidController + .bangumiFollowList[index], + ), + ); + }, + ), + ); } else { return SizedBox(); } - }, - ), + } else { + return SizedBox(); + } + }, ), - ], - ), - ), - ), - ), - SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.only(top: 10, bottom: 10, left: 6), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - '推荐', - style: Theme.of(context).textTheme.titleMedium, ), ], ), ), ), - SliverPadding( - padding: EdgeInsets.zero, - sliver: FutureBuilder( - future: _futureBuilderFuture, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - Map data = snapshot.data as Map; - if (data['status']) { - return Obx(() => contentGrid(_bangumidController, - _bangumidController.bangumiList)); - } else { - return HttpError( - errMsg: data['msg'], - fn: () => {}, - ); - } - } else { - return contentGrid(_bangumidController, []); - } - }, + ), + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.only(top: 10, bottom: 10, left: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '推荐', + style: Theme.of(context).textTheme.titleMedium, + ), + ], ), ), - const LoadingMore() - ], - ), + ), + SliverPadding( + padding: const EdgeInsets.fromLTRB( + StyleString.safeSpace, 0, StyleString.safeSpace, 0), + sliver: FutureBuilder( + future: _futureBuilderFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + Map data = snapshot.data as Map; + if (data['status']) { + return Obx(() => contentGrid( + _bangumidController, _bangumidController.bangumiList)); + } else { + return HttpError( + errMsg: data['msg'], + fn: () => {}, + ); + } + } else { + return contentGrid(_bangumidController, []); + } + }, + ), + ), + const LoadingMore() + ], ), ); } diff --git a/lib/pages/home/controller.dart b/lib/pages/home/controller.dart index f5ce88ac..980d7381 100644 --- a/lib/pages/home/controller.dart +++ b/lib/pages/home/controller.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:hive/hive.dart'; -import 'package:pilipala/http/index.dart'; import 'package:pilipala/models/common/tab_type.dart'; import 'package:pilipala/utils/storage.dart'; @@ -12,7 +11,6 @@ class HomeController extends GetxController with GetTickerProviderStateMixin { late TabController tabController; late List tabsCtrList; late List tabsPageList; - RxString defaultSearch = '输入关键词搜索'.obs; Box user = GStrorage.user; RxBool userLogin = false.obs; RxString userFace = ''.obs; @@ -21,7 +19,6 @@ class HomeController extends GetxController with GetTickerProviderStateMixin { void onInit() { super.onInit(); - searchDefault(); userLogin.value = user.get(UserBoxKey.userLogin) ?? false; userFace.value = user.get(UserBoxKey.userFace) ?? ''; @@ -49,13 +46,6 @@ class HomeController extends GetxController with GetTickerProviderStateMixin { ctr().animateToTop(); } - void searchDefault() async { - var res = await Request().get(Api.searchDefault); - if (res.data['code'] == 0) { - defaultSearch.value = res.data['data']['name']; - } - } - // 更新登录状态 void updateLoginStatus(val) { userLogin.value = val ?? false; diff --git a/lib/pages/home/view.dart b/lib/pages/home/view.dart index 8c65ebf9..9ad8a25f 100644 --- a/lib/pages/home/view.dart +++ b/lib/pages/home/view.dart @@ -1,12 +1,9 @@ import 'package:flutter/material.dart'; import 'package:get/get.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/pages/search/index.dart'; import 'package:pilipala/utils/feed_back.dart'; import './controller.dart'; @@ -53,49 +50,21 @@ class _HomePageState extends State ctr: _homeController, callback: showUserBottonSheet, ), - Padding( - padding: const EdgeInsets.only(left: 12, right: 12, bottom: 4), - child: Theme( - data: ThemeData( - splashColor: Colors.transparent, // 点击时的水波纹颜色设置为透明 - highlightColor: Colors.transparent, // 点击时的背景高亮颜色设置为透明 - ), + const SizedBox(height: 8), + SizedBox( + width: double.infinity, + height: 42, + child: Align( + alignment: Alignment.center, child: TabBar( controller: _homeController.tabController, tabs: [ for (var i in _homeController.tabs) Tab(text: i['label']) ], isScrollable: true, - indicatorWeight: 0, - indicatorPadding: const EdgeInsets.only( - top: 37, left: 18, right: 18, bottom: 6), - indicatorColor: Colors.black, - indicator: BoxDecoration( - gradient: RadialGradient( - center: Alignment.centerLeft, - radius: 20.00, - colors: [ - Theme.of(context).colorScheme.primary, - Theme.of(context).colorScheme.background, - ], - ), - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(4), - topRight: Radius.circular(2), - bottomLeft: Radius.circular(2), - bottomRight: Radius.circular(4), - ), - ), - indicatorSize: TabBarIndicatorSize.tab, - labelColor: Theme.of(context).colorScheme.primary, - labelStyle: - const TextStyle(fontSize: 13, fontWeight: FontWeight.bold), dividerColor: Colors.transparent, - unselectedLabelStyle: TextStyle( - color: Theme.of(context).colorScheme.outline, - fontWeight: FontWeight.normal, - ), - unselectedLabelColor: Theme.of(context).colorScheme.outline, + enableFeedback: true, + splashBorderRadius: BorderRadius.circular(10), onTap: (value) { feedBack(); if (_homeController.initialIndex == value) { @@ -141,83 +110,52 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { 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 - ? MediaQuery.of(context).padding.top + 42 - : MediaQuery.of(context).padding.top, - child: Container( - padding: EdgeInsets.only( - left: 12, - right: 12, - bottom: 0, - top: MediaQuery.of(context).padding.top, - ), - child: Row(children: [ - Image.asset( - 'assets/images/logo/logo_android_2.png', - color: Theme.of(context).colorScheme.primary, - ), - const SizedBox(width: 4), - Expanded( - child: GestureDetector( - onTap: () { - Get.toNamed('/search', - parameters: {'hintText': ctr!.defaultSearch.value}); - }, - child: Container( - width: 250, - height: 40, - 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: 21, - color: Theme.of(context).colorScheme.outline, - ), - const SizedBox(width: 6), - Expanded( - child: Obx( - () => Text( - ctr!.defaultSearch.value, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle( - color: Theme.of(context) - .colorScheme - .outline), - ), - ), - ), - ], - ), - ), - ), - ), + return AnimatedOpacity( + opacity: snapshot.data ? 1 : 0, + duration: const Duration(milliseconds: 300), + child: AnimatedContainer( + curve: Curves.easeInOutCubicEmphasized, + duration: const Duration(milliseconds: 500), + height: snapshot.data + ? MediaQuery.of(context).padding.top + 52 + : MediaQuery.of(context).padding.top, + child: Container( + padding: EdgeInsets.only( + left: 20, + right: 20, + bottom: 0, + top: MediaQuery.of(context).padding.top + 4, + ), + child: Row( + children: [ + const Expanded(child: SearchPage()), const SizedBox(width: 10), Obx( () => ctr!.userLogin.value - ? GestureDetector( - onTap: () => callback!(), - child: NetworkImgLayer( - type: 'avatar', - width: 38, - height: 38, - src: ctr!.userFace.value, - ), + ? Stack( + children: [ + NetworkImgLayer( + type: 'avatar', + width: 34, + height: 34, + src: ctr!.userFace.value, + ), + Positioned.fill( + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () => callback!(), + splashColor: Theme.of(context) + .colorScheme + .primaryContainer + .withOpacity(0.3), + borderRadius: const BorderRadius.all( + Radius.circular(50), + ), + ), + ), + ) + ], ) : SizedBox( width: 38, @@ -242,7 +180,7 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { ), ), ), - ]), + ], ), ), ), diff --git a/lib/pages/hot/view.dart b/lib/pages/hot/view.dart index aa990326..1b76c079 100644 --- a/lib/pages/hot/view.dart +++ b/lib/pages/hot/view.dart @@ -3,6 +3,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/widgets/animated_dialog.dart'; import 'package:pilipala/common/widgets/overlay_pop.dart'; import 'package:pilipala/common/skeleton/video_card_h.dart'; @@ -59,52 +60,57 @@ class _HotPageState extends State with AutomaticKeepAliveClientMixin { super.build(context); return Scaffold( body: RefreshIndicator( - displacement: kToolbarHeight + MediaQuery.of(context).padding.top, onRefresh: () async { return await _hotController.onRefresh(); }, child: CustomScrollView( controller: _hotController.scrollController, slivers: [ - FutureBuilder( - future: _futureBuilderFuture, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - Map data = snapshot.data as Map; - if (data['status']) { - return Obx( - () => SliverList( - delegate: SliverChildBuilderDelegate((context, index) { - return VideoCardH( - videoItem: _hotController.videoList[index], - longPress: () { - _hotController.popupDialog = _createPopupDialog( - _hotController.videoList[index]); - Overlay.of(context) - .insert(_hotController.popupDialog!); - }, - longPressEnd: () { - _hotController.popupDialog?.remove(); - }, - ); - }, childCount: _hotController.videoList.length), - ), - ); + SliverPadding( + // 单列布局 EdgeInsets.zero + padding: + const EdgeInsets.fromLTRB(0, StyleString.safeSpace - 5, 0, 0), + sliver: FutureBuilder( + future: _futureBuilderFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + Map data = snapshot.data as Map; + if (data['status']) { + return Obx( + () => SliverList( + delegate: + SliverChildBuilderDelegate((context, index) { + return VideoCardH( + videoItem: _hotController.videoList[index], + longPress: () { + _hotController.popupDialog = _createPopupDialog( + _hotController.videoList[index]); + Overlay.of(context) + .insert(_hotController.popupDialog!); + }, + longPressEnd: () { + _hotController.popupDialog?.remove(); + }, + ); + }, childCount: _hotController.videoList.length), + ), + ); + } else { + return HttpError( + errMsg: data['msg'], + fn: () => setState(() {}), + ); + } } else { - return HttpError( - errMsg: data['msg'], - fn: () => setState(() {}), + // 骨架屏 + return SliverList( + delegate: SliverChildBuilderDelegate((context, index) { + return const VideoCardHSkeleton(); + }, childCount: 10), ); } - } else { - // 骨架屏 - return SliverList( - delegate: SliverChildBuilderDelegate((context, index) { - return const VideoCardHSkeleton(); - }, childCount: 10), - ); - } - }, + }, + ), ), SliverToBoxAdapter( child: SizedBox( diff --git a/lib/pages/live/view.dart b/lib/pages/live/view.dart index 20eeb09c..8947212b 100644 --- a/lib/pages/live/view.dart +++ b/lib/pages/live/view.dart @@ -70,7 +70,8 @@ class _LivePageState extends State { slivers: [ SliverPadding( // 单列布局 EdgeInsets.zero - padding: EdgeInsets.zero, + padding: + const EdgeInsets.fromLTRB(0, StyleString.safeSpace, 0, 0), sliver: FutureBuilder( future: _liveController.queryLiveList('init'), builder: (context, snapshot) { @@ -118,13 +119,13 @@ class _LivePageState extends State { return SliverGrid( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( // 行间距 - mainAxisSpacing: StyleString.cardSpace + 2, + mainAxisSpacing: StyleString.cardSpace + 4, // 列间距 - crossAxisSpacing: StyleString.cardSpace + 3, + crossAxisSpacing: StyleString.cardSpace + 4, // 列数 crossAxisCount: ctr.crossAxisCount, mainAxisExtent: - Get.size.width / ctr.crossAxisCount / StyleString.aspectRatio + 60, + Get.size.width / ctr.crossAxisCount / StyleString.aspectRatio + 64, ), delegate: SliverChildBuilderDelegate( (BuildContext context, int index) { diff --git a/lib/pages/live/widgets/live_item.dart b/lib/pages/live/widgets/live_item.dart index 692aa039..9a04255a 100644 --- a/lib/pages/live/widgets/live_item.dart +++ b/lib/pages/live/widgets/live_item.dart @@ -103,7 +103,7 @@ class LiveContent extends StatelessWidget { return Expanded( child: Padding( // 多列 - padding: const EdgeInsets.fromLTRB(4, 5, 6, 6), + padding: const EdgeInsets.fromLTRB(4, 8, 0, 6), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -154,7 +154,7 @@ class VideoStat extends StatelessWidget { @override Widget build(BuildContext context) { return Container( - height: 45, + height: 50, padding: const EdgeInsets.only(top: 22, left: 10, right: 10), decoration: const BoxDecoration( gradient: LinearGradient( diff --git a/lib/pages/main/controller.dart b/lib/pages/main/controller.dart index 122edb87..1d2385e0 100644 --- a/lib/pages/main/controller.dart +++ b/lib/pages/main/controller.dart @@ -15,23 +15,35 @@ class MainController extends GetxController { RxList navigationBars = [ { 'icon': const Icon( - Icons.motion_photos_on_outlined, + Icons.favorite_outline, size: 21, ), - 'label': "推荐", + 'selectIcon': const Icon( + Icons.favorite, + size: 21, + ), + 'label': "首页", }, { 'icon': const Icon( - Icons.bolt, + Icons.motion_photos_on_outlined, + size: 21, + ), + 'selectIcon': const Icon( + Icons.motion_photos_on, size: 21, ), 'label': "动态", }, { 'icon': const Icon( - Icons.folder_open_outlined, + Icons.folder_outlined, size: 20, ), + 'selectIcon': const Icon( + Icons.folder, + size: 21, + ), 'label': "媒体库", } ].obs; diff --git a/lib/pages/main/view.dart b/lib/pages/main/view.dart index 4cec9ac8..8a1cdeef 100644 --- a/lib/pages/main/view.dart +++ b/lib/pages/main/view.dart @@ -135,21 +135,17 @@ class _MainAppState extends State with SingleTickerProviderStateMixin { initialData: true, builder: (context, AsyncSnapshot snapshot) { return AnimatedSlide( - curve: Curves.linear, - duration: const Duration(milliseconds: 300), + curve: Curves.easeInOutCubicEmphasized, + duration: const Duration(milliseconds: 1000), 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.outline.withOpacity(0.5), - selectedFontSize: 12.4, - onTap: (value) => setIndex(value), - items: [ + child: NavigationBar( + onDestinationSelected: (value) => setIndex(value), + selectedIndex: selectedIndex, + destinations: [ ..._mainController.navigationBars.map((e) { - return BottomNavigationBarItem( + return NavigationDestination( icon: e['icon'], + selectedIcon: e['selectIcon'], label: e['label'], ); }).toList(), diff --git a/lib/pages/rcmd/view.dart b/lib/pages/rcmd/view.dart index 8fea2f79..a5338d7a 100644 --- a/lib/pages/rcmd/view.dart +++ b/lib/pages/rcmd/view.dart @@ -77,7 +77,7 @@ class _RcmdPageState extends State // 单列布局 EdgeInsets.zero padding: _rcmdController.crossAxisCount == 1 ? EdgeInsets.zero - : const EdgeInsets.fromLTRB(0, 0, 0, 0), + : const EdgeInsets.fromLTRB(0, StyleString.safeSpace, 0, 0), sliver: FutureBuilder( future: _rcmdController.queryRcmdFeed('init'), builder: (context, snapshot) { @@ -124,13 +124,13 @@ class _RcmdPageState extends State return SliverGrid( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( // 行间距 - mainAxisSpacing: StyleString.cardSpace + 2, + mainAxisSpacing: StyleString.cardSpace + 4, // 列间距 - crossAxisSpacing: StyleString.cardSpace + 3, + crossAxisSpacing: StyleString.cardSpace + 4, // 列数 crossAxisCount: ctr.crossAxisCount, mainAxisExtent: - Get.size.width / ctr.crossAxisCount / StyleString.aspectRatio + 60, + Get.size.width / ctr.crossAxisCount / StyleString.aspectRatio + 64, ), delegate: SliverChildBuilderDelegate( (BuildContext context, int index) { diff --git a/lib/pages/search/controller.dart b/lib/pages/search/controller.dart index 8fc33ffc..9b3f7dad 100644 --- a/lib/pages/search/controller.dart +++ b/lib/pages/search/controller.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:get/get_rx/src/rx_workers/utils/debouncer.dart'; import 'package:hive/hive.dart'; +import 'package:pilipala/http/index.dart'; import 'package:pilipala/http/search.dart'; import 'package:pilipala/models/search/hot.dart'; import 'package:pilipala/models/search/suggest.dart'; @@ -20,10 +21,12 @@ class SSearchController extends GetxController { final _debouncer = Debouncer(delay: const Duration(milliseconds: 200)); // 设置延迟时间 String hintText = '搜索'; + RxString defaultSearch = '输入关键词搜索'.obs; @override void onInit() { super.onInit(); + searchDefault(); if (hotKeyword.get('cacheList') != null && hotKeyword.get('cacheList').isNotEmpty) { List list = []; @@ -121,4 +124,12 @@ class SSearchController extends GetxController { historyList.refresh(); histiryWord.put('cacheList', []); } + + void searchDefault() async { + var res = await Request().get(Api.searchDefault); + if (res.data['code'] == 0) { + searchKeyWord.value = + hintText = defaultSearch.value = res.data['data']['name']; + } + } } diff --git a/lib/pages/search/view.dart b/lib/pages/search/view.dart index 726b0a0d..d4190e47 100644 --- a/lib/pages/search/view.dart +++ b/lib/pages/search/view.dart @@ -1,5 +1,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:animations/animations.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/widgets/http_error.dart'; import 'controller.dart'; @@ -41,61 +42,117 @@ class _SearchPageState extends State with RouteAware { @override Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, - appBar: AppBar( - shape: Border( - bottom: BorderSide( - color: Theme.of(context).dividerColor.withOpacity(0.08), - width: 1, + return OpenContainer( + closedElevation: 0, + openElevation: 0, + openColor: Theme.of(context).colorScheme.background, + middleColor: Theme.of(context).colorScheme.background, + closedColor: Theme.of(context).colorScheme.background, + closedShape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(30.0))), + openShape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(30.0))), + closedBuilder: (BuildContext context, VoidCallback openContainer) { + return Container( + width: 250, + height: 44, + clipBehavior: Clip.hardEdge, + decoration: const BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(25)), ), - ), - titleSpacing: 0, - actions: [ - Hero( - tag: 'searchTag', - child: IconButton( - onPressed: () => _searchController.submit(), - icon: const Icon(CupertinoIcons.search, size: 22)), - ), - 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: _searchController.hintText, - border: InputBorder.none, - suffixIcon: IconButton( - icon: Icon( - Icons.clear, - size: 22, - color: Theme.of(context).colorScheme.outline, - ), - onPressed: () => _searchController.onClear(), + child: Material( + color: + Theme.of(context).colorScheme.secondaryContainer.withAlpha(115), + child: InkWell( + splashColor: Theme.of(context) + .colorScheme + .primaryContainer + .withOpacity(0.3), + onTap: openContainer, + child: Row( + children: [ + const SizedBox(width: 14), + Icon( + Icons.search_outlined, + color: Theme.of(context).colorScheme.onSecondaryContainer, + ), + const SizedBox(width: 10), + Expanded( + child: Obx( + () => Text( + _searchController.defaultSearch.value, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: Theme.of(context).colorScheme.outline, + ), + ), + ), + ), + ], ), ), - onSubmitted: (String value) => _searchController.submit(), ), - ), - ), - body: SingleChildScrollView( - child: Column( - children: [ - const SizedBox(height: 12), - // 搜索建议 - _searchSuggest(), - // 热搜 - hotSearch(), - // 搜索历史 - _history() - ], - ), - ), + ); + }, + openBuilder: (BuildContext context, VoidCallback _) { + return Scaffold( + resizeToAvoidBottomInset: false, + appBar: AppBar( + shape: Border( + bottom: BorderSide( + color: Theme.of(context).dividerColor.withOpacity(0.08), + width: 1, + ), + ), + titleSpacing: 0, + actions: [ + Hero( + tag: 'searchTag', + child: IconButton( + onPressed: () => _searchController.submit(), + icon: const Icon(CupertinoIcons.search, size: 22)), + ), + 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: _searchController.hintText, + border: InputBorder.none, + suffixIcon: IconButton( + icon: Icon( + Icons.clear, + size: 22, + color: Theme.of(context).colorScheme.outline, + ), + onPressed: () => _searchController.onClear(), + ), + ), + onSubmitted: (String value) => _searchController.submit(), + ), + ), + ), + body: SingleChildScrollView( + child: Column( + children: [ + const SizedBox(height: 12), + // 搜索建议 + _searchSuggest(), + // 热搜 + hotSearch(), + // 搜索历史 + _history() + ], + ), + ), + ); + }, ); } diff --git a/lib/pages/video/detail/introduction/widgets/fav_panel.dart b/lib/pages/video/detail/introduction/widgets/fav_panel.dart index 6a52e683..68f53772 100644 --- a/lib/pages/video/detail/introduction/widgets/fav_panel.dart +++ b/lib/pages/video/detail/introduction/widgets/fav_panel.dart @@ -102,10 +102,11 @@ class _FavPanelState extends State { ), Padding( padding: EdgeInsets.only( - left: 20, - right: 20, - top: 12, - bottom: MediaQuery.of(context).padding.bottom), + left: 20, + right: 20, + top: 12, + bottom: MediaQuery.of(context).padding.bottom + 12, + ), child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ diff --git a/lib/utils/storage.dart b/lib/utils/storage.dart index f1a25f49..2807d00f 100644 --- a/lib/utils/storage.dart +++ b/lib/utils/storage.dart @@ -31,6 +31,10 @@ class GStrorage { localCache = await Hive.openBox('localCache'); // 设置 setting = await Hive.openBox('setting'); + // 热搜关键词 + hotKeyword = await Hive.openBox('hotKeyword'); + // 搜索历史 + historyword = await Hive.openBox('historyWord'); } static regAdapter() { @@ -45,10 +49,6 @@ class GStrorage { } static Future lazyInit() async { - // 热搜关键词 - hotKeyword = await Hive.openBox('hotKeyword'); - // 搜索历史 - historyword = await Hive.openBox('historyWord'); // 视频设置 video = await Hive.openBox('video'); } diff --git a/pubspec.lock b/pubspec.lock index 1b1a5f1c..2e84ff6b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -17,6 +17,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.13.0" + animations: + dependency: "direct main" + description: + name: animations + sha256: fe8a6bdca435f718bb1dc8a11661b2c22504c6da40ef934cee8327ed77934164 + url: "https://pub.dev" + source: hosted + version: "2.0.7" archive: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index a739df24..0c0f442b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -107,6 +107,7 @@ dependencies: universal_platform: ^1.0.0+1 # 进度条 audio_video_progress_bar: ^1.0.1 + animations: ^2.0.7 # auto_orientation: ^2.3.1 dev_dependencies: