diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 94db6539..55667534 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -20,7 +20,22 @@ "android.support.customtabs.action.CustomTabsService" /> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -56,7 +229,6 @@ android:value="2" /> - diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 5100bf76..183d026e 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,4 +1,6 @@ PODS: + - appscheme (1.0.4): + - Flutter - connectivity_plus (0.0.1): - Flutter - ReachabilitySwift @@ -45,6 +47,7 @@ PODS: - Flutter DEPENDENCIES: + - appscheme (from `.symlinks/plugins/appscheme/ios`) - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - Flutter (from `Flutter`) @@ -71,6 +74,8 @@ SPEC REPOS: - ReachabilitySwift EXTERNAL SOURCES: + appscheme: + :path: ".symlinks/plugins/appscheme/ios" connectivity_plus: :path: ".symlinks/plugins/connectivity_plus/ios" device_info_plus: @@ -111,6 +116,7 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/webview_flutter_wkwebview/ios" SPEC CHECKSUMS: + appscheme: b1c3f8862331cb20430cf9e0e4af85dbc1572ad8 connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a device_info_plus: 7545d84d8d1b896cb16a4ff98c19f07ec4b298ea Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 863a8ef8..57e29a51 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -1,62 +1,107 @@ - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - PiliPala - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - pilipala - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleSignature - ???? - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UIViewControllerBasedStatusBarAppearance - - CADisableMinimumFrameDurationOnPhone - - UIApplicationSupportsIndirectInputEvents - - NSPhotoLibraryAddUsageDescription - 请允许APP保存图片到相册 - NSCameraUsageDescription - App需要您的同意,才能访问相册 - NSAppleMusicUsageDescription - App需要您的同意,才能访问媒体资料库 - LSApplicationQueriesSchemes - - https - http - - + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + PiliPala + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + PiliPala + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + NSPhotoLibraryAddUsageDescription + 请允许APP保存图片到相册 + NSCameraUsageDescription + App需要您的同意,才能访问相册 + NSAppleMusicUsageDescription + App需要您的同意,才能访问媒体资料库 + LSApplicationQueriesSchemes + + https + http + + + CFBundleURLTypes + + + CFBundleURLName + + CFBundleURLSchemes + + http + https + + CFBundleURLTypes + + + CFBundleURLName + + CFBundleURLSchemes + + m.bilibili.com + bilibili.com + www.bilibili.com + bangumi.bilibili.com + bilibili.cn + www.bilibili.cn + bangumi.bilibili.cn + bilibili.tv + www.bilibili.tv + bangumi.bilibili.tv + miniapp.bilibili.com + live.bilibili.com + + + + + + + + CFBundleURLName + bilibili + CFBundleURLSchemes + + bilibili + + + + diff --git a/lib/common/skeleton/video_card_v.dart b/lib/common/skeleton/video_card_v.dart index fc247e56..d13eaee3 100644 --- a/lib/common/skeleton/video_card_v.dart +++ b/lib/common/skeleton/video_card_v.dart @@ -45,11 +45,6 @@ class VideoCardVSkeleton extends StatelessWidget { margin: const EdgeInsets.only(bottom: 12), color: Theme.of(context).colorScheme.onInverseSurface, ), - Container( - width: 80, - height: 12, - color: Theme.of(context).colorScheme.onInverseSurface, - ), ], ), ), diff --git a/lib/common/widgets/video_card_v.dart b/lib/common/widgets/video_card_v.dart index eb37d0e1..c81c878b 100644 --- a/lib/common/widgets/video_card_v.dart +++ b/lib/common/widgets/video_card_v.dart @@ -15,12 +15,14 @@ import 'package:pilipala/common/widgets/network_img_layer.dart'; // 视频卡片 - 垂直布局 class VideoCardV extends StatelessWidget { final dynamic videoItem; + final int crossAxisCount; final Function()? longPress; final Function()? longPressEnd; const VideoCardV({ Key? key, required this.videoItem, + required this.crossAxisCount, this.longPress, this.longPressEnd, }) : super(key: key); @@ -77,7 +79,7 @@ class VideoCardV extends StatelessWidget { Widget build(BuildContext context) { String heroTag = Utils.makeHeroTag(videoItem.id); return Card( - elevation: 1, + elevation: crossAxisCount == 1 ? 0 : 1, clipBehavior: Clip.hardEdge, margin: EdgeInsets.zero, child: GestureDetector( @@ -100,17 +102,27 @@ class VideoCardV extends StatelessWidget { child: LayoutBuilder(builder: (context, boxConstraints) { double maxWidth = boxConstraints.maxWidth; double maxHeight = boxConstraints.maxHeight; - return Hero( - tag: heroTag, - child: NetworkImgLayer( - src: videoItem.pic, - width: maxWidth, - height: maxHeight, - ), + return Stack( + children: [ + Hero( + tag: heroTag, + child: NetworkImgLayer( + src: videoItem.pic, + width: maxWidth, + height: maxHeight, + ), + ), + if (crossAxisCount == 1) + PBadge( + bottom: 10, + right: 10, + text: videoItem.duration, + ) + ], ); }), ), - VideoContent(videoItem: videoItem) + VideoContent(videoItem: videoItem, crossAxisCount: crossAxisCount) ], ), ), @@ -121,22 +133,47 @@ class VideoCardV extends StatelessWidget { class VideoContent extends StatelessWidget { final dynamic videoItem; - const VideoContent({Key? key, required this.videoItem}) : super(key: key); + final int crossAxisCount; + const VideoContent( + {Key? key, required this.videoItem, required this.crossAxisCount}) + : super(key: key); @override Widget build(BuildContext context) { return Expanded( + flex: crossAxisCount == 1 ? 0 : 1, child: Padding( - padding: const EdgeInsets.fromLTRB(9, 8, 9, 4), + padding: crossAxisCount == 1 + ? const EdgeInsets.fromLTRB(9, 9, 9, 4) + : const EdgeInsets.fromLTRB(9, 8, 9, 4), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - videoItem.title, - maxLines: 2, - overflow: TextOverflow.ellipsis, + Row( + children: [ + Expanded( + child: Text( + videoItem.title, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + if (videoItem.goto == 'av' && crossAxisCount == 1) ...[ + const SizedBox(width: 10), + WatchLater( + size: 32, + iconSize: 18, + callFn: () async { + int aid = videoItem.param; + var res = + await UserHttp.toViewLater(bvid: IdUtils.av2bv(aid)); + SmartDialog.showToast(res['msg']); + }, + ), + ], + ], ), - + if (crossAxisCount == 1) const SizedBox(height: 6), Row( children: [ if (videoItem.goto == 'bangumi') ...[ @@ -167,6 +204,7 @@ class VideoContent extends StatelessWidget { ) ], Expanded( + flex: crossAxisCount == 1 ? 0 : 1, child: Text( videoItem.owner.name, maxLines: 1, @@ -177,95 +215,33 @@ class VideoContent extends StatelessWidget { ), ), ), - if (videoItem.goto == 'av') - 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 { - int aid = videoItem.param; - var res = await UserHttp.toViewLater( - bvid: IdUtils.av2bv(aid)); - 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)) - ], - ), - ), - ], + if (crossAxisCount == 1) ...[ + Text( + ' • ', + style: TextStyle( + fontSize: + Theme.of(context).textTheme.labelMedium!.fontSize, + color: Theme.of(context).colorScheme.outline, ), ), + VideoStat( + videoItem: videoItem, + ) + ], + const Spacer(), + if (videoItem.goto == 'av' && crossAxisCount != 1) + WatchLater( + size: 24, + iconSize: 14, + callFn: () async { + int aid = videoItem.param; + var res = + await UserHttp.toViewLater(bvid: IdUtils.av2bv(aid)); + SmartDialog.showToast(res['msg']); + }, + ), ], ), - // Row( - // children: [ - // const SizedBox(width: 1), - // StatView( - // theme: 'gray', - // view: videoItem.stat.view, - // ), - // const SizedBox(width: 10), - // StatDanMu( - // 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)) - // ], - // ), - // ), - // ], - // ), - // ), - // ], - // ), ], ), ), @@ -274,53 +250,77 @@ class VideoContent extends StatelessWidget { } class VideoStat extends StatelessWidget { - final int? view; - final int? danmaku; - final int? duration; + final dynamic videoItem; - const VideoStat( - {Key? key, - required this.view, - required this.danmaku, - required this.duration}) - : super(key: key); + const VideoStat({ + Key? key, + required this.videoItem, + }) : super(key: key); @override Widget build(BuildContext context) { - return Container( - height: 48, - padding: const EdgeInsets.only(top: 22, left: 6, right: 6), - 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: 6), - StatDanMu( - theme: 'white', - danmu: danmaku, - ), - ], + return Row( + children: [ + Text( + '${videoItem.stat.view}次观看', + style: TextStyle( + fontSize: Theme.of(context).textTheme.labelMedium!.fontSize, + color: Theme.of(context).colorScheme.outline, + ), + ), + Text( + ' • ${videoItem.stat.danmu}条弹幕', + style: TextStyle( + fontSize: Theme.of(context).textTheme.labelMedium!.fontSize, + color: Theme.of(context).colorScheme.outline, + ), + ), + ], + ); + } +} + +class WatchLater extends StatelessWidget { + final double? size; + final double? iconSize; + final Function? callFn; + + const WatchLater({ + Key? key, + required this.size, + required this.iconSize, + this.callFn, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return SizedBox( + width: size, + height: size, + child: PopupMenuButton( + padding: EdgeInsets.zero, + tooltip: '稍后再看', + icon: Icon( + Icons.more_vert_outlined, + color: Theme.of(context).colorScheme.outline, + size: iconSize, + ), + position: PopupMenuPosition.under, + // constraints: const BoxConstraints(maxHeight: 35), + onSelected: (String type) {}, + itemBuilder: (BuildContext context) => >[ + PopupMenuItem( + onTap: () => callFn!(), + value: 'pause', + height: 35, + child: const Row( + children: [ + Icon(Icons.watch_later_outlined, size: 16), + SizedBox(width: 6), + Text('稍后再看', style: TextStyle(fontSize: 13)) + ], + ), ), - Text( - Utils.timeFormat(duration!), - style: const TextStyle(fontSize: 11, color: Colors.white), - ) ], ), ); diff --git a/lib/main.dart b/lib/main.dart index 0bb42d79..5040edca 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -13,6 +13,7 @@ import 'package:pilipala/pages/search/index.dart'; import 'package:pilipala/pages/video/detail/index.dart'; import 'package:pilipala/router/app_pages.dart'; import 'package:pilipala/pages/main/view.dart'; +import 'package:pilipala/utils/app_scheme.dart'; import 'package:pilipala/utils/data.dart'; import 'package:pilipala/utils/storage.dart'; import 'package:media_kit/media_kit.dart'; // Provides [Player], [Media], [Playlist] etc. @@ -25,9 +26,6 @@ void main() async { .then((_) async { await GStrorage.init(); runApp(const MyApp()); - await Request.setCookie(); - await Data.init(); - await GStrorage.lazyInit(); // 小白条、导航栏沉浸 SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle( @@ -35,6 +33,10 @@ void main() async { systemNavigationBarDividerColor: Colors.transparent, statusBarColor: Colors.transparent, )); + await Request.setCookie(); + Data.init(); + GStrorage.lazyInit(); + PiliSchame.init(); }); } diff --git a/lib/pages/live/controller.dart b/lib/pages/live/controller.dart index 2779659a..8cdf53a7 100644 --- a/lib/pages/live/controller.dart +++ b/lib/pages/live/controller.dart @@ -1,17 +1,27 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:hive/hive.dart'; import 'package:pilipala/http/live.dart'; import 'package:pilipala/models/live/item.dart'; +import 'package:pilipala/utils/storage.dart'; class LiveController extends GetxController { final ScrollController scrollController = ScrollController(); int count = 12; int _currentPage = 1; - int crossAxisCount = 2; + RxInt crossAxisCount = 2.obs; RxList liveList = [LiveItemModel()].obs; bool isLoadingMore = false; bool flag = false; OverlayEntry? popupDialog; + Box setting = GStrorage.setting; + + @override + void onInit() { + super.onInit(); + crossAxisCount.value = + setting.get(SettingBoxKey.enableSingleRow, defaultValue: false) ? 1 : 2; + } // 获取推荐 Future queryLiveList(type) async { diff --git a/lib/pages/live/view.dart b/lib/pages/live/view.dart index 1acf0dc7..5e3e68a1 100644 --- a/lib/pages/live/view.dart +++ b/lib/pages/live/view.dart @@ -129,14 +129,15 @@ class _LivePageState extends State { } Widget contentGrid(ctr, liveList) { - double maxWidth = Get.size.width; - int baseWidth = 500; - int step = 300; - int crossAxisCount = - maxWidth > baseWidth ? 2 + ((maxWidth - baseWidth) / step).ceil() : 2; - if (maxWidth < 300) { - crossAxisCount = 1; - } + // double maxWidth = Get.size.width; + // int baseWidth = 500; + // int step = 300; + // int crossAxisCount = + // maxWidth > baseWidth ? 2 + ((maxWidth - baseWidth) / step).ceil() : 2; + // if (maxWidth < 300) { + // crossAxisCount = 1; + // } + int crossAxisCount = ctr.crossAxisCount.value; return SliverGrid( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( // 行间距 @@ -147,13 +148,15 @@ class _LivePageState extends State { crossAxisCount: crossAxisCount, mainAxisExtent: Get.size.width / crossAxisCount / StyleString.aspectRatio + - 68 * MediaQuery.of(context).textScaleFactor, + (crossAxisCount == 1 ? 48 : 68) * + MediaQuery.of(context).textScaleFactor, ), delegate: SliverChildBuilderDelegate( (BuildContext context, int index) { return liveList!.isNotEmpty ? LiveCardV( liveItem: liveList[index], + crossAxisCount: crossAxisCount, longPress: () { _liveController.popupDialog = _createPopupDialog(liveList[index]); diff --git a/lib/pages/live/widgets/live_item.dart b/lib/pages/live/widgets/live_item.dart index a8185be7..48a4356e 100644 --- a/lib/pages/live/widgets/live_item.dart +++ b/lib/pages/live/widgets/live_item.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/constants.dart'; -import 'package:pilipala/common/widgets/badge.dart'; import 'package:pilipala/models/live/item.dart'; import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; @@ -9,12 +8,14 @@ import 'package:pilipala/common/widgets/network_img_layer.dart'; // 视频卡片 - 垂直布局 class LiveCardV extends StatelessWidget { final LiveItemModel liveItem; + final int crossAxisCount; final Function()? longPress; final Function()? longPressEnd; const LiveCardV({ Key? key, required this.liveItem, + required this.crossAxisCount, this.longPress, this.longPressEnd, }) : super(key: key); @@ -23,7 +24,7 @@ class LiveCardV extends StatelessWidget { Widget build(BuildContext context) { String heroTag = Utils.makeHeroTag(liveItem.roomId); return Card( - elevation: 1, + elevation: crossAxisCount == 1 ? 0 : 1, clipBehavior: Clip.hardEdge, margin: EdgeInsets.zero, child: GestureDetector( @@ -45,12 +46,7 @@ class LiveCardV extends StatelessWidget { child: Column( children: [ ClipRRect( - borderRadius: const BorderRadius.only( - topLeft: StyleString.imgRadius, - topRight: StyleString.imgRadius, - bottomLeft: StyleString.imgRadius, - bottomRight: StyleString.imgRadius, - ), + borderRadius: const BorderRadius.all(StyleString.imgRadius), child: AspectRatio( aspectRatio: StyleString.aspectRatio, child: LayoutBuilder(builder: (context, boxConstraints) { @@ -66,24 +62,25 @@ class LiveCardV extends StatelessWidget { height: maxHeight, ), ), - Positioned( - left: 0, - right: 0, - bottom: 0, - child: AnimatedOpacity( - opacity: 1, - duration: const Duration(milliseconds: 200), - child: VideoStat( - liveItem: liveItem, + if (crossAxisCount != 1) + Positioned( + left: 0, + right: 0, + bottom: 0, + child: AnimatedOpacity( + opacity: 1, + duration: const Duration(milliseconds: 200), + child: VideoStat( + liveItem: liveItem, + ), ), ), - ), ], ); }), ), ), - LiveContent(liveItem: liveItem) + LiveContent(liveItem: liveItem, crossAxisCount: crossAxisCount) ], ), ), @@ -94,13 +91,18 @@ class LiveCardV extends StatelessWidget { class LiveContent extends StatelessWidget { final dynamic liveItem; - const LiveContent({Key? key, required this.liveItem}) : super(key: key); + final int crossAxisCount; + const LiveContent( + {Key? key, required this.liveItem, required this.crossAxisCount}) + : super(key: key); @override Widget build(BuildContext context) { return Expanded( + flex: crossAxisCount == 1 ? 0 : 1, child: Padding( - // 多列 - padding: const EdgeInsets.fromLTRB(9, 9, 9, 8), + padding: crossAxisCount == 1 + ? const EdgeInsets.fromLTRB(9, 9, 9, 4) + : const EdgeInsets.fromLTRB(9, 8, 9, 8), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -112,29 +114,40 @@ class LiveContent extends StatelessWidget { fontWeight: FontWeight.w500, letterSpacing: 0.3, ), - maxLines: 2, + maxLines: crossAxisCount == 1 ? 1 : 2, overflow: TextOverflow.ellipsis, ), + if (crossAxisCount == 1) const SizedBox(height: 4), Row( children: [ - const PBadge( - text: 'UP', - size: 'small', - stack: 'normal', - fs: 9, + Text( + liveItem.uname, + textAlign: TextAlign.start, + style: TextStyle( + fontSize: Theme.of(context).textTheme.labelMedium!.fontSize, + color: Theme.of(context).colorScheme.outline, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, ), - Expanded( - child: Text( - liveItem.uname, - textAlign: TextAlign.start, + if (crossAxisCount == 1) ...[ + Text( + ' • ${liveItem!.areaName!}', style: TextStyle( fontSize: Theme.of(context).textTheme.labelMedium!.fontSize, + color: Theme.of(context).colorScheme.outline, ), - maxLines: 1, - overflow: TextOverflow.ellipsis, ), - ) + Text( + ' • ${liveItem!.watchedShow!['text_small']}人观看', + style: TextStyle( + fontSize: + Theme.of(context).textTheme.labelMedium!.fontSize, + color: Theme.of(context).colorScheme.outline, + ), + ), + ] ], ), ], diff --git a/lib/pages/liveRoom/controller.dart b/lib/pages/liveRoom/controller.dart index 18b39756..2f489fec 100644 --- a/lib/pages/liveRoom/controller.dart +++ b/lib/pages/liveRoom/controller.dart @@ -29,10 +29,10 @@ class LiveRoomController extends GetxController { if (Get.arguments != null) { liveItem = Get.arguments['liveItem']; heroTag = Get.arguments['heroTag'] ?? ''; - if (liveItem.pic != null && liveItem.pic != '') { + if (liveItem != null && liveItem.pic != null && liveItem.pic != '') { cover = liveItem.pic; } - if (liveItem.cover != null && liveItem.cover != '') { + if (liveItem != null && liveItem.cover != null && liveItem.cover != '') { cover = liveItem.cover; } } diff --git a/lib/pages/liveRoom/view.dart b/lib/pages/liveRoom/view.dart index 0c650126..fa881cb8 100644 --- a/lib/pages/liveRoom/view.dart +++ b/lib/pages/liveRoom/view.dart @@ -48,32 +48,35 @@ class _LiveRoomPageState extends State { appBar: AppBar( centerTitle: false, titleSpacing: 0, - title: Row( - children: [ - NetworkImgLayer( - width: 34, - height: 34, - type: 'avatar', - src: _liveRoomController.liveItem.face, - ), - const SizedBox(width: 10), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - _liveRoomController.liveItem.uname, - style: const TextStyle(fontSize: 14), - ), - const SizedBox(height: 1), - if (_liveRoomController.liveItem.watchedShow != null) - Text( - _liveRoomController.liveItem.watchedShow['text_large'] ?? - '', - style: const TextStyle(fontSize: 12)), - ], - ), - ], - ), + title: _liveRoomController.liveItem != null + ? Row( + children: [ + NetworkImgLayer( + width: 34, + height: 34, + type: 'avatar', + src: _liveRoomController.liveItem.face, + ), + const SizedBox(width: 10), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + _liveRoomController.liveItem.uname, + style: const TextStyle(fontSize: 14), + ), + const SizedBox(height: 1), + if (_liveRoomController.liveItem.watchedShow != null) + Text( + _liveRoomController + .liveItem.watchedShow['text_large'] ?? + '', + style: const TextStyle(fontSize: 12)), + ], + ), + ], + ) + : const SizedBox(), // actions: [ // SizedBox( // height: 34, @@ -94,21 +97,22 @@ class _LiveRoomPageState extends State { ? PLVideoPlayer(controller: plPlayerController!) : const SizedBox(), ), - if (_liveRoomController.liveItem.cover != null) - Visibility( - visible: isShowCover, - child: Positioned( - top: 0, - left: 0, - right: 0, - child: NetworkImgLayer( - type: 'emote', - src: _liveRoomController.liveItem.cover, - width: Get.size.width, - height: videoHeight, - ), - ), - ), + // if (_liveRoomController.liveItem != null && + // _liveRoomController.liveItem.cover != null) + // Visibility( + // visible: isShowCover, + // child: Positioned( + // top: 0, + // left: 0, + // right: 0, + // child: NetworkImgLayer( + // type: 'emote', + // src: _liveRoomController.liveItem.cover, + // width: Get.size.width, + // height: videoHeight, + // ), + // ), + // ), ], ), ), diff --git a/lib/pages/rcmd/controller.dart b/lib/pages/rcmd/controller.dart index df7aa2e7..183b79bf 100644 --- a/lib/pages/rcmd/controller.dart +++ b/lib/pages/rcmd/controller.dart @@ -12,10 +12,14 @@ class RcmdController extends GetxController { bool isLoadingMore = true; OverlayEntry? popupDialog; Box recVideo = GStrorage.recVideo; + Box setting = GStrorage.setting; + RxInt crossAxisCount = 2.obs; @override void onInit() { super.onInit(); + crossAxisCount.value = + setting.get(SettingBoxKey.enableSingleRow, defaultValue: false) ? 1 : 2; if (recVideo.get('cacheList') != null && recVideo.get('cacheList').isNotEmpty) { List list = []; diff --git a/lib/pages/rcmd/view.dart b/lib/pages/rcmd/view.dart index 0ed66ac9..ab4d7f55 100644 --- a/lib/pages/rcmd/view.dart +++ b/lib/pages/rcmd/view.dart @@ -142,31 +142,34 @@ class _RcmdPageState extends State } Widget contentGrid(ctr, videoList) { - double maxWidth = Get.size.width; - int baseWidth = 500; - int step = 300; - int crossAxisCount = - maxWidth > baseWidth ? 2 + ((maxWidth - baseWidth) / step).ceil() : 2; - if (maxWidth < 300) { - crossAxisCount = 1; - } + // double maxWidth = Get.size.width; + // int baseWidth = 500; + // int step = 300; + // int crossAxisCount = + // maxWidth > baseWidth ? 2 + ((maxWidth - baseWidth) / step).ceil() : 2; + // if (maxWidth < 300) { + // crossAxisCount = 1; + // } + int crossAxisCount = ctr.crossAxisCount.value; + double mainAxisExtent = + (Get.size.width / crossAxisCount / StyleString.aspectRatio) + + 68 * MediaQuery.of(context).textScaleFactor; return SliverGrid( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( // 行间距 - mainAxisSpacing: StyleString.cardSpace + 4, + mainAxisSpacing: StyleString.safeSpace, // 列间距 - crossAxisSpacing: StyleString.cardSpace + 4, + crossAxisSpacing: StyleString.safeSpace, // 列数 crossAxisCount: crossAxisCount, - mainAxisExtent: - (Get.size.width / crossAxisCount / StyleString.aspectRatio) + - 68 * MediaQuery.of(context).textScaleFactor, + mainAxisExtent: mainAxisExtent, ), delegate: SliverChildBuilderDelegate( (BuildContext context, int index) { return videoList!.isNotEmpty ? VideoCardV( videoItem: videoList[index], + crossAxisCount: crossAxisCount, longPress: () { _rcmdController.popupDialog = _createPopupDialog(videoList[index]); diff --git a/lib/pages/search/controller.dart b/lib/pages/search/controller.dart index 20277b89..64f3cb65 100644 --- a/lib/pages/search/controller.dart +++ b/lib/pages/search/controller.dart @@ -21,6 +21,8 @@ class SSearchController extends GetxController { Debouncer(delay: const Duration(milliseconds: 200)); // 设置延迟时间 String hintText = '搜索'; RxString defaultSearch = '输入关键词搜索'.obs; + Box setting = GStrorage.setting; + bool enableHotKey = true; @override void onInit() { @@ -38,6 +40,7 @@ class SSearchController extends GetxController { } historyCacheList = histiryWord.get('cacheList') ?? []; historyList.value = historyCacheList; + enableHotKey = setting.get(SettingBoxKey.enableHotKey, defaultValue: true); } void onChange(value) { diff --git a/lib/pages/search/view.dart b/lib/pages/search/view.dart index 04af9ed4..ea902365 100644 --- a/lib/pages/search/view.dart +++ b/lib/pages/search/view.dart @@ -146,7 +146,9 @@ class _SearchPageState extends State with RouteAware { // 搜索建议 _searchSuggest(), // 热搜 - hotSearch(_searchController), + Visibility( + visible: _searchController.enableHotKey, + child: hotSearch(_searchController)), // 搜索历史 _history() ], diff --git a/lib/pages/searchPanel/controller.dart b/lib/pages/searchPanel/controller.dart index 826fdacc..399df1e1 100644 --- a/lib/pages/searchPanel/controller.dart +++ b/lib/pages/searchPanel/controller.dart @@ -56,14 +56,20 @@ class SearchPanelController extends GetxController { // 匹配输入内容,如果是AV、BV号且有结果 直接跳转详情页 Map matchRes = IdUtils.matchAvorBv(input: keyword); List matchKeys = matchRes.keys.toList(); - if (matchKeys.isNotEmpty && searchType == SearchType.video) { - String bvid = resultList.first.bvid; - int aid = resultList.first.aid; + String bvid = resultList.first.bvid; + // keyword 可能输入纯数字 + int aid = resultList.first.aid; + if (matchKeys.isNotEmpty && searchType == SearchType.video || + aid.toString() == keyword) { String heroTag = Utils.makeHeroTag(bvid); - int cid = await SearchHttp.ab2c(aid: aid, bvid: bvid); - if (matchKeys.first == 'BV' && matchRes[matchKeys.first] == bvid || - matchKeys.first == 'AV' && matchRes[matchKeys.first] == aid) { + if (matchKeys.isNotEmpty && + matchKeys.first == 'BV' && + matchRes[matchKeys.first] == bvid || + matchKeys.isNotEmpty && + matchKeys.first == 'AV' && + matchRes[matchKeys.first] == aid || + aid.toString() == keyword) { Get.toNamed( '/video?bvid=$bvid&cid=$cid', arguments: {'videoItem': resultList.first, 'heroTag': heroTag}, diff --git a/lib/pages/setting/extra_setting.dart b/lib/pages/setting/extra_setting.dart index 29ad5aac..4ed55f96 100644 --- a/lib/pages/setting/extra_setting.dart +++ b/lib/pages/setting/extra_setting.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:hive/hive.dart'; import 'package:pilipala/models/common/dynamics_type.dart'; import 'package:pilipala/models/common/reply_sort_type.dart'; @@ -47,6 +48,13 @@ class _ExtraSettingState extends State { ), body: ListView( children: [ + SetSwitchItem( + title: '大家都在搜', + subTitle: '是否展示「大家都在搜」', + setKey: SettingBoxKey.enableHotKey, + defaultVal: true, + callFn: (val) => {SmartDialog.showToast('下次启动时生效')}, + ), ListTile( dense: false, title: Text('评论展示', style: titleStyle), diff --git a/lib/pages/setting/style_setting.dart b/lib/pages/setting/style_setting.dart index 73cad841..c502beeb 100644 --- a/lib/pages/setting/style_setting.dart +++ b/lib/pages/setting/style_setting.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:hive/hive.dart'; import 'package:pilipala/models/common/theme_type.dart'; @@ -75,6 +76,13 @@ class _StyleSettingState extends State { setKey: SettingBoxKey.iosTransition, defaultVal: false, ), + SetSwitchItem( + title: '首页单列', + subTitle: '每行展示一个内容卡片', + setKey: SettingBoxKey.enableSingleRow, + defaultVal: false, + callFn: (val) => {SmartDialog.showToast('下次启动时生效')}, + ), ListTile( dense: false, onTap: () { diff --git a/lib/pages/setting/widgets/switch_item.dart b/lib/pages/setting/widgets/switch_item.dart index 1b2cc620..0e091b9a 100644 --- a/lib/pages/setting/widgets/switch_item.dart +++ b/lib/pages/setting/widgets/switch_item.dart @@ -8,12 +8,14 @@ class SetSwitchItem extends StatefulWidget { final String? subTitle; final String? setKey; final bool? defaultVal; + final Function? callFn; const SetSwitchItem({ this.title, this.subTitle, this.setKey, this.defaultVal, + this.callFn, Key? key, }) : super(key: key); @@ -32,12 +34,15 @@ class _SetSwitchItemState extends State { val = Setting.get(widget.setKey, defaultValue: widget.defaultVal ?? false); } - void switchChange(value) { + void switchChange(value) async { val = value ?? !val; - Setting.put(widget.setKey, val); + await Setting.put(widget.setKey, val); if (widget.setKey == SettingBoxKey.autoUpdate && value == true) { Utils.checkUpdata(); } + if (widget.callFn != null) { + widget.callFn!.call(val); + } setState(() {}); } diff --git a/lib/pages/video/detail/controller.dart b/lib/pages/video/detail/controller.dart index e4fd89dc..8fd71b32 100644 --- a/lib/pages/video/detail/controller.dart +++ b/lib/pages/video/detail/controller.dart @@ -209,27 +209,17 @@ class VideoDetailController extends GetxController if (result['status']) { data = result['data']; - /// 优先顺序 省流模式 -> 设置中指定质量 -> 当前可选的最高质量 - // firstVideo = data.dash!.video!.first; - // videoUrl = firstVideo.baseUrl!; - // // - // currentVideoQa = VideoQualityCode.fromCode(firstVideo.id!)!; - - // /// 优先顺序 设置中指定质量 -> 当前可选的最高质量 - // AudioItem firstAudio = - // data.dash!.audio!.isNotEmpty ? data.dash!.audio!.first : AudioItem(); - // audioUrl = firstAudio.baseUrl ?? ''; - List allVideosList = data.dash!.video!; try { // 当前可播放的最高质量视频 int currentHighVideoQa = allVideosList.first.quality!.code; - // + // 使用预设的画质 | 当前可用的最高质量 int cacheVideoQa = setting.get(SettingBoxKey.defaultVideoQa, defaultValue: currentHighVideoQa); int resVideoQa = currentHighVideoQa; if (cacheVideoQa <= currentHighVideoQa) { + // 如果预设的画质低于当前最高 List numbers = data.acceptQuality! .where((e) => e <= currentHighVideoQa) .toList(); @@ -250,10 +240,16 @@ class VideoDetailController extends GetxController currentDecodeFormats = VideoDecodeFormatsCode.fromString(setting.get( SettingBoxKey.defaultDecode, defaultValue: VideoDecodeFormats.values.last.code))!; + print(currentDecodeFormats.description); try { // 当前视频没有对应格式返回第一个 - currentDecodeFormats = supportDecodeFormats - .contains(currentDecodeFormats) + bool flag = false; + for (var i in supportDecodeFormats) { + if (i.startsWith(currentDecodeFormats.code)) { + flag = true; + } + } + currentDecodeFormats = flag ? currentDecodeFormats : VideoDecodeFormatsCode.fromString(supportDecodeFormats.first)!; } catch (e) { @@ -297,13 +293,6 @@ class VideoDetailController extends GetxController } defaultST = Duration(milliseconds: data.lastPlayTime!); await playerInit(); - - // await playerInit( - // firstVideo, - // audioUrl, - // defaultST: Duration(milliseconds: data.lastPlayTime!), - // duration: data.timeLength ?? 0, - // ); } else { if (result['code'] == -404) { isShowCover.value = false; diff --git a/lib/pages/video/detail/reply/widgets/reply_item.dart b/lib/pages/video/detail/reply/widgets/reply_item.dart index c45d56dd..8b47dde6 100644 --- a/lib/pages/video/detail/reply/widgets/reply_item.dart +++ b/lib/pages/video/detail/reply/widgets/reply_item.dart @@ -602,9 +602,26 @@ InlineSpan buildContent( color: Theme.of(context).colorScheme.primary, ), recognizer: TapGestureRecognizer() - ..onTap = () => Get.toNamed('/searchResult', parameters: { - 'keyword': content.jumpUrl[matchStr]['title'] - }), + ..onTap = () { + String appUrlSchema = + content.jumpUrl[matchStr]['app_url_schema']; + if (appUrlSchema == '') { + Get.toNamed( + '/webview', + parameters: { + 'url': matchStr, + 'type': 'url', + 'pageTitle': '' + }, + ); + } else { + if (appUrlSchema.startsWith('bilibili://search')) { + Get.toNamed('/searchResult', parameters: { + 'keyword': content.jumpUrl[matchStr]['title'] + }); + } + } + }, ), ); spanChilds.add( diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index d45bcfb9..2bf53cf0 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -149,7 +149,7 @@ class _VideoDetailPageState extends State key: videoDetailController.scaffoldKey, // fix 1px black line // backgroundColor: Colors.transparent, - backgroundColor: Theme.of(context).colorScheme.background, + backgroundColor: Colors.black, body: ExtendedNestedScrollView( controller: _extendNestCtr, headerSliverBuilder: @@ -162,11 +162,7 @@ class _VideoDetailPageState extends State scrolledUnderElevation: 0, forceElevated: innerBoxIsScrolled, expandedHeight: videoHeight, - backgroundColor: - MediaQuery.of(Get.context!).platformBrightness == - Brightness.dark - ? Colors.black - : Theme.of(context).colorScheme.background, + backgroundColor: Colors.black, flexibleSpace: FlexibleSpaceBar( background: Padding( padding: EdgeInsets.only(top: statusBarHeight), diff --git a/lib/pages/video/detail/widgets/header_control.dart b/lib/pages/video/detail/widgets/header_control.dart index 77619043..0a84a193 100644 --- a/lib/pages/video/detail/widgets/header_control.dart +++ b/lib/pages/video/detail/widgets/header_control.dart @@ -167,50 +167,46 @@ class _HeaderControlState extends State { /// 选择倍速 void showSetSpeedSheet() { - showModalBottomSheet( - context: context, - elevation: 0, - backgroundColor: Colors.transparent, - builder: (BuildContext context) { - return Container( - width: double.infinity, - height: 450, - clipBehavior: Clip.hardEdge, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.background, - borderRadius: const BorderRadius.all(Radius.circular(12)), - ), - margin: const EdgeInsets.all(12), - child: Material( - child: ListView( - physics: const NeverScrollableScrollPhysics(), + double currentSpeed = widget.controller!.playbackSpeed; + SmartDialog.show( + animationType: SmartAnimationType.centerFade_otherSlide, + builder: (context) { + return AlertDialog( + title: const Text('播放速度'), + contentPadding: const EdgeInsets.fromLTRB(0, 20, 0, 20), + content: StatefulBuilder(builder: (context, StateSetter setState) { + return Column( + mainAxisSize: MainAxisSize.min, children: [ - const SizedBox( - height: 45, - child: Center( - child: Text('播放速度'), - ), - ), - for (var i in playSpeed) ...[ - ListTile( - onTap: () { - widget.controller!.setPlaybackSpeed(i.value); - Get.back(result: {'playbackSpeed': i.value}); - }, - dense: true, - contentPadding: const EdgeInsets.only(left: 20, right: 20), - title: Text(i.description), - trailing: i.value == widget.controller!.playbackSpeed - ? Icon( - Icons.done, - color: Theme.of(context).colorScheme.primary, - ) - : null, - ), - ] + Text('$currentSpeed倍'), + Slider( + min: PlaySpeed.values.first.value, + max: PlaySpeed.values.last.value, + value: currentSpeed, + divisions: PlaySpeed.values.length - 1, + label: '${currentSpeed}x', + onChanged: (double val) => + {setState(() => currentSpeed = val)}, + ) ], + ); + }), + actions: [ + TextButton( + onPressed: () => SmartDialog.dismiss(), + child: Text( + '取消', + style: TextStyle(color: Theme.of(context).colorScheme.outline), + ), ), - ), + TextButton( + onPressed: () async { + await SmartDialog.dismiss(); + widget.controller!.setPlaybackSpeed(currentSpeed); + }, + child: const Text('确定'), + ), + ], ); }, ); diff --git a/lib/pages/webview/controller.dart b/lib/pages/webview/controller.dart index 03b99ee8..67d60d6a 100644 --- a/lib/pages/webview/controller.dart +++ b/lib/pages/webview/controller.dart @@ -63,6 +63,13 @@ class WebviewController extends GetxController { onWebResourceError: (WebResourceError error) {}, onNavigationRequest: (NavigationRequest request) { if (request.url.startsWith('bilibili://')) { + if (request.url.startsWith('bilibili://video/')) { + String str = Uri.parse(request.url).pathSegments[0]; + Get.offAndToNamed( + '/searchResult', + parameters: {'keyword': str}, + ); + } return NavigationDecision.prevent; } return NavigationDecision.navigate; diff --git a/lib/plugin/pl_player/models/play_speed.dart b/lib/plugin/pl_player/models/play_speed.dart index 01226ed5..5ccb91a4 100644 --- a/lib/plugin/pl_player/models/play_speed.dart +++ b/lib/plugin/pl_player/models/play_speed.dart @@ -2,11 +2,23 @@ enum PlaySpeed { pointTwoFive, pointFive, pointSevenFive, + one, onePointTwoFive, onePointFive, onePointSevenFive, - two + + two, + twoPointTwoFive, + twoPointFive, + twoPointSevenFive, + + twhree, + threePointTwoFive, + threePointFive, + threePointSevenFive, + + four, } extension PlaySpeedExtension on PlaySpeed { @@ -17,8 +29,15 @@ extension PlaySpeedExtension on PlaySpeed { '正常速度', '1.25倍', '1.5倍', - '1.75倍', '2.0倍', + '2.25倍', + '2.5倍', + '2.75倍', + '3.0倍', + '3.25倍', + '3.5倍', + '3.75倍', + '4.0倍' ]; get description => _descList[index]; @@ -30,7 +49,15 @@ extension PlaySpeedExtension on PlaySpeed { 1.25, 1.5, 1.75, - 2.0 + 2.0, + 2.25, + 2.5, + 2.75, + 3.0, + 3.25, + 3.5, + 3.75, + 4.0, ]; get value => _valueList[index]; get defaultValue => _valueList[3]; diff --git a/lib/plugin/pl_player/utils/fullscreen.dart b/lib/plugin/pl_player/utils/fullscreen.dart index 46540bbd..6d36c409 100644 --- a/lib/plugin/pl_player/utils/fullscreen.dart +++ b/lib/plugin/pl_player/utils/fullscreen.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; @@ -48,12 +49,17 @@ Future enterFullScreen() async { //退出全屏显示 Future exitFullScreen() async { dynamic document; + late SystemUiMode mode = SystemUiMode.edgeToEdge; try { if (kIsWeb) { document.exitFullscreen(); } else if (Platform.isAndroid || Platform.isIOS) { + if (Platform.isAndroid && + (await DeviceInfoPlugin().androidInfo).version.sdkInt < 29) { + mode = SystemUiMode.manual; + } await SystemChrome.setEnabledSystemUIMode( - SystemUiMode.manual, + mode, overlays: SystemUiOverlay.values, ); await SystemChrome.setPreferredOrientations([]); diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index 48c016c9..077b5a1b 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -199,22 +199,11 @@ class _PLVideoPlayerState extends State context: Get.context!, useSafeArea: false, builder: (context) => Dialog.fullscreen( - child: Scaffold( - backgroundColor: Colors.black, - appBar: AppBar( - primary: false, - toolbarHeight: 0, - backgroundColor: Colors.black, - systemOverlayStyle: SystemUiOverlayStyle.light, + backgroundColor: Colors.black, + child: PLVideoPlayer( + controller: _, + headerControl: _.headerControl, ), - body: SafeArea( - bottom: false, - child: PLVideoPlayer( - controller: _, - headerControl: _.headerControl, - ), - ), - ), ), ); if (result == null) { diff --git a/lib/plugin/pl_player/widgets/bottom_control.dart b/lib/plugin/pl_player/widgets/bottom_control.dart index 995aef59..f49e9138 100644 --- a/lib/plugin/pl_player/widgets/bottom_control.dart +++ b/lib/plugin/pl_player/widgets/bottom_control.dart @@ -30,12 +30,12 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget { elevation: 0, scrolledUnderElevation: 0, primary: false, - toolbarHeight: 73, + toolbarHeight: 85, automaticallyImplyLeading: false, titleSpacing: 14, title: Column( children: [ - const SizedBox(height: 23), + const SizedBox(height: 17), Obx( () { final int value = _.sliderPosition.value.inSeconds; @@ -45,7 +45,7 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget { return Container(); } return Padding( - padding: const EdgeInsets.only(left: 5, right: 5, bottom: 5), + padding: const EdgeInsets.only(left: 7, right: 5, bottom: 6), child: ProgressBar( progress: Duration(seconds: value), buffered: Duration(seconds: buffer), diff --git a/lib/utils/app_scheme.dart b/lib/utils/app_scheme.dart new file mode 100644 index 00000000..3736b5b5 --- /dev/null +++ b/lib/utils/app_scheme.dart @@ -0,0 +1,106 @@ +import 'package:appscheme/appscheme.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:get/get.dart'; +import 'package:pilipala/http/search.dart'; +import 'package:pilipala/models/common/search_type.dart'; + +import 'id_utils.dart'; +import 'utils.dart'; + +class PiliSchame { + static AppScheme appScheme = AppSchemeImpl.getInstance() as AppScheme; + static void init() async { + /// + SchemeEntity? value = await appScheme.getInitScheme(); + if (value != null) { + _routePush(value); + } + + /// + appScheme.getLatestScheme().then((value) { + if (value != null) {} + }); + + /// 注册从外部打开的Scheme监听信息 # + appScheme.registerSchemeListener().listen((event) { + if (event != null) { + _routePush(event); + } + }); + } + + /// 路由跳转 + static void _routePush(value) async { + String scheme = value.scheme; + String host = value.host; + String path = value.path; + + if (scheme == 'bilibili') { + // bilibili://root + if (host == 'root') { + Navigator.popUntil(Get.context!, (route) => route.isFirst); + } + + // bilibili://space/{uid} + else if (host == 'space') { + var mid = path.split('/').last; + Get.toNamed( + '/member?mid=$mid', + arguments: {'face': null}, + ); + } + + // bilibili://video/{aid} + else if (host == 'video') { + var pathQuery = path.split('/').last; + int aid = int.parse(pathQuery); + String bvid = IdUtils.av2bv(aid); + int cid = await SearchHttp.ab2c(bvid: bvid); + String heroTag = Utils.makeHeroTag(aid); + Get.toNamed('/video?bvid=$bvid&cid=$cid', arguments: { + 'pic': null, + 'heroTag': heroTag, + }); + } + + // bilibili://live/{roomid} + else if (host == 'live') { + var roomId = path.split('/').last; + Get.toNamed('/liveRoom?roomid=$roomId', + arguments: {'liveItem': null, 'heroTag': roomId.toString()}); + } + + // bilibili://bangumi/season/${ssid} + else if (host == 'bangumi') { + if (path.startsWith('/season')) { + SmartDialog.showLoading(msg: '获取中...'); + try { + var seasonId = path.split('/').last; + var result = await SearchHttp.bangumiInfo( + seasonId: int.parse(seasonId), epId: null); + if (result['status']) { + var bangumiDetail = result['data']; + int cid = bangumiDetail.episodes!.first.cid; + String bvid = IdUtils.av2bv(bangumiDetail.episodes!.first.aid); + String heroTag = Utils.makeHeroTag(cid); + var epId = bangumiDetail.episodes!.first.id; + SmartDialog.dismiss().then( + (e) => Get.toNamed( + '/video?bvid=$bvid&cid=$cid&epId=$epId', + arguments: { + 'pic': bangumiDetail.cover, + 'heroTag': heroTag, + 'videoType': SearchType.media_bangumi, + }, + ), + ); + } + } catch (e) { + SmartDialog.showToast('失败:${e.toString()}'); + } + } + } + } + } +} diff --git a/lib/utils/storage.dart b/lib/utils/storage.dart index a117760e..21a8060b 100644 --- a/lib/utils/storage.dart +++ b/lib/utils/storage.dart @@ -105,6 +105,7 @@ class SettingBoxKey { static const String autoUpdate = 'autoUpdate'; static const String replySortType = 'replySortType'; static const String defaultDynamicType = 'defaultDynamicType'; + static const String enableHotKey = 'enableHotKey'; /// 外观 static const String themeMode = 'themeMode'; @@ -112,6 +113,7 @@ class SettingBoxKey { static const String dynamicColor = 'dynamicColor'; // bool static const String customColor = 'customColor'; // 自定义主题色 static const String iosTransition = 'iosTransition'; // ios路由 + static const String enableSingleRow = 'enableSingleRow'; // 首页单列 } class LocalCacheKey { diff --git a/pubspec.lock b/pubspec.lock index eddd41e7..633e51c6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -25,6 +25,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.7" + appscheme: + dependency: "direct main" + description: + name: appscheme + sha256: b885b65219f3839ebafc937024a1bc5ce5a75b0e458fd249ef15e80e81235b6f + url: "https://pub.dev" + source: hosted + version: "1.0.8" archive: dependency: transitive description: @@ -713,20 +721,20 @@ packages: dependency: "direct main" description: name: media_kit - sha256: "0a89e7037002a62701ec319c375586849f9ef8e681820e1dd4a4ff7b843f7542" + sha256: "66f04934bcadf592f24d829127471e4dc304de8e9bba5795ade2f3e95552ebfc" url: "https://pub.dev" source: hosted - version: "1.1.4+1" + version: "1.1.6" media_kit_libs_android_video: - dependency: "direct main" + dependency: transitive description: name: media_kit_libs_android_video - sha256: "142d389bf3efcf8469594a9c7a06a92fc25843fc6c0c3247f76cdcf70b3b29de" + sha256: "498a5062bc5f000bd23ada3be788ea886ab32c52f7a8252dde1264ca019b819b" url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.3.3" media_kit_libs_ios_video: - dependency: "direct main" + dependency: transitive description: name: media_kit_libs_ios_video sha256: fed403dc9d54462e51ee80e0cb23c12a53fadea9a8fa18aca2de9054176d1159 @@ -734,31 +742,39 @@ packages: source: hosted version: "1.1.3" media_kit_libs_linux: - dependency: "direct main" + dependency: transitive description: name: media_kit_libs_linux - sha256: "570bf18ebbd1221caec082657468be05d180510385d3515ec38e0be44fdcc859" + sha256: "3b7c272179639a914dc8a50bf8a3f2df0e9a503bd727c88fab499dbdf6cb1eb8" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" media_kit_libs_macos_video: - dependency: "direct main" + dependency: transitive description: name: media_kit_libs_macos_video sha256: c06e831f3c22a45296d375788d9bc07871b448f8e9ec98d77b11e5e118a83fb2 url: "https://pub.dev" source: hosted version: "1.1.3" - media_kit_libs_windows_video: + media_kit_libs_video: dependency: "direct main" description: - name: media_kit_libs_windows_video - sha256: f33aabd8414470d99e2c91dd98d605e6a5f1c4b8082dd933c10951bc961b9124 + name: media_kit_libs_video + sha256: "48c8ace458f340e6b930c89c48141ea727b80aa0878f7a01904d7d439865f162" url: "https://pub.dev" source: hosted - version: "1.0.7" + version: "1.0.0" + media_kit_libs_windows_video: + dependency: transitive + description: + name: media_kit_libs_windows_video + sha256: "923f068344d7d200184e0aaa2597f3de6c05982a3b1f18035d842ab53f2a1350" + url: "https://pub.dev" + source: hosted + version: "1.0.8" media_kit_native_event_loop: - dependency: "direct main" + dependency: transitive description: name: media_kit_native_event_loop sha256: e37ce6fb5fa71b8cf513c6a6cd591367743a342a385e7da621a047dd8ef6f4a4 @@ -769,10 +785,10 @@ packages: dependency: "direct main" description: name: media_kit_video - sha256: e7fcbe426d42a78ad6696f8f557adb9cbdc012177829026d04992cc106a1c815 + sha256: "809a3797da7d49fad85f139555b352dd615f9d2da6ae9f1745c6978963491bae" url: "https://pub.dev" source: hosted - version: "1.1.5" + version: "1.1.7" meta: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index a49569a2..9ba87b5a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -79,26 +79,14 @@ dependencies: flutter_smart_dialog: ^4.9.3+2 # 下滑关闭 dismissible_page: ^1.0.2 - # 媒体播放 - # flutter_meedu_media_kit: - # path: /Users/rr/Desktop/code/flutter_meedu_media_kit/package - # git: - # url: https://github.com/guozhigq/flutter_meedu_media_kit.git - # ref: feature-custom - # path: package custom_sliding_segmented_control: ^1.7.5 # 加密 crypto: ^3.0.3 # 视频播放器 - media_kit: ^1.1.4 # Primary package. - media_kit_video: ^1.1.5 # For video rendering. - media_kit_native_event_loop: ^1.0.7 # Support for higher number of concurrent instances & better performance. - media_kit_libs_android_video: ^1.3.2 # Android package for video native libraries. - media_kit_libs_ios_video: ^1.1.3 # iOS package for video native libraries. - media_kit_libs_macos_video: ^1.1.3 # macOS package for video native libraries. - media_kit_libs_windows_video: ^1.0.7 # Windows package for video native libraries. - media_kit_libs_linux: ^1.1.1 + media_kit: ^1.1.6 + media_kit_video: ^1.1.7 + media_kit_libs_video: ^1.0.0 # 音量、亮度、屏幕控制 flutter_volume_controller: ^1.2.7 @@ -119,6 +107,8 @@ dependencies: easy_debounce: ^2.0.3 # 高帧率 flutter_displaymode: ^0.6.0 + # scheme跳转 + appscheme: ^1.0.8 dev_dependencies: flutter_test: