From c961dc6cf596c84a2c02ac4f9b6d4102c2540845 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Fri, 4 Aug 2023 17:04:55 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=95=AA=E5=89=A7=E6=92=AD=E6=94=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ios/Podfile.lock | 6 - lib/models/bangumi/info.dart | 4 +- lib/models/search/result.dart | 10 +- lib/models/video_detail_res.dart | 16 +- .../bangumi/introduction/controller.dart | 401 ++++++++++++++++ lib/pages/bangumi/introduction/index.dart | 4 + lib/pages/bangumi/introduction/view.dart | 446 ++++++++++++++++++ lib/pages/bangumi/widgets/bangumi_panel.dart | 233 +++++++++ lib/pages/searchPanel/view.dart | 4 +- .../widgets/media_bangumi_panel.dart | 13 +- lib/pages/video/detail/controller.dart | 5 + .../video/detail/introduction/controller.dart | 9 + lib/pages/video/detail/view.dart | 29 +- lib/plugin/pl_player/utils/fullscreen.dart | 12 +- lib/plugin/pl_player/view.dart | 2 +- pubspec.lock | 8 - pubspec.yaml | 2 +- 17 files changed, 1155 insertions(+), 49 deletions(-) create mode 100644 lib/pages/bangumi/introduction/controller.dart create mode 100644 lib/pages/bangumi/introduction/index.dart create mode 100644 lib/pages/bangumi/introduction/view.dart create mode 100644 lib/pages/bangumi/widgets/bangumi_panel.dart diff --git a/ios/Podfile.lock b/ios/Podfile.lock index ed152c62..65ff98a1 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,6 +1,4 @@ PODS: - - auto_orientation (0.0.1): - - Flutter - connectivity_plus (0.0.1): - Flutter - ReachabilitySwift @@ -43,7 +41,6 @@ PODS: - Flutter DEPENDENCIES: - - auto_orientation (from `.symlinks/plugins/auto_orientation/ios`) - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - Flutter (from `Flutter`) @@ -68,8 +65,6 @@ SPEC REPOS: - ReachabilitySwift EXTERNAL SOURCES: - auto_orientation: - :path: ".symlinks/plugins/auto_orientation/ios" connectivity_plus: :path: ".symlinks/plugins/connectivity_plus/ios" device_info_plus: @@ -106,7 +101,6 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/webview_flutter_wkwebview/ios" SPEC CHECKSUMS: - auto_orientation: 102ed811a5938d52c86520ddd7ecd3a126b5d39d connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a device_info_plus: 7545d84d8d1b896cb16a4ff98c19f07ec4b298ea Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 diff --git a/lib/models/bangumi/info.dart b/lib/models/bangumi/info.dart index 3bc2af25..c66f66ab 100644 --- a/lib/models/bangumi/info.dart +++ b/lib/models/bangumi/info.dart @@ -95,7 +95,7 @@ class BangumiInfoModel { jpTitle = json['jp_title']; link = json['link']; mediaId = json['media_id']; - newEp = json['newEp']; + newEp = json['new_ep']; playStrategy = json['play_strategy']; positive = json['positive']; publish = json['publish']; @@ -184,7 +184,7 @@ class EpisodeItem { EpisodeItem.fromJson(Map json) { aid = json['aid']; - badge = json['badge']; + badge = json['badge'] != '' ? json['badge'] : null; badgeInfo = json['badge_info']; badgeType = json['badge_type']; bvid = json['bvid']; diff --git a/lib/models/search/result.dart b/lib/models/search/result.dart index 9579f2e4..af5e2cfb 100644 --- a/lib/models/search/result.dart +++ b/lib/models/search/result.dart @@ -277,10 +277,12 @@ class SearchMBangumiModel { SearchMBangumiModel({this.list}); List? list; SearchMBangumiModel.fromJson(Map json) { - list = json['result'] - .map( - (e) => SearchMBangumiItemModel.fromJson(e)) - .toList(); + list = json['result'] != null + ? json['result'] + .map( + (e) => SearchMBangumiItemModel.fromJson(e)) + .toList() + : []; } } diff --git a/lib/models/video_detail_res.dart b/lib/models/video_detail_res.dart index 5b16d70c..5de83ce2 100644 --- a/lib/models/video_detail_res.dart +++ b/lib/models/video_detail_res.dart @@ -57,7 +57,7 @@ class VideoDetailData { bool? isChargeableSeason; bool? isStory; bool? noCache; - List? pages; + List? pages; Subtitle? subtitle; // Label? label; UgcSeason? ugcSeason; @@ -136,7 +136,7 @@ class VideoDetailData { noCache = json["no_cache"]; pages = json["pages"] == null ? [] - : List.from(json["pages"]!.map((e) => Page.fromJson(e))); + : List.from(json["pages"]!.map((e) => Part.fromJson(e))); subtitle = json["subtitle"] == null ? null : Subtitle.fromJson(json["subtitle"]); ugcSeason = json["ugc_season"] != null @@ -352,7 +352,7 @@ class Owner { } } -class Page { +class Part { int? cid; int? page; String? from; @@ -363,7 +363,7 @@ class Page { Dimension? dimension; String? firstFrame; - Page({ + Part({ this.cid, this.page, this.from, @@ -375,11 +375,11 @@ class Page { this.firstFrame, }); - fromRawJson(String str) => Page.fromJson(json.decode(str)); + fromRawJson(String str) => Part.fromJson(json.decode(str)); String toRawJson() => json.encode(toJson()); - Page.fromJson(Map json) { + Part.fromJson(Map json) { cid = json["cid"]; page = json["page"]; from = json["from"]; @@ -620,7 +620,7 @@ class EpisodeItem { int? cid; String? title; int? attribute; - Page? page; + Part? page; String? bvid; EpisodeItem.fromJson(Map json) { @@ -631,7 +631,7 @@ class EpisodeItem { cid = json['cid']; title = json['title']; attribute = json['attribute']; - page = Page.fromJson(json['page']); + page = Part.fromJson(json['page']); bvid = json['bvid']; } } diff --git a/lib/pages/bangumi/introduction/controller.dart b/lib/pages/bangumi/introduction/controller.dart new file mode 100644 index 00000000..d3cbabda --- /dev/null +++ b/lib/pages/bangumi/introduction/controller.dart @@ -0,0 +1,401 @@ +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/http/constants.dart'; +import 'package:pilipala/http/search.dart'; +import 'package:pilipala/http/user.dart'; +import 'package:pilipala/http/video.dart'; +import 'package:pilipala/models/bangumi/info.dart'; +import 'package:pilipala/models/user/fav_folder.dart'; +import 'package:pilipala/models/video_detail_res.dart'; +import 'package:pilipala/pages/video/detail/index.dart'; +import 'package:pilipala/utils/feed_back.dart'; +import 'package:pilipala/utils/id_utils.dart'; +import 'package:pilipala/utils/storage.dart'; +import 'package:share_plus/share_plus.dart'; + +class BangumiIntroController extends GetxController { + // 视频bvid + String bvid = Get.parameters['bvid']!; + int seasonId = int.parse(Get.parameters['seasonId']!); + + // 是否预渲染 骨架屏 + bool preRender = false; + + // 视频详情 上个页面传入 + Map? videoItem = {}; + BangumiInfoModel? bangumiItem; + + // 请求状态 + RxBool isLoading = false.obs; + + // 视频详情 请求返回 + Rx videoDetail = VideoDetailData().obs; + Rx bangumiDetail = BangumiInfoModel().obs; + + // 请求返回的信息 + String responseMsg = '请求异常'; + + // up主粉丝数 + Map userStat = {'follower': '-'}; + + // 是否点赞 + RxBool hasLike = false.obs; + // 是否投币 + RxBool hasCoin = false.obs; + // 是否收藏 + RxBool hasFav = false.obs; + Box user = GStrorage.user; + bool userLogin = false; + Rx favFolderData = FavFolderData().obs; + List addMediaIdsNew = []; + List delMediaIdsNew = []; + // 关注状态 默认未关注 + RxMap followStatus = {}.obs; + int _tempThemeValue = -1; + + @override + void onInit() { + super.onInit(); + if (Get.arguments.isNotEmpty) { + if (Get.arguments.containsKey('bangumiItem')) { + preRender = true; + bangumiItem = Get.arguments['bangumiItem']; + // bangumiItem!['pic'] = args.pic; + // if (args.title is String) { + // videoItem!['title'] = args.title; + // } else { + // String str = ''; + // for (Map map in args.title) { + // str += map['text']; + // } + // videoItem!['title'] = str; + // } + // if (args.stat != null) { + // videoItem!['stat'] = args.stat; + // } + // videoItem!['pubdate'] = args.pubdate; + // videoItem!['owner'] = args.owner; + } + } + userLogin = user.get(UserBoxKey.userLogin) != null; + } + + // 获取番剧简介&选集 + Future queryBangumiIntro() async { + print('🐶🐶: $seasonId'); + var result = await SearchHttp.bangumiInfo(seasonId: seasonId); + print("🐶🐶:${result['data']}"); + if (result['status']) { + bangumiDetail.value = result['data']; + } + if (userLogin) { + // 获取点赞状态 + // queryHasLikeVideo(); + // 获取投币状态 + // queryHasCoinVideo(); + // 获取收藏状态 + // queryHasFavVideo(); + // + // queryFollowStatus(); + } + return result; + } + + // 获取up主粉丝数 + Future queryUserStat() async { + var result = await UserHttp.userStat(mid: videoDetail.value.owner!.mid!); + if (result['status']) { + userStat = result['data']; + } + } + + // 获取点赞状态 + Future queryHasLikeVideo() async { + var result = await VideoHttp.hasLikeVideo(bvid: bvid); + // data num 被点赞标志 0:未点赞 1:已点赞 + hasLike.value = result["data"] == 1 ? true : false; + } + + // 获取投币状态 + Future queryHasCoinVideo() async { + var result = await VideoHttp.hasCoinVideo(bvid: bvid); + hasCoin.value = result["data"]['multiply'] == 0 ? false : true; + } + + // 获取收藏状态 + Future queryHasFavVideo() async { + var result = await VideoHttp.hasFavVideo(aid: IdUtils.bv2av(bvid)); + if (result['status']) { + hasFav.value = result["data"]['favoured']; + } else { + hasFav.value = false; + } + } + + // 一键三连 + Future actionOneThree() async { + if (user.get(UserBoxKey.userMid) == null) { + SmartDialog.showToast('账号未登录'); + return; + } + if (hasLike.value && hasCoin.value && hasFav.value) { + // 已点赞、投币、收藏 + SmartDialog.showToast('🙏 UP已经收到了~'); + return false; + } + SmartDialog.show( + useSystem: true, + animationType: SmartAnimationType.centerFade_otherSlide, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('提示'), + content: const Text('一键三连 给UP送温暖'), + actions: [ + TextButton( + onPressed: () => SmartDialog.dismiss(), + child: const Text('点错了')), + TextButton( + onPressed: () async { + var result = await VideoHttp.oneThree(bvid: bvid); + if (result['status']) { + hasLike.value = result["data"]["like"]; + hasCoin.value = result["data"]["coin"]; + hasFav.value = result["data"]["fav"]; + SmartDialog.showToast('三连成功 🎉'); + } else { + SmartDialog.showToast(result['msg']); + } + SmartDialog.dismiss(); + }, + child: const Text('确认'), + ) + ], + ); + }, + ); + } + + // (取消)点赞 + Future actionLikeVideo() async { + var result = await VideoHttp.likeVideo(bvid: bvid, type: !hasLike.value); + if (result['status']) { + // hasLike.value = result["data"] == 1 ? true : false; + if (!hasLike.value) { + SmartDialog.showToast('点赞成功 👍'); + hasLike.value = true; + videoDetail.value.stat!.like = videoDetail.value.stat!.like! + 1; + } else if (hasLike.value) { + SmartDialog.showToast('取消赞'); + hasLike.value = false; + videoDetail.value.stat!.like = videoDetail.value.stat!.like! - 1; + } + hasLike.refresh(); + } else { + SmartDialog.showToast(result['msg']); + } + } + + // 投币 + Future actionCoinVideo() async { + if (user.get(UserBoxKey.userMid) == null) { + SmartDialog.showToast('账号未登录'); + return; + } + showDialog( + context: Get.context!, + builder: (context) { + return AlertDialog( + title: const Text('选择投币个数'), + contentPadding: const EdgeInsets.fromLTRB(0, 12, 0, 12), + content: StatefulBuilder(builder: (context, StateSetter setState) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + RadioListTile( + value: 1, + title: const Text('1枚'), + groupValue: _tempThemeValue, + onChanged: (value) { + _tempThemeValue = value!; + Get.appUpdate(); + }, + ), + RadioListTile( + value: 2, + title: const Text('2枚'), + groupValue: _tempThemeValue, + onChanged: (value) { + _tempThemeValue = value!; + Get.appUpdate(); + }, + ), + ], + ); + }), + actions: [ + TextButton(onPressed: () => Get.back(), child: const Text('取消')), + TextButton( + onPressed: () async { + var res = await VideoHttp.coinVideo( + bvid: bvid, multiply: _tempThemeValue); + if (res['status']) { + SmartDialog.showToast('投币成功 👏'); + hasCoin.value = true; + videoDetail.value.stat!.coin = + videoDetail.value.stat!.coin! + _tempThemeValue; + } else { + SmartDialog.showToast(res['msg']); + } + Get.back(); + }, + child: const Text('确定')) + ], + ); + }); + } + + // (取消)收藏 + Future actionFavVideo() async { + try { + for (var i in favFolderData.value.list!.toList()) { + if (i.favState == 1) { + addMediaIdsNew.add(i.id); + } else { + delMediaIdsNew.add(i.id); + } + } + } catch (e) { + // ignore: avoid_print + print(e); + } + var result = await VideoHttp.favVideo( + aid: IdUtils.bv2av(bvid), + addIds: addMediaIdsNew.join(','), + delIds: delMediaIdsNew.join(',')); + if (result['status']) { + if (result['data']['prompt']) { + addMediaIdsNew = []; + delMediaIdsNew = []; + Get.back(); + // 重新获取收藏状态 + queryHasFavVideo(); + SmartDialog.showToast('✅ 操作成功'); + } + } + } + + // 分享视频 + Future actionShareVideo() async { + var result = await Share.share('${HttpString.baseUrl}/video/$bvid') + .whenComplete(() {}); + return result; + } + + Future queryVideoInFolder() async { + var result = await VideoHttp.videoInFolder( + mid: user.get(UserBoxKey.userMid), rid: IdUtils.bv2av(bvid)); + if (result['status']) { + favFolderData.value = result['data']; + } + return result; + } + + // 选择文件夹 + onChoose(bool checkValue, int index) { + feedBack(); + List datalist = favFolderData.value.list!; + for (var i = 0; i < datalist.length; i++) { + if (i == index) { + datalist[i].favState = checkValue == true ? 1 : 0; + datalist[i].mediaCount = checkValue == true + ? datalist[i].mediaCount! + 1 + : datalist[i].mediaCount! - 1; + } + } + favFolderData.value.list = datalist; + favFolderData.refresh(); + } + + // 查询关注状态 + Future queryFollowStatus() async { + var result = await VideoHttp.hasFollow(mid: videoDetail.value.owner!.mid!); + if (result['status']) { + followStatus.value = result['data']; + } + return result; + } + + // 关注/取关up + Future actionRelationMod() async { + feedBack(); + if (user.get(UserBoxKey.userMid) == null) { + SmartDialog.showToast('账号未登录'); + return; + } + int currentStatus = followStatus['attribute']; + int actionStatus = 0; + switch (currentStatus) { + case 0: + actionStatus = 1; + break; + case 2: + actionStatus = 2; + break; + default: + actionStatus = 0; + break; + } + SmartDialog.show( + useSystem: true, + animationType: SmartAnimationType.centerFade_otherSlide, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('提示'), + content: Text(currentStatus == 0 ? '关注UP主?' : '取消关注UP主?'), + actions: [ + TextButton( + onPressed: () => SmartDialog.dismiss(), + child: const Text('点错了')), + TextButton( + onPressed: () async { + var result = await VideoHttp.relationMod( + mid: videoDetail.value.owner!.mid!, + act: actionStatus, + reSrc: 14, + ); + if (result['status']) { + switch (currentStatus) { + case 0: + actionStatus = 2; + break; + case 2: + actionStatus = 0; + break; + default: + actionStatus = 0; + break; + } + followStatus['attribute'] = actionStatus; + followStatus.refresh(); + } + SmartDialog.dismiss(); + }, + child: const Text('确认'), + ) + ], + ); + }, + ); + } + + // 修改分P或番剧分集 + Future changeSeasonOrbangu(bvid, cid) async { + VideoDetailController videoDetailCtr = + Get.find(tag: Get.arguments['heroTag']); + videoDetailCtr.bvid = bvid; + videoDetailCtr.cid = cid; + videoDetailCtr.queryVideoUrl(); + } +} diff --git a/lib/pages/bangumi/introduction/index.dart b/lib/pages/bangumi/introduction/index.dart new file mode 100644 index 00000000..40eab5b4 --- /dev/null +++ b/lib/pages/bangumi/introduction/index.dart @@ -0,0 +1,4 @@ +library bangumi_intro; + +export './controller.dart'; +export './view.dart'; diff --git a/lib/pages/bangumi/introduction/view.dart b/lib/pages/bangumi/introduction/view.dart new file mode 100644 index 00000000..dc1d5252 --- /dev/null +++ b/lib/pages/bangumi/introduction/view.dart @@ -0,0 +1,446 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:get/get.dart'; +import 'package:hive/hive.dart'; +import 'package:pilipala/common/constants.dart'; +import 'package:pilipala/common/widgets/http_error.dart'; +import 'package:pilipala/common/widgets/network_img_layer.dart'; +import 'package:pilipala/common/widgets/stat/danmu.dart'; +import 'package:pilipala/common/widgets/stat/view.dart'; +import 'package:pilipala/models/bangumi/info.dart'; +import 'package:pilipala/pages/bangumi/widgets/bangumi_panel.dart'; +import 'package:pilipala/pages/video/detail/index.dart'; +import 'package:pilipala/pages/video/detail/introduction/widgets/action_item.dart'; +import 'package:pilipala/pages/video/detail/introduction/widgets/action_row_item.dart'; +import 'package:pilipala/utils/feed_back.dart'; +import 'package:pilipala/utils/storage.dart'; + +import 'controller.dart'; + +class BangumiIntroPanel extends StatefulWidget { + const BangumiIntroPanel({super.key}); + + @override + State createState() => _BangumiIntroPanelState(); +} + +class _BangumiIntroPanelState extends State + with AutomaticKeepAliveClientMixin { + final BangumiIntroController bangumiIntroController = + Get.put(BangumiIntroController(), tag: Get.arguments['heroTag']); + BangumiInfoModel? bangumiDetail; + +// 添加页面缓存 + @override + bool get wantKeepAlive => true; + + @override + void initState() { + super.initState(); + bangumiIntroController.bangumiDetail.listen((value) { + bangumiDetail = value; + }); + } + + @override + Widget build(BuildContext context) { + super.build(context); + return FutureBuilder( + future: bangumiIntroController.queryBangumiIntro(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.data['status']) { + // 请求成功 + return BangumiInfo( + loadingStatus: false, + bangumiDetail: bangumiDetail, + ); + } else { + // 请求错误 + return HttpError( + errMsg: snapshot.data['msg'], + fn: () => Get.back(), + ); + } + } else { + return BangumiInfo(loadingStatus: true, bangumiDetail: bangumiDetail); + } + }, + ); + } +} + +class BangumiInfo extends StatefulWidget { + final bool loadingStatus; + final BangumiInfoModel? bangumiDetail; + + const BangumiInfo({ + Key? key, + this.loadingStatus = false, + this.bangumiDetail, + }) : super(key: key); + + @override + State createState() => _BangumiInfoState(); +} + +class _BangumiInfoState extends State { + late BangumiInfoModel bangumiItem; + final BangumiIntroController bangumiIntroController = + Get.put(BangumiIntroController(), tag: Get.arguments['heroTag']); + bool isExpand = false; + + late VideoDetailController? videoDetailCtr; + Box localCache = GStrorage.localCache; + late double sheetHeight; + + @override + void initState() { + super.initState(); + bangumiItem = bangumiIntroController.bangumiItem!; + videoDetailCtr = + Get.find(tag: Get.arguments['heroTag']); + sheetHeight = localCache.get('sheetHeight'); + } + + // 收藏 + showFavBottomSheet() { + if (bangumiIntroController.user.get(UserBoxKey.userMid) == null) { + SmartDialog.showToast('账号未登录'); + return; + } + // showModalBottomSheet( + // context: context, + // useRootNavigator: true, + // isScrollControlled: true, + // builder: (context) { + // return FavPanel(ctr: videoIntroController); + // }, + // ); + } + + // 视频介绍 + showIntroDetail() { + feedBack(); + // showBottomSheet( + // context: context, + // enableDrag: true, + // builder: (BuildContext context) { + // return IntroDetail(videoDetail: widget.videoDetail!); + // }, + // ); + } + + @override + Widget build(BuildContext context) { + ThemeData t = Theme.of(context); + return SliverPadding( + padding: const EdgeInsets.only( + left: StyleString.safeSpace, right: StyleString.safeSpace, top: 13), + sliver: SliverToBoxAdapter( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + NetworkImgLayer( + width: 105, + height: 160, + src: !widget.loadingStatus + ? widget.bangumiDetail!.cover! + : bangumiItem.cover!, + ), + const SizedBox(width: 10), + Expanded( + child: InkWell( + onTap: () => showIntroDetail(), + child: SizedBox( + height: 158, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + Expanded( + child: Text( + !widget.loadingStatus + ? widget.bangumiDetail!.title! + : bangumiItem.title!, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + const SizedBox(width: 20), + SizedBox( + width: 34, + height: 34, + child: IconButton( + style: ButtonStyle( + padding: MaterialStateProperty.all( + EdgeInsets.zero), + backgroundColor: + MaterialStateProperty.resolveWith( + (states) { + return t.colorScheme.primaryContainer + .withOpacity(0.7); + }), + ), + onPressed: () {}, + icon: Icon( + Icons.favorite_border_rounded, + color: t.colorScheme.primary, + size: 22, + ), + ), + ), + ], + ), + Row( + children: [ + // const SizedBox(width: 6), + StatView( + theme: 'gray', + view: !widget.loadingStatus + ? widget.bangumiDetail!.stat!['views'] + : bangumiItem.stat!['views'], + size: 'medium', + ), + const SizedBox(width: 6), + StatDanMu( + theme: 'gray', + danmu: !widget.loadingStatus + ? widget.bangumiDetail!.stat!['danmakus'] + : bangumiItem.stat!['danmakus'], + size: 'medium', + ), + ], + ), + const SizedBox(height: 2), + Row( + children: [ + Text( + !widget.loadingStatus + ? widget.bangumiDetail!.areas!.first['name'] + : bangumiItem.areas!.first['name'], + style: TextStyle( + fontSize: 12, + color: t.colorScheme.outline, + ), + ), + const SizedBox(width: 6), + Text( + !widget.loadingStatus + ? widget.bangumiDetail! + .publish!['pub_time_show'] + : bangumiItem.publish!['pub_time_show'], + style: TextStyle( + fontSize: 12, + color: t.colorScheme.outline, + ), + ), + const SizedBox(width: 6), + Text( + !widget.loadingStatus + ? widget.bangumiDetail!.newEp!['desc'] + : bangumiItem.newEp!['desc'], + style: TextStyle( + fontSize: 12, + color: t.colorScheme.outline, + ), + ), + ], + ), + const SizedBox(height: 10), + Text( + '简介:${!widget.loadingStatus ? widget.bangumiDetail!.evaluate! : bangumiItem.evaluate!}', + maxLines: 3, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 12, + color: t.colorScheme.outline, + ), + ), + const Spacer(), + Text( + '评分 ${!widget.loadingStatus ? widget.bangumiDetail!.rating!['score']! : bangumiItem.rating!['score']!}', + style: TextStyle( + fontSize: 13, + color: t.colorScheme.primary, + ), + ), + ], + ), + ), + ), + ), + ], + ), + const SizedBox(height: 6), + // 点赞收藏转发 布局样式1 + // SingleChildScrollView( + // padding: const EdgeInsets.only(top: 7, bottom: 7), + // scrollDirection: Axis.horizontal, + // child: actionRow( + // context, + // bangumiIntroController, + // videoDetailCtr, + // ), + // ), + // 点赞收藏转发 布局样式2 + actionGrid(context, bangumiIntroController), + // 番剧分p + if (!widget.loadingStatus && + widget.bangumiDetail!.episodes!.isNotEmpty) ...[ + BangumiPanel( + pages: widget.bangumiDetail!.episodes!, + cid: widget.bangumiDetail!.episodes!.first.cid, + sheetHeight: sheetHeight, + changeFuc: (bvid, cid) => + bangumiIntroController.changeSeasonOrbangu(bvid, cid), + ) + ], + ], + ), + ), + ); + } + + Widget actionGrid(BuildContext context, bangumiIntroController) { + return LayoutBuilder(builder: (context, constraints) { + return Material( + child: Padding( + padding: const EdgeInsets.only(top: 16, bottom: 8), + child: SizedBox( + height: constraints.maxWidth / 5 * 0.8, + child: GridView.count( + primary: false, + padding: const EdgeInsets.all(0), + crossAxisCount: 5, + childAspectRatio: 1.25, + children: [ + Obx( + () => ActionItem( + icon: const Icon(FontAwesomeIcons.thumbsUp), + selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp), + onTap: () => bangumiIntroController.actionLikeVideo(), + selectStatus: bangumiIntroController.hasLike.value, + loadingStatus: widget.loadingStatus, + text: !widget.loadingStatus + ? widget.bangumiDetail!.stat!['likes']!.toString() + : '-'), + ), + ActionItem( + icon: const Icon(FontAwesomeIcons.clock), + onTap: () => () {}, + selectStatus: false, + loadingStatus: widget.loadingStatus, + text: '稍后再看'), + Obx( + () => ActionItem( + icon: const Icon(FontAwesomeIcons.b), + selectIcon: const Icon(FontAwesomeIcons.b), + onTap: () => bangumiIntroController.actionCoinVideo(), + selectStatus: bangumiIntroController.hasCoin.value, + loadingStatus: widget.loadingStatus, + text: !widget.loadingStatus + ? widget.bangumiDetail!.stat!['coins']!.toString() + : '-'), + ), + Obx( + () => ActionItem( + icon: const Icon(FontAwesomeIcons.star), + selectIcon: const Icon(FontAwesomeIcons.solidStar), + // onTap: () => videoIntroController.actionFavVideo(), + onTap: () => showFavBottomSheet(), + selectStatus: bangumiIntroController.hasFav.value, + loadingStatus: widget.loadingStatus, + text: !widget.loadingStatus + ? widget.bangumiDetail!.stat!['favorite']!.toString() + : '-'), + ), + ActionItem( + icon: const Icon(FontAwesomeIcons.shareFromSquare), + onTap: () => bangumiIntroController.actionShareVideo(), + selectStatus: false, + loadingStatus: widget.loadingStatus, + text: !widget.loadingStatus + ? widget.bangumiDetail!.stat!['share']!.toString() + : '-'), + ], + ), + ), + ), + ); + }); + } + + Widget actionRow(BuildContext context, videoIntroController, videoDetailCtr) { + return Row(children: [ + Obx( + () => ActionRowItem( + icon: const Icon(FontAwesomeIcons.thumbsUp), + onTap: () => videoIntroController.actionLikeVideo(), + selectStatus: videoIntroController.hasLike.value, + loadingStatus: widget.loadingStatus, + text: !widget.loadingStatus + ? widget.bangumiDetail!.stat!['likes']!.toString() + : '-', + ), + ), + const SizedBox(width: 8), + Obx( + () => ActionRowItem( + icon: const Icon(FontAwesomeIcons.b), + onTap: () => videoIntroController.actionCoinVideo(), + selectStatus: videoIntroController.hasCoin.value, + loadingStatus: widget.loadingStatus, + text: !widget.loadingStatus + ? widget.bangumiDetail!.stat!['coins']!.toString() + : '-', + ), + ), + const SizedBox(width: 8), + Obx( + () => ActionRowItem( + icon: const Icon(FontAwesomeIcons.heart), + onTap: () => showFavBottomSheet(), + selectStatus: videoIntroController.hasFav.value, + loadingStatus: widget.loadingStatus, + text: !widget.loadingStatus + ? widget.bangumiDetail!.stat!['favorite']!.toString() + : '-', + ), + ), + const SizedBox(width: 8), + ActionRowItem( + icon: const Icon(FontAwesomeIcons.comment), + onTap: () { + videoDetailCtr.tabCtr.animateTo(1); + }, + selectStatus: false, + loadingStatus: widget.loadingStatus, + text: !widget.loadingStatus + ? widget.bangumiDetail!.stat!['reply']!.toString() + : '-', + ), + const SizedBox(width: 8), + ActionRowItem( + icon: const Icon(FontAwesomeIcons.share), + onTap: () => videoIntroController.actionShareVideo(), + selectStatus: false, + loadingStatus: widget.loadingStatus, + // text: !widget.loadingStatus + // ? widget.videoDetail!.stat!.share!.toString() + // : '-', + text: '转发'), + ]); + } +} diff --git a/lib/pages/bangumi/widgets/bangumi_panel.dart b/lib/pages/bangumi/widgets/bangumi_panel.dart new file mode 100644 index 00000000..6ee22024 --- /dev/null +++ b/lib/pages/bangumi/widgets/bangumi_panel.dart @@ -0,0 +1,233 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:pilipala/models/bangumi/info.dart'; + +class BangumiPanel extends StatefulWidget { + final List pages; + final int? cid; + final double? sheetHeight; + final Function? changeFuc; + + const BangumiPanel({ + super.key, + required this.pages, + this.cid, + this.sheetHeight, + this.changeFuc, + }); + + @override + State createState() => _BangumiPanelState(); +} + +class _BangumiPanelState extends State { + late int currentIndex; + + @override + void initState() { + super.initState(); + currentIndex = widget.pages.indexWhere((e) => e.cid == widget.cid!); + } + + void showBangumiPanel() { + showBottomSheet( + context: context, + builder: (_) => Container( + height: widget.sheetHeight, + color: Theme.of(context).colorScheme.background, + child: Column( + children: [ + Container( + height: 45, + padding: const EdgeInsets.only(left: 14, right: 14), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '合集(${widget.pages.length})', + style: Theme.of(context).textTheme.titleMedium, + ), + IconButton( + icon: const Icon(Icons.close), + onPressed: () => Navigator.pop(context), + ), + ], + ), + ), + Divider( + height: 1, + color: Theme.of(context).dividerColor.withOpacity(0.1), + ), + Expanded( + child: Material( + child: ListView.builder( + itemCount: widget.pages.length, + itemBuilder: (context, index) { + return ListTile( + onTap: () async { + if (widget.pages[index].badge != null) { + SmartDialog.showToast('需要大会员'); + return; + } + await widget.changeFuc!( + widget.pages[index].bvid, + widget.pages[index].cid, + ); + currentIndex = index; + setState(() {}); + }, + dense: false, + title: Text( + widget.pages[index].longTitle!, + style: TextStyle( + fontSize: 14, + color: index == currentIndex + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.onSurface, + ), + ), + trailing: widget.pages[index].badge != null + ? Image.asset( + 'assets/images/big-vip.png', + height: 20, + ) + : const SizedBox(), + ); + }, + ), + ), + ), + ], + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Padding( + padding: const EdgeInsets.only(top: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text('合集 '), + Expanded( + child: Text( + ' 正在播放:${widget.pages[currentIndex].longTitle}', + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 12, + color: Theme.of(context).colorScheme.outline, + ), + ), + ), + const SizedBox(width: 10), + SizedBox( + height: 34, + child: TextButton( + style: ButtonStyle( + padding: MaterialStateProperty.all(EdgeInsets.zero), + ), + onPressed: () => showBangumiPanel(), + child: Text( + '全${widget.pages.length}话', + style: const TextStyle(fontSize: 13), + ), + ), + ), + ], + ), + ), + SingleChildScrollView( + padding: const EdgeInsets.only(top: 7, bottom: 7), + scrollDirection: Axis.horizontal, + child: Row( + children: [ + for (int i = 0; i < widget.pages.length; i++) ...[ + Container( + width: 150, + margin: const EdgeInsets.only(right: 10), + child: Material( + color: Theme.of(context).colorScheme.onInverseSurface, + borderRadius: BorderRadius.circular(6), + clipBehavior: Clip.hardEdge, + child: InkWell( + onTap: () async { + if (widget.pages[i].badge != null) { + SmartDialog.showToast('需要大会员'); + return; + } + await widget.changeFuc!( + widget.pages[i].bvid, + widget.pages[i].cid, + ); + currentIndex = i; + setState(() {}); + }, + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 8, horizontal: 10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + if (i == currentIndex) ...[ + Image.asset( + 'assets/images/live.gif', + color: + Theme.of(context).colorScheme.primary, + height: 12, + ), + const SizedBox(width: 6) + ], + Text( + '第${i + 1}话', + style: TextStyle( + fontSize: 13, + color: i == currentIndex + ? Theme.of(context) + .colorScheme + .primary + : Theme.of(context) + .colorScheme + .onSurface), + ), + const SizedBox(width: 2), + if (widget.pages[i].badge != null) ...[ + Image.asset( + 'assets/images/big-vip.png', + height: 16, + ), + ], + ], + ), + const SizedBox(height: 3), + Text( + widget.pages[i].longTitle!, + maxLines: 1, + style: TextStyle( + fontSize: 13, + color: i == currentIndex + ? Theme.of(context).colorScheme.primary + : Theme.of(context) + .colorScheme + .onSurface), + overflow: TextOverflow.ellipsis, + ) + ], + ), + ), + ), + ), + ), + ] + ], + ), + ) + ], + ); + } +} diff --git a/lib/pages/searchPanel/view.dart b/lib/pages/searchPanel/view.dart index 4d49f11c..2683e7e9 100644 --- a/lib/pages/searchPanel/view.dart +++ b/lib/pages/searchPanel/view.dart @@ -27,6 +27,7 @@ class _SearchPanelState extends State late SearchPanelController? _searchPanelController; bool _isLoadingMore = false; + late Future _futureBuilderFuture; @override bool get wantKeepAlive => true; @@ -53,6 +54,7 @@ class _SearchPanelState extends State } } }); + _futureBuilderFuture = _searchPanelController!.onSearch(); } @override @@ -63,7 +65,7 @@ class _SearchPanelState extends State await _searchPanelController!.onRefresh(); }, child: FutureBuilder( - future: _searchPanelController!.onSearch(), + future: _futureBuilderFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { Map data = snapshot.data; diff --git a/lib/pages/searchPanel/widgets/media_bangumi_panel.dart b/lib/pages/searchPanel/widgets/media_bangumi_panel.dart index 4a0cb8c4..82558105 100644 --- a/lib/pages/searchPanel/widgets/media_bangumi_panel.dart +++ b/lib/pages/searchPanel/widgets/media_bangumi_panel.dart @@ -5,6 +5,7 @@ import 'package:pilipala/common/widgets/badge.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/http/search.dart'; import 'package:pilipala/models/bangumi/info.dart'; +import 'package:pilipala/models/common/search_type.dart'; import 'package:pilipala/utils/utils.dart'; Widget searchMbangumiPanel(BuildContext context, ctr, list) { @@ -19,8 +20,12 @@ Widget searchMbangumiPanel(BuildContext context, ctr, list) { var i = list![index]; return InkWell( onTap: () { - Get.toNamed('/video?bvid=${i.bvid}&cid=${i.cid}', - arguments: {'videoItem': i, 'heroTag': Utils.makeHeroTag(i.id)}); + /// TODO 番剧详情页面 + // Get.toNamed('/video?bvid=${i.bvid}&cid=${i.cid}', arguments: { + // 'videoItem': i, + // 'heroTag': Utils.makeHeroTag(i.id), + // 'videoType': SearchType.media_bangumi + // }); }, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), @@ -107,10 +112,12 @@ Widget searchMbangumiPanel(BuildContext context, ctr, list) { String pic = episode.cover!; String heroTag = Utils.makeHeroTag(cid); Get.toNamed( - '/video?bvid=$bvid&cid=$cid', + '/video?bvid=$bvid&cid=$cid&seasonId=${i.seasonId}', arguments: { 'pic': pic, 'heroTag': heroTag, + 'videoType': SearchType.media_bangumi, + 'bangumiItem': res['data'], }, ); } diff --git a/lib/pages/video/detail/controller.dart b/lib/pages/video/detail/controller.dart index 597030ed..0f07101c 100644 --- a/lib/pages/video/detail/controller.dart +++ b/lib/pages/video/detail/controller.dart @@ -5,6 +5,7 @@ import 'package:hive/hive.dart'; import 'package:pilipala/http/constants.dart'; import 'package:pilipala/http/video.dart'; import 'package:pilipala/models/common/reply_type.dart'; +import 'package:pilipala/models/common/search_type.dart'; import 'package:pilipala/models/video/play/quality.dart'; import 'package:pilipala/models/video/play/url.dart'; import 'package:pilipala/models/video/reply/item.dart'; @@ -22,6 +23,9 @@ class VideoDetailController extends GetxController // 视频aid String bvid = Get.parameters['bvid']!; int cid = int.parse(Get.parameters['cid']!); + // 视频类型 默认投稿视频 + SearchType videoType = SearchType.video; + late PlayUrlModel data; // 当前画质 late VideoQuality currentVideoQa; @@ -74,6 +78,7 @@ class VideoDetailController extends GetxController bgCover.value = Get.arguments['pic']; } heroTag = Get.arguments['heroTag']; + videoType = Get.arguments['videoType'] ?? SearchType.video; } tabCtr = TabController(length: 2, vsync: this); // queryVideoUrl(); diff --git a/lib/pages/video/detail/introduction/controller.dart b/lib/pages/video/detail/introduction/controller.dart index c6cb792b..494f163f 100644 --- a/lib/pages/video/detail/introduction/controller.dart +++ b/lib/pages/video/detail/introduction/controller.dart @@ -389,4 +389,13 @@ class VideoIntroController extends GetxController { }, ); } + + // 修改分P或番剧分集 + Future changeSeasonOrbangu(bvid, cid) async { + var _videoDetailCtr = + Get.find(tag: Get.arguments['heroTag']); + _videoDetailCtr.bvid = bvid; + _videoDetailCtr.cid = cid; + _videoDetailCtr.queryVideoUrl(); + } } diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index 7325cfd0..cf74d5aa 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -6,6 +6,8 @@ import 'package:flutter/material.dart'; import 'package:hive/hive.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/sliver_header.dart'; +import 'package:pilipala/models/common/search_type.dart'; +import 'package:pilipala/pages/bangumi/introduction/index.dart'; import 'package:pilipala/pages/video/detail/introduction/widgets/menu_row.dart'; import 'package:pilipala/pages/video/detail/reply/index.dart'; import 'package:pilipala/pages/video/detail/controller.dart'; @@ -324,16 +326,25 @@ class _VideoDetailPageState extends State return CustomScrollView( key: const PageStorageKey('简介'), slivers: [ - const VideoIntroPanel(), - SliverPersistentHeader( - floating: true, - pinned: true, - delegate: SliverHeaderDelegate( - height: 50, - child: - const MenuRow(loadingStatus: false), + if (videoDetailController.videoType == + SearchType.video) ...[ + const VideoIntroPanel(), + ] else if (videoDetailController.videoType == + SearchType.media_bangumi) ...[ + const BangumiIntroPanel() + ], + if (videoDetailController.videoType == + SearchType.video) ...[ + SliverPersistentHeader( + floating: true, + pinned: true, + delegate: SliverHeaderDelegate( + height: 50, + child: + const MenuRow(loadingStatus: false), + ), ), - ), + ], const RelatedVideoPanel(), ], ); diff --git a/lib/plugin/pl_player/utils/fullscreen.dart b/lib/plugin/pl_player/utils/fullscreen.dart index 4f5ca948..6139416d 100644 --- a/lib/plugin/pl_player/utils/fullscreen.dart +++ b/lib/plugin/pl_player/utils/fullscreen.dart @@ -1,16 +1,16 @@ import 'dart:io'; import 'package:device_info_plus/device_info_plus.dart'; -import 'package:auto_orientation/auto_orientation.dart'; +// import 'package:auto_orientation/auto_orientation.dart'; import 'package:flutter/services.dart'; //横屏 /// 低版本xcode不支持auto_orientation -Future landScape() async { - if (Platform.isAndroid || Platform.isIOS) { - await AutoOrientation.landscapeAutoMode(forceSensor: true); - } -} +// Future landScape() async { +// if (Platform.isAndroid || Platform.isIOS) { +// await AutoOrientation.landscapeAutoMode(forceSensor: true); +// } +// } //竖屏 Future verticalScreen() async { diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index 07745901..4b69a5b9 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -153,7 +153,7 @@ class _PLVideoPlayerState extends State /// 进入全屏 await enterFullScreen(); // 横屏 - await landScape(); + // await landScape(); } else { // 竖屏 await verticalScreen(); diff --git a/pubspec.lock b/pubspec.lock index db99cf2a..494f2fe1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -49,14 +49,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" - auto_orientation: - dependency: "direct main" - description: - name: auto_orientation - sha256: cd56bb59b36fa54cc28ee254bc600524f022a4862f31d5ab20abd7bb1c54e678 - url: "https://pub.dev" - source: hosted - version: "2.3.1" boolean_selector: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index acc018c7..5d97d0e4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -107,7 +107,7 @@ dependencies: universal_platform: ^1.0.0+1 # 进度条 audio_video_progress_bar: ^1.0.1 - auto_orientation: ^2.3.1 + # auto_orientation: ^2.3.1 dev_dependencies: flutter_test: