diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index c52d8447..f119eb1e 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -45,7 +45,7 @@ android:fullBackupContent="false" tools:replace="android:allowBackup"> NSPhotoLibraryAddUsageDescription 请允许APP保存图片到相册 + NSPhotoLibraryUsageDescription + 请允许APP保存图片到相册 NSCameraUsageDescription App需要您的同意,才能访问相册 NSAppleMusicUsageDescription diff --git a/lib/common/widgets/network_img_layer.dart b/lib/common/widgets/network_img_layer.dart index bda8ee5c..d2772478 100644 --- a/lib/common/widgets/network_img_layer.dart +++ b/lib/common/widgets/network_img_layer.dart @@ -34,6 +34,9 @@ class NetworkImgLayer extends StatelessWidget { @override Widget build(BuildContext context) { final int defaultImgQuality = GlobalData().imgQuality; + if (src == '' || src == null) { + return placeholder(context); + } final String imageUrl = '${src!.startsWith('//') ? 'https:${src!}' : src!}@${quality ?? defaultImgQuality}q.webp'; int? memCacheWidth, memCacheHeight; diff --git a/lib/common/widgets/overlay_pop.dart b/lib/common/widgets/overlay_pop.dart deleted file mode 100644 index 4f0a3899..00000000 --- a/lib/common/widgets/overlay_pop.dart +++ /dev/null @@ -1,87 +0,0 @@ -import 'package:flutter/material.dart'; -import '../../utils/download.dart'; -import '../constants.dart'; -import 'network_img_layer.dart'; - -class OverlayPop extends StatelessWidget { - const OverlayPop({super.key, this.videoItem, this.closeFn}); - - final dynamic videoItem; - final Function? closeFn; - - @override - Widget build(BuildContext context) { - final double imgWidth = MediaQuery.sizeOf(context).width - 8 * 2; - return Container( - margin: const EdgeInsets.symmetric(horizontal: 8), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.background, - borderRadius: BorderRadius.circular(10.0), - ), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Stack( - children: [ - NetworkImgLayer( - width: imgWidth, - height: imgWidth / StyleString.aspectRatio, - src: videoItem.pic! as String, - quality: 100, - ), - Positioned( - right: 8, - top: 8, - child: Container( - width: 30, - height: 30, - decoration: BoxDecoration( - color: Colors.black.withOpacity(0.3), - borderRadius: - const BorderRadius.all(Radius.circular(20))), - child: IconButton( - style: ButtonStyle( - padding: MaterialStateProperty.all(EdgeInsets.zero), - ), - onPressed: () => closeFn!(), - icon: const Icon( - Icons.close, - size: 18, - color: Colors.white, - ), - ), - ), - ), - ], - ), - Padding( - padding: const EdgeInsets.fromLTRB(12, 10, 8, 10), - child: Row( - children: [ - Expanded( - child: Text( - videoItem.title! as String, - style: Theme.of(context).textTheme.titleSmall, - ), - ), - const SizedBox(width: 4), - IconButton( - tooltip: '保存封面图', - onPressed: () async { - await DownloadUtils.downloadImg( - videoItem.pic != null - ? videoItem.pic as String - : videoItem.cover as String, - ); - // closeFn!(); - }, - icon: const Icon(Icons.download, size: 20), - ) - ], - )), - ], - ), - ); - } -} diff --git a/lib/common/widgets/video_card_h.dart b/lib/common/widgets/video_card_h.dart index 25e701ac..1265477f 100644 --- a/lib/common/widgets/video_card_h.dart +++ b/lib/common/widgets/video_card_h.dart @@ -1,6 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; +import 'package:pilipala/http/constants.dart'; +import 'package:pilipala/utils/feed_back.dart'; +import 'package:pilipala/utils/image_save.dart'; +import 'package:pilipala/utils/route_push.dart'; +import 'package:pilipala/utils/url_utils.dart'; import '../../http/search.dart'; import '../../http/user.dart'; import '../../http/video.dart'; @@ -16,8 +21,7 @@ class VideoCardH extends StatelessWidget { const VideoCardH({ super.key, required this.videoItem, - this.longPress, - this.longPressEnd, + this.onPressedFn, this.source = 'normal', this.showOwner = true, this.showView = true, @@ -27,8 +31,8 @@ class VideoCardH extends StatelessWidget { }); // ignore: prefer_typing_uninitialized_variables final videoItem; - final Function()? longPress; - final Function()? longPressEnd; + final Function()? onPressedFn; + // normal 推荐, later 稍后再看, search 搜索 final String source; final bool showOwner; final bool showView; @@ -45,109 +49,117 @@ class VideoCardH extends StatelessWidget { type = videoItem.type; } catch (_) {} final String heroTag = Utils.makeHeroTag(aid); - return GestureDetector( - onLongPress: () { - if (longPress != null) { - longPress!(); + return InkWell( + onTap: () async { + try { + if (type == 'ketang') { + SmartDialog.showToast('课堂视频暂不支持播放'); + return; + } + if (showCharge && videoItem?.typeid == 33) { + final String redirectUrl = await UrlUtils.parseRedirectUrl( + '${HttpString.baseUrl}/video/$bvid/'); + final String lastPathSegment = redirectUrl.split('/').last; + if (lastPathSegment.contains('ss')) { + RoutePush.bangumiPush( + Utils.matchNum(lastPathSegment).first, null); + } + if (lastPathSegment.contains('ep')) { + RoutePush.bangumiPush( + null, Utils.matchNum(lastPathSegment).first); + } + return; + } + final int cid = + videoItem.cid ?? await SearchHttp.ab2c(aid: aid, bvid: bvid); + Get.toNamed('/video?bvid=$bvid&cid=$cid', + arguments: {'videoItem': videoItem, 'heroTag': heroTag}); + } catch (err) { + SmartDialog.showToast(err.toString()); } }, - // onLongPressEnd: (details) { - // if (longPressEnd != null) { - // longPressEnd!(); - // } - // }, - child: InkWell( - onTap: () async { - try { - if (type == 'ketang') { - SmartDialog.showToast('课堂视频暂不支持播放'); - return; - } - final int cid = - videoItem.cid ?? await SearchHttp.ab2c(aid: aid, bvid: bvid); - Get.toNamed('/video?bvid=$bvid&cid=$cid', - arguments: {'videoItem': videoItem, 'heroTag': heroTag}); - } catch (err) { - SmartDialog.showToast(err.toString()); - } - }, - child: Padding( - padding: const EdgeInsets.fromLTRB( - StyleString.safeSpace, 5, StyleString.safeSpace, 5), - child: LayoutBuilder( - builder: (BuildContext context, BoxConstraints boxConstraints) { - final double width = (boxConstraints.maxWidth - - StyleString.cardSpace * - 6 / - MediaQuery.textScalerOf(context).scale(1.0)) / - 2; - return Container( - constraints: const BoxConstraints(minHeight: 88), - height: width / StyleString.aspectRatio, - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - AspectRatio( - aspectRatio: StyleString.aspectRatio, - child: LayoutBuilder( - builder: (BuildContext context, - BoxConstraints boxConstraints) { - final double maxWidth = boxConstraints.maxWidth; - final double maxHeight = boxConstraints.maxHeight; - return Stack( - children: [ - Hero( - tag: heroTag, - child: NetworkImgLayer( - src: videoItem.pic as String, - width: maxWidth, - height: maxHeight, - ), + onLongPress: () => imageSaveDialog( + context, + videoItem, + SmartDialog.dismiss, + ), + child: Padding( + padding: const EdgeInsets.fromLTRB( + StyleString.safeSpace, 5, StyleString.safeSpace, 5), + child: LayoutBuilder( + builder: (BuildContext context, BoxConstraints boxConstraints) { + final double width = (boxConstraints.maxWidth - + StyleString.cardSpace * + 6 / + MediaQuery.textScalerOf(context).scale(1.0)) / + 2; + return Container( + constraints: const BoxConstraints(minHeight: 88), + height: width / StyleString.aspectRatio, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AspectRatio( + aspectRatio: StyleString.aspectRatio, + child: LayoutBuilder( + builder: (BuildContext context, + BoxConstraints boxConstraints) { + final double maxWidth = boxConstraints.maxWidth; + final double maxHeight = boxConstraints.maxHeight; + return Stack( + children: [ + Hero( + tag: heroTag, + child: NetworkImgLayer( + src: videoItem.pic as String, + width: maxWidth, + height: maxHeight, ), - if (videoItem.duration != 0) - PBadge( - text: Utils.timeFormat(videoItem.duration!), - right: 6.0, - bottom: 6.0, - type: 'gray', - ), - if (type != 'video') - PBadge( - text: type, - left: 6.0, - bottom: 6.0, - type: 'primary', - ), - // if (videoItem.rcmdReason != null && - // videoItem.rcmdReason.content != '') - // pBadge(videoItem.rcmdReason.content, context, - // 6.0, 6.0, null, null), - if (showCharge && videoItem?.isChargingSrc) - const PBadge( - text: '充电专属', - right: 6.0, - top: 6.0, - type: 'primary', - ), - ], - ); - }, - ), + ), + if (videoItem.duration != 0) + PBadge( + text: Utils.timeFormat(videoItem.duration!), + right: 6.0, + bottom: 6.0, + type: 'gray', + ), + if (type != 'video') + PBadge( + text: type, + left: 6.0, + bottom: 6.0, + type: 'primary', + ), + // if (videoItem.rcmdReason != null && + // videoItem.rcmdReason.content != '') + // pBadge(videoItem.rcmdReason.content, context, + // 6.0, 6.0, null, null), + if (showCharge && videoItem?.isChargingSrc) + const PBadge( + text: '充电专属', + right: 6.0, + top: 6.0, + type: 'primary', + ), + ], + ); + }, ), - VideoContent( - videoItem: videoItem, - source: source, - showOwner: showOwner, - showView: showView, - showDanmaku: showDanmaku, - showPubdate: showPubdate, - ) - ], - ), - ); - }, - ), + ), + VideoContent( + videoItem: videoItem, + source: source, + showOwner: showOwner, + showView: showView, + showDanmaku: showDanmaku, + showPubdate: showPubdate, + onPressedFn: onPressedFn, + ) + ], + ), + ); + }, ), ), ); @@ -162,6 +174,7 @@ class VideoContent extends StatelessWidget { final bool showView; final bool showDanmaku; final bool showPubdate; + final Function()? onPressedFn; const VideoContent({ super.key, @@ -171,6 +184,7 @@ class VideoContent extends StatelessWidget { this.showView = true, this.showDanmaku = true, this.showPubdate = false, + this.onPressedFn, }); @override @@ -181,7 +195,7 @@ class VideoContent extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (videoItem.title is String) ...[ + if (source == 'normal' || source == 'later') ...[ Text( videoItem.title as String, textAlign: TextAlign.start, @@ -196,7 +210,7 @@ class VideoContent extends StatelessWidget { maxLines: 2, text: TextSpan( children: [ - for (final i in videoItem.title) ...[ + for (final i in videoItem.titleList) ...[ TextSpan( text: i['text'] as String, style: TextStyle( @@ -263,117 +277,44 @@ class VideoContent extends StatelessWidget { theme: 'gray', danmu: videoItem.stat.danmaku as int, ), - 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, - // ), - // ), - // ), if (source == 'normal') SizedBox( width: 24, height: 24, - child: PopupMenuButton( + child: IconButton( padding: EdgeInsets.zero, + onPressed: () { + feedBack(); + showModalBottomSheet( + context: context, + useRootNavigator: true, + isScrollControlled: true, + builder: (context) { + return MorePanel(videoItem: videoItem); + }, + ); + }, 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 as String); - SmartDialog.showToast(res['msg']); - }, - value: 'pause', - height: 40, - child: const Row( - children: [ - Icon(Icons.watch_later_outlined, size: 16), - SizedBox(width: 6), - Text('稍后再看', style: TextStyle(fontSize: 13)) - ], - ), - ), - const PopupMenuDivider(), - PopupMenuItem( - onTap: () async { - SmartDialog.show( - useSystem: true, - animationType: - SmartAnimationType.centerFade_otherSlide, - builder: (BuildContext context) { - return AlertDialog( - title: const Text('提示'), - content: Text( - '确定拉黑:${videoItem.owner.name}(${videoItem.owner.mid})?' - '\n\n注:被拉黑的Up可以在隐私设置-黑名单管理中解除'), - actions: [ - TextButton( - onPressed: () => SmartDialog.dismiss(), - child: Text( - '点错了', - style: TextStyle( - color: Theme.of(context) - .colorScheme - .outline), - ), - ), - TextButton( - onPressed: () async { - var res = await VideoHttp.relationMod( - mid: videoItem.owner.mid, - act: 5, - reSrc: 11, - ); - SmartDialog.dismiss(); - SmartDialog.showToast(res['code'] == 0 - ? '成功' - : res['msg']); - }, - child: const Text('确认'), - ) - ], - ); - }, - ); - }, - value: 'pause', - height: 40, - child: Row( - children: [ - const Icon(Icons.block, size: 16), - const SizedBox(width: 6), - Text('拉黑:${videoItem.owner.name}', - style: const TextStyle(fontSize: 13)) - ], - ), - ), - ], ), ), + if (source == 'later') ...[ + IconButton( + style: ButtonStyle( + padding: MaterialStateProperty.all(EdgeInsets.zero), + ), + onPressed: () => onPressedFn?.call(), + icon: Icon( + Icons.clear_outlined, + color: Theme.of(context).colorScheme.outline, + size: 18, + ), + ) + ], ], ), ], @@ -382,3 +323,110 @@ class VideoContent extends StatelessWidget { ); } } + +class MorePanel extends StatelessWidget { + final dynamic videoItem; + const MorePanel({super.key, required this.videoItem}); + + Future menuActionHandler(String type) async { + switch (type) { + case 'block': + blockUser(); + break; + case 'watchLater': + var res = await UserHttp.toViewLater(bvid: videoItem.bvid as String); + SmartDialog.showToast(res['msg']); + Get.back(); + break; + default: + } + } + + void blockUser() async { + SmartDialog.show( + useSystem: true, + animationType: SmartAnimationType.centerFade_otherSlide, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('提示'), + content: Text('确定拉黑:${videoItem.owner.name}(${videoItem.owner.mid})?' + '\n\n注:被拉黑的Up可以在隐私设置-黑名单管理中解除'), + actions: [ + TextButton( + onPressed: () => SmartDialog.dismiss(), + child: Text( + '点错了', + style: TextStyle(color: Theme.of(context).colorScheme.outline), + ), + ), + TextButton( + onPressed: () async { + var res = await VideoHttp.relationMod( + mid: videoItem.owner.mid, + act: 5, + reSrc: 11, + ); + SmartDialog.dismiss(); + SmartDialog.showToast(res['msg'] ?? '成功'); + }, + child: const Text('确认'), + ) + ], + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + InkWell( + onTap: () => Get.back(), + child: Container( + height: 35, + padding: const EdgeInsets.only(bottom: 2), + child: Center( + child: Container( + width: 32, + height: 3, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.outline, + borderRadius: const BorderRadius.all(Radius.circular(3))), + ), + ), + ), + ), + ListTile( + onTap: () async => await menuActionHandler('block'), + minLeadingWidth: 0, + leading: const Icon(Icons.block, size: 19), + title: Text( + '拉黑up主 「${videoItem.owner.name}」', + style: Theme.of(context).textTheme.titleSmall, + ), + ), + ListTile( + onTap: () async => await menuActionHandler('watchLater'), + minLeadingWidth: 0, + leading: const Icon(Icons.watch_later_outlined, size: 19), + title: + Text('添加至稍后再看', style: Theme.of(context).textTheme.titleSmall), + ), + ListTile( + onTap: () => + imageSaveDialog(context, videoItem, SmartDialog.dismiss), + minLeadingWidth: 0, + leading: const Icon(Icons.photo_outlined, size: 19), + title: + Text('查看视频封面', style: Theme.of(context).textTheme.titleSmall), + ), + const SizedBox(height: 20), + ], + ), + ); + } +} diff --git a/lib/common/widgets/video_card_v.dart b/lib/common/widgets/video_card_v.dart index 9916aa7a..7b56152f 100644 --- a/lib/common/widgets/video_card_v.dart +++ b/lib/common/widgets/video_card_v.dart @@ -2,15 +2,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:pilipala/utils/feed_back.dart'; +import 'package:pilipala/utils/image_save.dart'; +import 'package:pilipala/utils/route_push.dart'; import '../../models/model_rec_video_item.dart'; -import 'overlay_pop.dart'; import 'stat/danmu.dart'; import 'stat/view.dart'; import '../../http/dynamics.dart'; -import '../../http/search.dart'; import '../../http/user.dart'; import '../../http/video.dart'; -import '../../models/common/search_type.dart'; import '../../utils/id_utils.dart'; import '../../utils/utils.dart'; import '../constants.dart'; @@ -42,23 +41,11 @@ class VideoCardV extends StatelessWidget { return; } int epId = videoItem.param; - SmartDialog.showLoading(msg: '资源获取中'); - var result = await SearchHttp.bangumiInfo(seasonId: null, epId: epId); - if (result['status']) { - var bangumiDetail = result['data']; - int cid = bangumiDetail.episodes!.first.cid; - String bvid = IdUtils.av2bv(bangumiDetail.episodes!.first.aid); - SmartDialog.dismiss().then( - (value) => Get.toNamed( - '/video?bvid=$bvid&cid=$cid&epId=$epId', - arguments: { - 'pic': videoItem.pic, - 'heroTag': heroTag, - 'videoType': SearchType.media_bangumi, - }, - ), - ); - } + RoutePush.bangumiPush( + null, + epId, + heroTag: heroTag, + ); break; case 'av': String bvid = videoItem.bvid ?? IdUtils.av2bv(videoItem.aid); @@ -127,14 +114,11 @@ class VideoCardV extends StatelessWidget { String heroTag = Utils.makeHeroTag(videoItem.id); return InkWell( onTap: () async => onPushDetail(heroTag), - onLongPress: () { - SmartDialog.show( - builder: (context) => OverlayPop( - videoItem: videoItem, - closeFn: () => SmartDialog.dismiss(), - ), - ); - }, + onLongPress: () => imageSaveDialog( + context, + videoItem, + SmartDialog.dismiss, + ), borderRadius: BorderRadius.circular(16), child: Column( children: [ @@ -249,6 +233,7 @@ class VideoContent extends StatelessWidget { width: 24, height: 24, child: IconButton( + padding: EdgeInsets.zero, onPressed: () { feedBack(); showModalBottomSheet( @@ -402,6 +387,15 @@ class MorePanel extends StatelessWidget { title: Text('添加至稍后再看', style: Theme.of(context).textTheme.titleSmall), ), + ListTile( + onTap: () => + imageSaveDialog(context, videoItem, SmartDialog.dismiss), + minLeadingWidth: 0, + leading: const Icon(Icons.photo_outlined, size: 19), + title: + Text('查看视频封面', style: Theme.of(context).textTheme.titleSmall), + ), + const SizedBox(height: 20), ], ), ); diff --git a/lib/http/api.dart b/lib/http/api.dart index 1735902c..42fc03b8 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -520,4 +520,7 @@ class Api { /// 删除收藏夹 static const String delFavFolder = '/x/v3/fav/folder/del'; + + /// 搜索结果计数 + static const String searchCount = '/x/web-interface/wbi/search/all/v2'; } diff --git a/lib/http/dynamics.dart b/lib/http/dynamics.dart index 63dea4ff..69619361 100644 --- a/lib/http/dynamics.dart +++ b/lib/http/dynamics.dart @@ -41,6 +41,7 @@ class DynamicsHttp { 'status': false, 'data': [], 'msg': res.data['message'], + 'code': res.data['code'], }; } } diff --git a/lib/http/interceptor.dart b/lib/http/interceptor.dart index a5359283..10b5aee5 100644 --- a/lib/http/interceptor.dart +++ b/lib/http/interceptor.dart @@ -46,7 +46,7 @@ class ApiInterceptor extends Interceptor { // 处理网络请求错误 // handler.next(err); String url = err.requestOptions.uri.toString(); - if (!url.contains('heartBeat')) { + if (!url.contains('heartbeat')) { SmartDialog.showToast( await dioError(err), displayType: SmartToastType.onlyRefresh, diff --git a/lib/http/search.dart b/lib/http/search.dart index 18481ea8..9b493a84 100644 --- a/lib/http/search.dart +++ b/lib/http/search.dart @@ -1,5 +1,7 @@ import 'dart:convert'; import 'package:hive/hive.dart'; +import 'package:pilipala/models/search/all.dart'; +import 'package:pilipala/utils/wbi_sign.dart'; import '../models/bangumi/info.dart'; import '../models/common/search_type.dart'; import '../models/search/hot.dart'; @@ -163,4 +165,42 @@ class SearchHttp { }; } } + + static Future> ab2cWithPic( + {int? aid, String? bvid}) async { + Map data = {}; + if (aid != null) { + data['aid'] = aid; + } else if (bvid != null) { + data['bvid'] = bvid; + } + final dynamic res = + await Request().get(Api.ab2c, data: {...data}); + return { + 'cid': res.data['data'].first['cid'], + 'pic': res.data['data'].first['first_frame'], + }; + } + + static Future> searchCount( + {required String keyword}) async { + Map data = { + 'keyword': keyword, + 'web_location': 333.999, + }; + Map params = await WbiSign().makSign(data); + final dynamic res = await Request().get(Api.searchCount, data: params); + if (res.data['code'] == 0) { + return { + 'status': true, + 'data': SearchAllModel.fromJson(res.data['data']), + }; + } else { + return { + 'status': false, + 'data': [], + 'msg': '请求错误 🙅', + }; + } + } } diff --git a/lib/http/user.dart b/lib/http/user.dart index dfdf187e..972acfdd 100644 --- a/lib/http/user.dart +++ b/lib/http/user.dart @@ -62,7 +62,8 @@ class UserHttp { return { 'status': false, 'data': [], - 'msg': res.data['message'] ?? '账号未登录' + 'msg': res.data['message'], + 'code': res.data['code'], }; } } @@ -111,7 +112,12 @@ class UserHttp { 'data': {'list': list, 'count': res.data['data']['count']} }; } else { - return {'status': false, 'data': [], 'msg': res.data['message']}; + return { + 'status': false, + 'data': [], + 'msg': res.data['message'], + 'code': res.data['code'], + }; } } @@ -126,7 +132,12 @@ class UserHttp { if (res.data['code'] == 0) { return {'status': true, 'data': HistoryData.fromJson(res.data['data'])}; } else { - return {'status': false, 'data': [], 'msg': res.data['message']}; + return { + 'status': false, + 'data': [], + 'msg': res.data['message'], + 'code': res.data['code'], + }; } } @@ -326,7 +337,12 @@ class UserHttp { 'data': SubFolderModelData.fromJson(res.data['data']) }; } else { - return {'status': false, 'msg': res.data['message']}; + return { + 'status': false, + 'data': [], + 'msg': res.data['message'], + 'code': res.data['code'], + }; } } diff --git a/lib/main.dart b/lib/main.dart index 43bcf930..066bb599 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/services.dart'; import 'package:flutter_displaymode/flutter_displaymode.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; @@ -67,9 +68,8 @@ void main() async { // 小白条、导航栏沉浸 if (Platform.isAndroid) { - List versionParts = Platform.version.split('.'); - int androidVersion = int.parse(versionParts[0]); - if (androidVersion >= 29) { + final androidInfo = await DeviceInfoPlugin().androidInfo; + if (androidInfo.version.sdkInt >= 29) { SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); } SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle( diff --git a/lib/models/bangumi/list.dart b/lib/models/bangumi/list.dart index c15014d0..fe71bb61 100644 --- a/lib/models/bangumi/list.dart +++ b/lib/models/bangumi/list.dart @@ -30,6 +30,7 @@ class BangumiListItemModel { BangumiListItemModel({ this.badge, this.badgeType, + this.pic, this.cover, // this.firstEp, this.indexShow, @@ -50,6 +51,7 @@ class BangumiListItemModel { String? badge; int? badgeType; + String? pic; String? cover; String? indexShow; int? isFinish; @@ -70,6 +72,7 @@ class BangumiListItemModel { BangumiListItemModel.fromJson(Map json) { badge = json['badge'] == '' ? null : json['badge']; badgeType = json['badge_type']; + pic = json['cover']; cover = json['cover']; indexShow = json['index_show']; isFinish = json['is_finish']; diff --git a/lib/models/dynamics/result.dart b/lib/models/dynamics/result.dart index bc7105d1..64a6e5b1 100644 --- a/lib/models/dynamics/result.dart +++ b/lib/models/dynamics/result.dart @@ -415,6 +415,7 @@ class DynamicMajorModel { this.type, this.courses, this.common, + this.music, }); DynamicArchiveModel? archive; @@ -431,6 +432,7 @@ class DynamicMajorModel { String? type; Map? courses; Map? common; + Map? music; DynamicMajorModel.fromJson(Map json) { archive = json['archive'] != null @@ -455,6 +457,7 @@ class DynamicMajorModel { type = json['type']; courses = json['courses'] ?? {}; common = json['common'] ?? {}; + music = json['music'] ?? {}; } } diff --git a/lib/models/search/all.dart b/lib/models/search/all.dart new file mode 100644 index 00000000..796400da --- /dev/null +++ b/lib/models/search/all.dart @@ -0,0 +1,9 @@ +class SearchAllModel { + SearchAllModel({this.topTList}); + + Map? topTList; + + SearchAllModel.fromJson(Map json) { + topTList = json['top_tlist']; + } +} diff --git a/lib/models/search/result.dart b/lib/models/search/result.dart index 418fb99d..81917b72 100644 --- a/lib/models/search/result.dart +++ b/lib/models/search/result.dart @@ -25,6 +25,7 @@ class SearchVideoItemModel { this.aid, this.bvid, this.title, + this.titleList, this.description, this.pic, // this.play, @@ -54,8 +55,8 @@ class SearchVideoItemModel { String? arcurl; int? aid; String? bvid; - List? title; - // List? titleList; + String? title; + List? titleList; String? description; String? pic; // String? play; @@ -82,8 +83,9 @@ class SearchVideoItemModel { aid = json['aid']; bvid = json['bvid']; mid = json['mid']; - // title = json['title'].replaceAll(RegExp(r'<.*?>'), ''); - title = Em.regTitle(json['title']); + title = json['title'].replaceAll(RegExp(r'<.*?>'), ''); + // title = Em.regTitle(json['title']); + titleList = Em.regTitle(json['title']); description = json['description']; pic = json['pic'] != null && json['pic'].startsWith('//') ? 'https:${json['pic']}' @@ -232,6 +234,7 @@ class SearchLiveItemModel { this.userCover, this.type, this.title, + this.titleList, this.cover, this.pic, this.online, @@ -251,7 +254,8 @@ class SearchLiveItemModel { String? face; String? userCover; String? type; - List? title; + String? title; + List? titleList; String? cover; String? pic; int? online; @@ -272,7 +276,8 @@ class SearchLiveItemModel { face = json['uface']; userCover = json['user_cover']; type = json['type']; - title = Em.regTitle(json['title']); + title = json['title'].replaceAll(RegExp(r'<.*?>'), ''); + titleList = Em.regTitle(json['title']); cover = json['cover']; pic = json['cover']; online = json['online']; @@ -302,6 +307,7 @@ class SearchMBangumiItemModel { this.type, this.mediaId, this.title, + this.titleList, this.orgTitle, this.mediaType, this.cv, @@ -328,7 +334,8 @@ class SearchMBangumiItemModel { String? type; int? mediaId; - List? title; + String? title; + List? titleList; String? orgTitle; int? mediaType; String? cv; @@ -355,7 +362,8 @@ class SearchMBangumiItemModel { SearchMBangumiItemModel.fromJson(Map json) { type = json['type']; mediaId = json['media_id']; - title = Em.regTitle(json['title']); + title = json['title'].replaceAll(RegExp(r'<.*?>'), ''); + titleList = Em.regTitle(json['title']); orgTitle = json['org_title']; mediaType = json['media_type']; cv = json['cv']; diff --git a/lib/pages/about/index.dart b/lib/pages/about/index.dart index b381691a..81d3c3f4 100644 --- a/lib/pages/about/index.dart +++ b/lib/pages/about/index.dart @@ -218,7 +218,7 @@ class AboutController extends GetxController { RxString currentVersion = ''.obs; RxString remoteVersion = ''.obs; late LatestDataModel remoteAppInfo; - RxBool isUpdate = true.obs; + RxBool isUpdate = false.obs; RxBool isLoading = true.obs; late LatestDataModel data; diff --git a/lib/pages/bangumi/introduction/view.dart b/lib/pages/bangumi/introduction/view.dart index 6876da79..95d4d898 100644 --- a/lib/pages/bangumi/introduction/view.dart +++ b/lib/pages/bangumi/introduction/view.dart @@ -194,7 +194,8 @@ class _BangumiInfoState extends State { src: widget.bangumiDetail!.cover!, ), PBadge( - text: '评分 ${widget.bangumiDetail!.rating!['score']!}', + text: + '评分 ${widget.bangumiDetail?.rating?['score']! ?? '暂无'}', top: null, right: 6, bottom: 6, diff --git a/lib/pages/bangumi/widgets/bangumu_card_v.dart b/lib/pages/bangumi/widgets/bangumu_card_v.dart index c1233ddf..a1d7a931 100644 --- a/lib/pages/bangumi/widgets/bangumu_card_v.dart +++ b/lib/pages/bangumi/widgets/bangumu_card_v.dart @@ -1,11 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -import 'package:get/get.dart'; import 'package:pilipala/common/constants.dart'; import 'package:pilipala/common/widgets/badge.dart'; -import 'package:pilipala/http/search.dart'; -import 'package:pilipala/models/bangumi/info.dart'; -import 'package:pilipala/models/common/search_type.dart'; +import 'package:pilipala/models/bangumi/list.dart'; +import 'package:pilipala/utils/image_save.dart'; +import 'package:pilipala/utils/route_push.dart'; import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; @@ -14,109 +13,67 @@ class BangumiCardV extends StatelessWidget { const BangumiCardV({ super.key, required this.bangumiItem, - this.longPress, - this.longPressEnd, }); - final bangumiItem; - final Function()? longPress; - final Function()? longPressEnd; + final BangumiListItemModel bangumiItem; @override Widget build(BuildContext context) { String heroTag = Utils.makeHeroTag(bangumiItem.mediaId); - return Card( - elevation: 0, - clipBehavior: Clip.hardEdge, - margin: EdgeInsets.zero, - child: GestureDetector( - // onLongPress: () { - // if (longPress != null) { - // longPress!(); - // } - // }, - // onLongPressEnd: (details) { - // if (longPressEnd != null) { - // longPressEnd!(); - // } - // }, - child: InkWell( - onTap: () async { - final int seasonId = bangumiItem.seasonId; - SmartDialog.showLoading(msg: '获取中...'); - final res = await SearchHttp.bangumiInfo(seasonId: seasonId); - SmartDialog.dismiss().then((value) { - if (res['status']) { - if (res['data'].episodes.isEmpty) { - SmartDialog.showToast('资源加载失败'); - return; - } - EpisodeItem episode = res['data'].episodes.first; - String bvid = episode.bvid!; - int cid = episode.cid!; - String pic = episode.cover!; - String heroTag = Utils.makeHeroTag(cid); - Get.toNamed( - '/video?bvid=$bvid&cid=$cid&seasonId=$seasonId', - arguments: { - 'pic': pic, - 'heroTag': heroTag, - 'videoType': SearchType.media_bangumi, - 'bangumiItem': res['data'], - }, + return InkWell( + onTap: () { + RoutePush.bangumiPush( + bangumiItem.seasonId, + null, + heroTag: heroTag, + ); + }, + onLongPress: () => + imageSaveDialog(context, bangumiItem, SmartDialog.dismiss), + child: Column( + children: [ + ClipRRect( + borderRadius: const BorderRadius.all( + StyleString.imgRadius, + ), + child: AspectRatio( + aspectRatio: 0.65, + child: LayoutBuilder(builder: (context, boxConstraints) { + final double maxWidth = boxConstraints.maxWidth; + final double maxHeight = boxConstraints.maxHeight; + return Stack( + children: [ + Hero( + tag: heroTag, + child: NetworkImgLayer( + src: bangumiItem.cover, + width: maxWidth, + height: maxHeight, + ), + ), + if (bangumiItem.badge != null) + PBadge( + text: bangumiItem.badge, + top: 6, + right: 6, + bottom: null, + left: null), + if (bangumiItem.order != null) + PBadge( + text: bangumiItem.order, + top: null, + right: null, + bottom: 6, + left: 6, + type: 'gray', + ), + ], ); - } - }); - }, - child: Column( - children: [ - ClipRRect( - borderRadius: const BorderRadius.only( - topLeft: StyleString.imgRadius, - topRight: StyleString.imgRadius, - bottomLeft: StyleString.imgRadius, - bottomRight: StyleString.imgRadius, - ), - child: AspectRatio( - aspectRatio: 0.65, - child: LayoutBuilder(builder: (context, boxConstraints) { - final double maxWidth = boxConstraints.maxWidth; - final double maxHeight = boxConstraints.maxHeight; - return Stack( - children: [ - Hero( - tag: heroTag, - child: NetworkImgLayer( - src: bangumiItem.cover, - width: maxWidth, - height: maxHeight, - ), - ), - if (bangumiItem.badge != null) - PBadge( - text: bangumiItem.badge, - top: 6, - right: 6, - bottom: null, - left: null), - if (bangumiItem.order != null) - PBadge( - text: bangumiItem.order, - top: null, - right: null, - bottom: 6, - left: 6, - type: 'gray', - ), - ], - ); - }), - ), - ), - BangumiContent(bangumiItem: bangumiItem) - ], + }), + ), ), - ), + BangumiContent(bangumiItem: bangumiItem) + ], ), ); } diff --git a/lib/pages/dynamics/controller.dart b/lib/pages/dynamics/controller.dart index b7676663..9bed3685 100644 --- a/lib/pages/dynamics/controller.dart +++ b/lib/pages/dynamics/controller.dart @@ -14,6 +14,7 @@ import 'package:pilipala/models/dynamics/up.dart'; import 'package:pilipala/models/live/item.dart'; import 'package:pilipala/utils/feed_back.dart'; import 'package:pilipala/utils/id_utils.dart'; +import 'package:pilipala/utils/route_push.dart'; import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/utils.dart'; @@ -70,7 +71,7 @@ class DynamicsController extends GetxController { Future queryFollowDynamic({type = 'init'}) async { if (!userLogin.value) { - return {'status': false, 'msg': '账号未登录'}; + return {'status': false, 'msg': '账号未登录', 'code': -101}; } if (type == 'init') { dynamicsList.clear(); @@ -220,25 +221,7 @@ class DynamicsController extends GetxController { print('DYNAMIC_TYPE_PGC_UNION 番剧'); DynamicArchiveModel pgc = item.modules.moduleDynamic.major.pgc; if (pgc.epid != null) { - SmartDialog.showLoading(msg: '获取中...'); - var res = await SearchHttp.bangumiInfo(epId: pgc.epid); - SmartDialog.dismiss(); - if (res['status']) { - EpisodeItem episode = res['data'].episodes.first; - String bvid = episode.bvid!; - int cid = episode.cid!; - String pic = episode.cover!; - String heroTag = Utils.makeHeroTag(cid); - Get.toNamed( - '/video?bvid=$bvid&cid=$cid&seasonId=${res['data'].seasonId}', - arguments: { - 'pic': pic, - 'heroTag': heroTag, - 'videoType': SearchType.media_bangumi, - 'bangumiItem': res['data'], - }, - ); - } + RoutePush.bangumiPush(null, pgc.epid); } break; } @@ -246,7 +229,7 @@ class DynamicsController extends GetxController { Future queryFollowUp({type = 'init'}) async { if (!userLogin.value) { - return {'status': false, 'msg': '账号未登录'}; + return {'status': false, 'msg': '账号未登录', 'code': -101}; } if (type == 'init') { upData.value.upList = []; diff --git a/lib/pages/dynamics/detail/view.dart b/lib/pages/dynamics/detail/view.dart index bd07921b..e83b5547 100644 --- a/lib/pages/dynamics/detail/view.dart +++ b/lib/pages/dynamics/detail/view.dart @@ -196,7 +196,7 @@ class _DynamicDetailPageState extends State centerTitle: false, titleSpacing: 0, title: StreamBuilder( - stream: titleStreamC.stream.distinct(), + stream: titleStreamC.stream, initialData: false, builder: (context, AsyncSnapshot snapshot) { return AnimatedOpacity( diff --git a/lib/pages/dynamics/view.dart b/lib/pages/dynamics/view.dart index 82a555b1..da15239d 100644 --- a/lib/pages/dynamics/view.dart +++ b/lib/pages/dynamics/view.dart @@ -11,6 +11,7 @@ import 'package:pilipala/common/widgets/no_data.dart'; import 'package:pilipala/models/dynamics/result.dart'; import 'package:pilipala/utils/feed_back.dart'; import 'package:pilipala/utils/main_stream.dart'; +import 'package:pilipala/utils/route_push.dart'; import 'package:pilipala/utils/storage.dart'; import '../mine/controller.dart'; @@ -224,8 +225,8 @@ class _DynamicsPageState extends State if (snapshot.data == null) { return const SliverToBoxAdapter(child: SizedBox()); } - Map data = snapshot.data; - if (data['status']) { + Map? data = snapshot.data; + if (data != null && data['status']) { List list = _dynamicsController.dynamicsList; return Obx( @@ -248,24 +249,21 @@ class _DynamicsPageState extends State } }, ); - } else if (data['msg'] == "账号未登录") { - return HttpError( - errMsg: data['msg'], - btnText: "去登录", - fn: () { - mineController.onLogin(); - }, - ); } else { return HttpError( - errMsg: data['msg'], + errMsg: data?['msg'] ?? '请求异常', + btnText: data?['code'] == -101 ? '去登录' : null, fn: () { - setState(() { - _futureBuilderFuture = - _dynamicsController.queryFollowDynamic(); - _futureBuilderFutureUp = - _dynamicsController.queryFollowUp(); - }); + if (data?['code'] == -101) { + RoutePush.loginRedirectPush(); + } else { + setState(() { + _futureBuilderFuture = + _dynamicsController.queryFollowDynamic(); + _futureBuilderFutureUp = + _dynamicsController.queryFollowUp(); + }); + } }, ); } diff --git a/lib/pages/dynamics/widgets/dynamic_panel.dart b/lib/pages/dynamics/widgets/dynamic_panel.dart index d273a1a6..cc5dcbb1 100644 --- a/lib/pages/dynamics/widgets/dynamic_panel.dart +++ b/lib/pages/dynamics/widgets/dynamic_panel.dart @@ -1,14 +1,13 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:pilipala/pages/dynamics/index.dart'; -import '../../../models/dynamics/result.dart'; import 'action_panel.dart'; import 'author_panel.dart'; import 'content_panel.dart'; import 'forward_panel.dart'; class DynamicPanel extends StatelessWidget { - final DynamicItemModel item; + final dynamic item; final String? source; DynamicPanel({required this.item, this.source, Key? key}) : super(key: key); final DynamicsController _dynamicsController = Get.put(DynamicsController()); diff --git a/lib/pages/dynamics/widgets/forward_panel.dart b/lib/pages/dynamics/widgets/forward_panel.dart index 76c7b24e..f8b90a81 100644 --- a/lib/pages/dynamics/widgets/forward_panel.dart +++ b/lib/pages/dynamics/widgets/forward_panel.dart @@ -238,6 +238,61 @@ Widget forWard(item, context, ctr, source, {floor = 1}) { ), ), ); + case 'DYNAMIC_TYPE_MUSIC': + final Map music = item.modules.moduleDynamic.major.music; + return Padding( + padding: const EdgeInsets.only(top: 8), + child: InkWell( + onTap: () { + Get.toNamed('/webview', parameters: { + 'url': "https:${music['jump_url']}", + 'type': 'url', + 'pageTitle': music['title'] + }); + }, + child: Container( + width: double.infinity, + padding: + const EdgeInsets.only(left: 12, top: 10, right: 12, bottom: 10), + color: Theme.of(context).dividerColor.withOpacity(0.08), + child: Row( + children: [ + NetworkImgLayer( + width: 45, + height: 45, + src: music['cover'], + ), + const SizedBox(width: 10), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + music['title'], + style: TextStyle( + color: Theme.of(context).colorScheme.primary, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 2), + Text( + music['label'], + style: TextStyle( + color: Theme.of(context).colorScheme.outline, + fontSize: + Theme.of(context).textTheme.labelMedium!.fontSize, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ) + ], + ), + // TextButton(onPressed: () {}, child: Text('123')) + ), + ), + ); default: return const SizedBox( width: double.infinity, diff --git a/lib/pages/dynamics/widgets/video_panel.dart b/lib/pages/dynamics/widgets/video_panel.dart index 32a6e21c..828fb283 100644 --- a/lib/pages/dynamics/widgets/video_panel.dart +++ b/lib/pages/dynamics/widgets/video_panel.dart @@ -80,14 +80,11 @@ Widget videoSeasonWidget(item, context, type, {floor = 1}) { double width = box.maxWidth; return Stack( children: [ - Hero( - tag: content.bvid, - child: NetworkImgLayer( - type: floor == 1 ? 'emote' : null, - width: width, - height: width / StyleString.aspectRatio, - src: content.cover, - ), + NetworkImgLayer( + type: floor == 1 ? 'emote' : null, + width: width, + height: width / StyleString.aspectRatio, + src: content.cover, ), if (content.badge != null && type == 'pgc') PBadge( diff --git a/lib/pages/fav/controller.dart b/lib/pages/fav/controller.dart index 2307d303..8fcbf971 100644 --- a/lib/pages/fav/controller.dart +++ b/lib/pages/fav/controller.dart @@ -17,10 +17,15 @@ class FavController extends GetxController { int pageSize = 60; RxBool hasMore = true.obs; - Future queryFavFolder({type = 'init'}) async { + @override + void onInit() { userInfo = userInfoCache.get('userInfoCache'); + super.onInit(); + } + + Future queryFavFolder({type = 'init'}) async { if (userInfo == null) { - return {'status': false, 'msg': '账号未登录'}; + return {'status': false, 'msg': '账号未登录', 'code': -101}; } if (!hasMore.value) { return; diff --git a/lib/pages/fav/view.dart b/lib/pages/fav/view.dart index 424a885d..b8d37f50 100644 --- a/lib/pages/fav/view.dart +++ b/lib/pages/fav/view.dart @@ -4,6 +4,7 @@ import 'package:get/get.dart'; import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/pages/fav/index.dart'; import 'package:pilipala/pages/fav/widgets/item.dart'; +import 'package:pilipala/utils/route_push.dart'; class FavPage extends StatefulWidget { const FavPage({super.key}); @@ -57,8 +58,8 @@ class _FavPageState extends State { future: _futureBuilderFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { - Map data = snapshot.data as Map; - if (data['status']) { + Map? data = snapshot.data; + if (data != null && data['status']) { return Obx( () => ListView.builder( controller: scrollController, @@ -74,8 +75,18 @@ class _FavPageState extends State { physics: const NeverScrollableScrollPhysics(), slivers: [ HttpError( - errMsg: data['msg'], - fn: () => setState(() {}), + errMsg: data?['msg'] ?? '请求异常', + btnText: data?['code'] == -101 ? '去登录' : null, + fn: () { + if (data?['code'] == -101) { + RoutePush.loginRedirectPush(); + } else { + setState(() { + _futureBuilderFuture = + _favController.queryFavFolder(); + }); + } + }, ), ], ); diff --git a/lib/pages/fav_detail/widget/fav_video_card.dart b/lib/pages/fav_detail/widget/fav_video_card.dart index 1c4008ff..79e5c073 100644 --- a/lib/pages/fav_detail/widget/fav_video_card.dart +++ b/lib/pages/fav_detail/widget/fav_video_card.dart @@ -1,3 +1,4 @@ +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:flutter/material.dart'; import 'package:pilipala/common/constants.dart'; @@ -7,6 +8,7 @@ import 'package:pilipala/http/search.dart'; import 'package:pilipala/http/video.dart'; import 'package:pilipala/models/common/search_type.dart'; import 'package:pilipala/utils/id_utils.dart'; +import 'package:pilipala/utils/image_save.dart'; import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; import '../../../common/widgets/badge.dart'; @@ -61,6 +63,11 @@ class FavVideoCardH extends StatelessWidget { epId != null ? SearchType.media_bangumi : SearchType.video, }); }, + onLongPress: () => imageSaveDialog( + context, + videoItem, + SmartDialog.dismiss, + ), child: Column( children: [ Padding( diff --git a/lib/pages/history/controller.dart b/lib/pages/history/controller.dart index a1f18113..64953e3b 100644 --- a/lib/pages/history/controller.dart +++ b/lib/pages/history/controller.dart @@ -4,6 +4,7 @@ import 'package:get/get.dart'; import 'package:hive/hive.dart'; import 'package:pilipala/http/user.dart'; import 'package:pilipala/models/user/history.dart'; +import 'package:pilipala/models/user/info.dart'; import 'package:pilipala/utils/storage.dart'; class HistoryController extends GetxController { @@ -15,14 +16,20 @@ class HistoryController extends GetxController { RxBool isLoading = false.obs; RxBool enableMultiple = false.obs; RxInt checkedCount = 0.obs; + Box userInfoCache = GStrorage.userInfo; + UserInfoData? userInfo; @override void onInit() { super.onInit(); historyStatus(); + userInfo = userInfoCache.get('userInfoCache'); } Future queryHistoryList({type = 'init'}) async { + if (userInfo == null) { + return {'status': false, 'msg': '账号未登录', 'code': -101}; + } int max = 0; int viewAt = 0; if (type == 'onload') { diff --git a/lib/pages/history/view.dart b/lib/pages/history/view.dart index 92e1eee7..f9695aed 100644 --- a/lib/pages/history/view.dart +++ b/lib/pages/history/view.dart @@ -5,6 +5,7 @@ import 'package:pilipala/common/skeleton/video_card_h.dart'; import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/common/widgets/no_data.dart'; import 'package:pilipala/pages/history/index.dart'; +import 'package:pilipala/utils/route_push.dart'; import 'widgets/item.dart'; @@ -183,8 +184,8 @@ class _HistoryPageState extends State { if (snapshot.data == null) { return const SliverToBoxAdapter(child: SizedBox()); } - Map data = snapshot.data; - if (data['status']) { + Map? data = snapshot.data; + if (data != null && data['status']) { return Obx( () => _historyController.historyList.isNotEmpty ? SliverList( @@ -209,8 +210,18 @@ class _HistoryPageState extends State { ); } else { return HttpError( - errMsg: data['msg'], - fn: () => setState(() {}), + errMsg: data?['msg'] ?? '请求异常', + btnText: data?['code'] == -101 ? '去登录' : null, + fn: () { + if (data?['code'] == -101) { + RoutePush.loginRedirectPush(); + } else { + setState(() { + _futureBuilderFuture = + _historyController.queryHistoryList(); + }); + } + }, ); } } else { diff --git a/lib/pages/history/widgets/item.dart b/lib/pages/history/widgets/item.dart index 39c6931d..00ebd511 100644 --- a/lib/pages/history/widgets/item.dart +++ b/lib/pages/history/widgets/item.dart @@ -7,13 +7,13 @@ import 'package:pilipala/common/widgets/network_img_layer.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/common/business_type.dart'; import 'package:pilipala/models/common/search_type.dart'; import 'package:pilipala/models/live/item.dart'; import 'package:pilipala/pages/history_search/index.dart'; import 'package:pilipala/utils/feed_back.dart'; import 'package:pilipala/utils/id_utils.dart'; +import 'package:pilipala/utils/route_push.dart'; import 'package:pilipala/utils/utils.dart'; class HistoryItem extends StatelessWidget { @@ -101,26 +101,11 @@ class HistoryItem extends StatelessWidget { } } else { if (videoItem.history.epid != '') { - SmartDialog.showLoading(msg: '获取中...'); - var res = - await SearchHttp.bangumiInfo(epId: videoItem.history.epid); - SmartDialog.dismiss(); - if (res['status']) { - EpisodeItem episode = res['data'].episodes.first; - String bvid = episode.bvid!; - int cid = episode.cid!; - String pic = episode.cover!; - String heroTag = Utils.makeHeroTag(cid); - Get.toNamed( - '/video?bvid=$bvid&cid=$cid&seasonId=${res['data'].seasonId}', - arguments: { - 'pic': pic, - 'heroTag': heroTag, - 'videoType': SearchType.media_bangumi, - 'bangumiItem': res['data'], - }, - ); - } + RoutePush.bangumiPush( + null, + videoItem.history.epid, + heroTag: heroTag, + ); } } } else { @@ -213,7 +198,8 @@ class HistoryItem extends StatelessWidget { duration: const Duration(milliseconds: 200), child: Container( decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular( + StyleString.imgRadius.x), color: Colors.black.withOpacity( ctr!.enableMultiple.value && videoItem.checked diff --git a/lib/pages/hot/view.dart b/lib/pages/hot/view.dart index e2e20e73..80d08e7b 100644 --- a/lib/pages/hot/view.dart +++ b/lib/pages/hot/view.dart @@ -3,8 +3,6 @@ import 'dart:async'; import 'package:flutter/material.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'; import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/common/widgets/video_card_h.dart'; @@ -78,15 +76,6 @@ class _HotPageState extends State with AutomaticKeepAliveClientMixin { return VideoCardH( videoItem: _hotController.videoList[index], showPubdate: true, - longPress: () { - _hotController.popupDialog = _createPopupDialog( - _hotController.videoList[index]); - Overlay.of(context) - .insert(_hotController.popupDialog!); - }, - longPressEnd: () { - _hotController.popupDialog?.remove(); - }, ); }, childCount: _hotController.videoList.length), ), @@ -122,14 +111,4 @@ class _HotPageState extends State with AutomaticKeepAliveClientMixin { ), ); } - - OverlayEntry _createPopupDialog(videoItem) { - return OverlayEntry( - builder: (context) => AnimatedDialog( - closeFn: _hotController.popupDialog?.remove, - child: OverlayPop( - videoItem: videoItem, closeFn: _hotController.popupDialog?.remove), - ), - ); - } } diff --git a/lib/pages/later/controller.dart b/lib/pages/later/controller.dart index 3de51901..f7dfea9f 100644 --- a/lib/pages/later/controller.dart +++ b/lib/pages/later/controller.dart @@ -1,16 +1,30 @@ 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/user.dart'; import 'package:pilipala/models/model_hot_video_item.dart'; +import 'package:pilipala/models/user/info.dart'; +import 'package:pilipala/utils/storage.dart'; class LaterController extends GetxController { final ScrollController scrollController = ScrollController(); RxList laterList = [].obs; int count = 0; RxBool isLoading = false.obs; + Box userInfoCache = GStrorage.userInfo; + UserInfoData? userInfo; + + @override + void onInit() { + super.onInit(); + userInfo = userInfoCache.get('userInfoCache'); + } Future queryLaterList() async { + if (userInfo == null) { + return {'status': false, 'msg': '账号未登录', 'code': -101}; + } isLoading.value = true; var res = await UserHttp.seeYouLater(); if (res['status']) { diff --git a/lib/pages/later/view.dart b/lib/pages/later/view.dart index d4695154..0a02cb79 100644 --- a/lib/pages/later/view.dart +++ b/lib/pages/later/view.dart @@ -5,6 +5,7 @@ import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/common/widgets/no_data.dart'; import 'package:pilipala/common/widgets/video_card_h.dart'; import 'package:pilipala/pages/later/index.dart'; +import 'package:pilipala/utils/route_push.dart'; class LaterPage extends StatefulWidget { const LaterPage({super.key}); @@ -72,8 +73,8 @@ class _LaterPageState extends State { future: _futureBuilderFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { - Map data = snapshot.data as Map; - if (data['status']) { + Map? data = snapshot.data; + if (data != null && data['status']) { return Obx( () => _laterController.laterList.isNotEmpty && !_laterController.isLoading.value @@ -84,7 +85,7 @@ class _LaterPageState extends State { return VideoCardH( videoItem: videoItem, source: 'later', - longPress: () => _laterController.toViewDel( + onPressedFn: () => _laterController.toViewDel( aid: videoItem.aid)); }, childCount: _laterController.laterList.length), ) @@ -96,10 +97,18 @@ class _LaterPageState extends State { ); } else { return HttpError( - errMsg: data['msg'], - fn: () => setState(() { - _futureBuilderFuture = _laterController.queryLaterList(); - }), + errMsg: data?['msg'] ?? '请求异常', + btnText: data?['code'] == -101 ? '去登录' : null, + fn: () { + if (data?['code'] == -101) { + RoutePush.loginRedirectPush(); + } else { + setState(() { + _futureBuilderFuture = + _laterController.queryLaterList(); + }); + } + }, ); } } else { diff --git a/lib/pages/live/view.dart b/lib/pages/live/view.dart index c61d20b3..83605495 100644 --- a/lib/pages/live/view.dart +++ b/lib/pages/live/view.dart @@ -5,9 +5,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/constants.dart'; import 'package:pilipala/common/skeleton/video_card_v.dart'; -import 'package:pilipala/common/widgets/animated_dialog.dart'; import 'package:pilipala/common/widgets/http_error.dart'; -import 'package:pilipala/common/widgets/overlay_pop.dart'; import 'package:pilipala/utils/main_stream.dart'; import 'controller.dart'; @@ -112,16 +110,6 @@ class _LivePageState extends State ); } - OverlayEntry _createPopupDialog(liveItem) { - return OverlayEntry( - builder: (context) => AnimatedDialog( - closeFn: _liveController.popupDialog?.remove, - child: OverlayPop( - videoItem: liveItem, closeFn: _liveController.popupDialog?.remove), - ), - ); - } - Widget contentGrid(ctr, liveList) { // double maxWidth = Get.size.width; // int baseWidth = 500; @@ -152,14 +140,6 @@ class _LivePageState extends State ? LiveCardV( liveItem: liveList[index], crossAxisCount: crossAxisCount, - longPress: () { - _liveController.popupDialog = - _createPopupDialog(liveList[index]); - Overlay.of(context).insert(_liveController.popupDialog!); - }, - longPressEnd: () { - _liveController.popupDialog?.remove(); - }, ) : const VideoCardVSkeleton(); }, diff --git a/lib/pages/live/widgets/live_item.dart b/lib/pages/live/widgets/live_item.dart index 9218d4fb..f70ba82b 100644 --- a/lib/pages/live/widgets/live_item.dart +++ b/lib/pages/live/widgets/live_item.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/constants.dart'; import 'package:pilipala/models/live/item.dart'; +import 'package:pilipala/utils/image_save.dart'; import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; @@ -9,81 +11,66 @@ 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); @override Widget build(BuildContext context) { String heroTag = Utils.makeHeroTag(liveItem.roomId); - return Card( - elevation: 0, - clipBehavior: Clip.hardEdge, - margin: EdgeInsets.zero, - child: GestureDetector( - onLongPress: () { - if (longPress != null) { - longPress!(); - } - }, - // onLongPressEnd: (details) { - // if (longPressEnd != null) { - // longPressEnd!(); - // } - // }, - child: InkWell( - onTap: () async { - Get.toNamed('/liveRoom?roomid=${liveItem.roomId}', - arguments: {'liveItem': liveItem, 'heroTag': heroTag}); - }, - child: Column( - children: [ - ClipRRect( - borderRadius: const BorderRadius.all(StyleString.imgRadius), - child: AspectRatio( - aspectRatio: StyleString.aspectRatio, - child: LayoutBuilder(builder: (context, boxConstraints) { - double maxWidth = boxConstraints.maxWidth; - double maxHeight = boxConstraints.maxHeight; - return Stack( - children: [ - Hero( - tag: heroTag, - child: NetworkImgLayer( - src: liveItem.cover!, - width: maxWidth, - height: maxHeight, + return InkWell( + onLongPress: () => imageSaveDialog( + context, + liveItem, + SmartDialog.dismiss, + ), + borderRadius: BorderRadius.circular(16), + onTap: () async { + Get.toNamed('/liveRoom?roomid=${liveItem.roomId}', + arguments: {'liveItem': liveItem, 'heroTag': heroTag}); + }, + child: Column( + children: [ + ClipRRect( + borderRadius: const BorderRadius.all(StyleString.imgRadius), + child: AspectRatio( + aspectRatio: StyleString.aspectRatio, + child: LayoutBuilder(builder: (context, boxConstraints) { + double maxWidth = boxConstraints.maxWidth; + double maxHeight = boxConstraints.maxHeight; + return Stack( + children: [ + Hero( + tag: heroTag, + child: NetworkImgLayer( + src: liveItem.cover!, + width: maxWidth, + height: maxHeight, + ), + ), + if (crossAxisCount != 1) + 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, crossAxisCount: crossAxisCount) - ], + ), + ], + ); + }), + ), ), - ), + LiveContent(liveItem: liveItem, crossAxisCount: crossAxisCount) + ], ), ); } diff --git a/lib/pages/live_room/controller.dart b/lib/pages/live_room/controller.dart index 5c2a9800..4e67fa2c 100644 --- a/lib/pages/live_room/controller.dart +++ b/lib/pages/live_room/controller.dart @@ -17,8 +17,7 @@ class LiveRoomController extends GetxController { double volume = 0.0; // 静音状态 RxBool volumeOff = false.obs; - PlPlayerController plPlayerController = - PlPlayerController.getInstance(videoType: 'live'); + PlPlayerController plPlayerController = PlPlayerController(videoType: 'live'); Rx roomInfoH5 = RoomInfoH5Model().obs; late bool enableCDN; late int currentQn; diff --git a/lib/pages/live_room/widgets/bottom_control.dart b/lib/pages/live_room/widgets/bottom_control.dart index e5a9d6c9..4dd7c538 100644 --- a/lib/pages/live_room/widgets/bottom_control.dart +++ b/lib/pages/live_room/widgets/bottom_control.dart @@ -153,7 +153,8 @@ class _BottomControlState extends State { size: 20, color: Colors.white, ), - fuc: () => widget.controller!.triggerFullScreen(), + fuc: () => widget.controller!.triggerFullScreen( + status: !(widget.controller!.isFullScreen.value)), ), ], ), diff --git a/lib/pages/main/controller.dart b/lib/pages/main/controller.dart index a77d9304..2ef2bf7f 100644 --- a/lib/pages/main/controller.dart +++ b/lib/pages/main/controller.dart @@ -40,10 +40,10 @@ class MainController extends GetxController { dynamicBadgeType.value = DynamicBadgeMode.values[setting.get( SettingBoxKey.dynamicBadgeMode, defaultValue: DynamicBadgeMode.number.code)]; + setNavBarConfig(); if (dynamicBadgeType.value != DynamicBadgeMode.hidden) { getUnreadDynamic(); } - setNavBarConfig(); } void onBackPressed(BuildContext context) { diff --git a/lib/pages/main/view.dart b/lib/pages/main/view.dart index 617fcf83..29573501 100644 --- a/lib/pages/main/view.dart +++ b/lib/pages/main/view.dart @@ -138,14 +138,14 @@ class _MainAppState extends State with SingleTickerProviderStateMixin { duration: const Duration(milliseconds: 500), offset: Offset(0, snapshot.data ? 0 : 1), child: GlobalData().enableMYBar - ? NavigationBar( - onDestinationSelected: (value) => setIndex(value), - selectedIndex: _mainController.selectedIndex, - destinations: [ - ..._mainController.navigationBars.map((e) { - return NavigationDestination( - icon: Obx( - () => Badge( + ? Obx( + () => NavigationBar( + onDestinationSelected: (value) => setIndex(value), + selectedIndex: _mainController.selectedIndex, + destinations: [ + ..._mainController.navigationBars.map((e) { + return NavigationDestination( + icon: Badge( label: _mainController .dynamicBadgeType.value == DynamicBadgeMode.number @@ -159,25 +159,25 @@ class _MainAppState extends State with SingleTickerProviderStateMixin { e['count'] > 0, child: e['icon'], ), - ), - selectedIcon: e['selectIcon'], - label: e['label'], - ); - }).toList(), - ], + selectedIcon: e['selectIcon'], + label: e['label'], + ); + }).toList(), + ], + ), ) - : BottomNavigationBar( - currentIndex: _mainController.selectedIndex, - type: BottomNavigationBarType.fixed, - onTap: (value) => setIndex(value), - iconSize: 16, - selectedFontSize: 12, - unselectedFontSize: 12, - items: [ - ..._mainController.navigationBars.map((e) { - return BottomNavigationBarItem( - icon: Obx( - () => Badge( + : Obx( + () => BottomNavigationBar( + currentIndex: _mainController.selectedIndex, + type: BottomNavigationBarType.fixed, + onTap: (value) => setIndex(value), + iconSize: 16, + selectedFontSize: 12, + unselectedFontSize: 12, + items: [ + ..._mainController.navigationBars.map((e) { + return BottomNavigationBarItem( + icon: Badge( label: _mainController .dynamicBadgeType.value == DynamicBadgeMode.number @@ -191,12 +191,12 @@ class _MainAppState extends State with SingleTickerProviderStateMixin { e['count'] > 0, child: e['icon'], ), - ), - activeIcon: e['selectIcon'], - label: e['label'], - ); - }).toList(), - ], + activeIcon: e['selectIcon'], + label: e['label'], + ); + }).toList(), + ], + ), ), ); }, diff --git a/lib/pages/member_seasons/widgets/item.dart b/lib/pages/member_seasons/widgets/item.dart index 4df74b70..85b763b7 100644 --- a/lib/pages/member_seasons/widgets/item.dart +++ b/lib/pages/member_seasons/widgets/item.dart @@ -1,10 +1,12 @@ import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/constants.dart'; import 'package:pilipala/common/widgets/badge.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/stat/view.dart'; import 'package:pilipala/http/search.dart'; +import 'package:pilipala/utils/image_save.dart'; import 'package:pilipala/utils/utils.dart'; class MemberSeasonsItem extends StatelessWidget { @@ -29,6 +31,11 @@ class MemberSeasonsItem extends StatelessWidget { Get.toNamed('/video?bvid=${seasonItem.bvid}&cid=$cid', arguments: {'videoItem': seasonItem, 'heroTag': heroTag}); }, + onLongPress: () => imageSaveDialog( + context, + seasonItem, + SmartDialog.dismiss, + ), child: Column( children: [ AspectRatio( diff --git a/lib/pages/mine/controller.dart b/lib/pages/mine/controller.dart index 5ad9e852..a61bb820 100644 --- a/lib/pages/mine/controller.dart +++ b/lib/pages/mine/controller.dart @@ -6,6 +6,7 @@ import 'package:pilipala/http/user.dart'; import 'package:pilipala/models/common/theme_type.dart'; import 'package:pilipala/models/user/info.dart'; import 'package:pilipala/models/user/stat.dart'; +import 'package:pilipala/utils/route_push.dart'; import 'package:pilipala/utils/storage.dart'; class MineController extends GetxController { @@ -33,14 +34,7 @@ class MineController extends GetxController { onLogin() async { if (!userLogin.value) { - Get.toNamed( - '/webview', - parameters: { - 'url': 'https://passport.bilibili.com/h5-app/passport/login', - 'type': 'login', - 'pageTitle': '登录bilibili', - }, - ); + RoutePush.loginPush(); // Get.toNamed('/loginPage'); } else { int mid = userInfo.value.mid!; diff --git a/lib/pages/rank/zone/view.dart b/lib/pages/rank/zone/view.dart index 04631a8c..72d81f95 100644 --- a/lib/pages/rank/zone/view.dart +++ b/lib/pages/rank/zone/view.dart @@ -3,8 +3,6 @@ import 'dart:async'; import 'package:flutter/material.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'; import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/common/widgets/video_card_h.dart'; @@ -82,15 +80,6 @@ class _ZonePageState extends State return VideoCardH( videoItem: _zoneController.videoList[index], showPubdate: true, - longPress: () { - _zoneController.popupDialog = _createPopupDialog( - _zoneController.videoList[index]); - Overlay.of(context) - .insert(_zoneController.popupDialog!); - }, - longPressEnd: () { - _zoneController.popupDialog?.remove(); - }, ); }, childCount: _zoneController.videoList.length), ), @@ -126,14 +115,4 @@ class _ZonePageState extends State ), ); } - - OverlayEntry _createPopupDialog(videoItem) { - return OverlayEntry( - builder: (context) => AnimatedDialog( - closeFn: _zoneController.popupDialog?.remove, - child: OverlayPop( - videoItem: videoItem, closeFn: _zoneController.popupDialog?.remove), - ), - ); - } } diff --git a/lib/pages/search_panel/widgets/live_panel.dart b/lib/pages/search_panel/widgets/live_panel.dart index 6fb5f5b8..5f797f09 100644 --- a/lib/pages/search_panel/widgets/live_panel.dart +++ b/lib/pages/search_panel/widgets/live_panel.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/constants.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; +import 'package:pilipala/utils/image_save.dart'; import 'package:pilipala/utils/utils.dart'; Widget searchLivePanel(BuildContext context, ctr, list) { @@ -42,15 +44,15 @@ class LiveItem extends StatelessWidget { Get.toNamed('/liveRoom?roomid=${liveItem.roomid}', arguments: {'liveItem': liveItem, 'heroTag': heroTag}); }, + onLongPress: () => imageSaveDialog( + context, + liveItem, + SmartDialog.dismiss, + ), 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) { @@ -108,7 +110,7 @@ class LiveContent extends StatelessWidget { RichText( text: TextSpan( children: [ - for (var i in liveItem.title) ...[ + for (var i in liveItem.titleList) ...[ TextSpan( text: i['text'], style: TextStyle( diff --git a/lib/pages/search_panel/widgets/media_bangumi_panel.dart b/lib/pages/search_panel/widgets/media_bangumi_panel.dart index 18799d3a..5bba0ab8 100644 --- a/lib/pages/search_panel/widgets/media_bangumi_panel.dart +++ b/lib/pages/search_panel/widgets/media_bangumi_panel.dart @@ -7,6 +7,7 @@ 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/route_push.dart'; import 'package:pilipala/utils/utils.dart'; Widget searchMbangumiPanel(BuildContext context, ctr, list) { @@ -63,7 +64,7 @@ Widget searchMbangumiPanel(BuildContext context, ctr, list) { style: TextStyle( color: Theme.of(context).colorScheme.onSurface), children: [ - for (var i in i.title) ...[ + for (var i in i.titleList) ...[ TextSpan( text: i['text'], style: TextStyle( @@ -108,28 +109,8 @@ Widget searchMbangumiPanel(BuildContext context, ctr, list) { SizedBox( height: 32, child: ElevatedButton( - onPressed: () async { - SmartDialog.showLoading(msg: '获取中...'); - var res = await SearchHttp.bangumiInfo( - seasonId: i.seasonId); - SmartDialog.dismiss().then((value) { - if (res['status']) { - EpisodeItem episode = res['data'].episodes.first; - String bvid = episode.bvid!; - int cid = episode.cid!; - String pic = episode.cover!; - String heroTag = Utils.makeHeroTag(cid); - Get.toNamed( - '/video?bvid=$bvid&cid=$cid&seasonId=${i.seasonId}', - arguments: { - 'pic': pic, - 'heroTag': heroTag, - 'videoType': SearchType.media_bangumi, - 'bangumiItem': res['data'], - }, - ); - } - }); + onPressed: () { + RoutePush.bangumiPush(i.seasonId, null); }, child: const Text('观看'), ), diff --git a/lib/pages/search_panel/widgets/video_panel.dart b/lib/pages/search_panel/widgets/video_panel.dart index b96ff004..c24a007c 100644 --- a/lib/pages/search_panel/widgets/video_panel.dart +++ b/lib/pages/search_panel/widgets/video_panel.dart @@ -35,7 +35,11 @@ class SearchVideoPanel extends StatelessWidget { padding: index == 0 ? const EdgeInsets.only(top: 2) : EdgeInsets.zero, - child: VideoCardH(videoItem: i, showPubdate: true), + child: VideoCardH( + videoItem: i, + showPubdate: true, + source: 'search', + ), ); }, ), diff --git a/lib/pages/search_result/controller.dart b/lib/pages/search_result/controller.dart index 9914d82b..02a9e63b 100644 --- a/lib/pages/search_result/controller.dart +++ b/lib/pages/search_result/controller.dart @@ -1,8 +1,11 @@ import 'package:get/get.dart'; +import 'package:pilipala/http/search.dart'; +import 'package:pilipala/models/common/search_type.dart'; class SearchResultController extends GetxController { String? keyword; int tabIndex = 0; + RxList searchTabs = [].obs; @override void onInit() { @@ -10,5 +13,21 @@ class SearchResultController extends GetxController { if (Get.parameters.keys.isNotEmpty) { keyword = Get.parameters['keyword']; } + searchTabs.value = SearchType.values + .map((type) => {'label': type.label, 'id': type.type}) + .toList(); + querySearchCount(); + } + + Future querySearchCount() async { + var result = await SearchHttp.searchCount(keyword: keyword!); + if (result['status']) { + for (var i in searchTabs) { + final count = result['data'].topTList[i['id']]; + i['count'] = count > 99 ? '99+' : count.toString(); + } + searchTabs.refresh(); + } + return result; } } diff --git a/lib/pages/search_result/view.dart b/lib/pages/search_result/view.dart index ff5bf780..96fdd91d 100644 --- a/lib/pages/search_result/view.dart +++ b/lib/pages/search_result/view.dart @@ -13,7 +13,7 @@ class SearchResultPage extends StatefulWidget { class _SearchResultPageState extends State with TickerProviderStateMixin { - late SearchResultController? _searchResultController; + late SearchResultController _searchResultController; late TabController? _tabController; @override @@ -25,7 +25,7 @@ class _SearchResultPageState extends State _tabController = TabController( vsync: this, length: SearchType.values.length, - initialIndex: _searchResultController!.tabIndex, + initialIndex: _searchResultController.tabIndex, ); } @@ -46,7 +46,7 @@ class _SearchResultPageState extends State child: SizedBox( width: double.infinity, child: Text( - '${_searchResultController!.keyword}', + '${_searchResultController.keyword}', style: Theme.of(context).textTheme.titleMedium, ), ), @@ -64,35 +64,39 @@ class _SearchResultPageState extends State splashColor: Colors.transparent, // 点击时的水波纹颜色设置为透明 highlightColor: Colors.transparent, // 点击时的背景高亮颜色设置为透明 ), - child: TabBar( - controller: _tabController, - tabs: [ - for (var i in SearchType.values) Tab(text: i.label), - ], - isScrollable: true, - indicatorWeight: 0, - indicatorPadding: - const EdgeInsets.symmetric(horizontal: 3, vertical: 8), - indicator: BoxDecoration( - color: Theme.of(context).colorScheme.secondaryContainer, - borderRadius: const BorderRadius.all(Radius.circular(20)), - ), - indicatorSize: TabBarIndicatorSize.tab, - labelColor: Theme.of(context).colorScheme.onSecondaryContainer, - labelStyle: const TextStyle(fontSize: 13), - dividerColor: Colors.transparent, - unselectedLabelColor: Theme.of(context).colorScheme.outline, - tabAlignment: TabAlignment.start, - onTap: (index) { - if (index == _searchResultController!.tabIndex) { - Get.find( - tag: SearchType.values[index].type + - _searchResultController!.keyword!) - .animateToTop(); - } + child: Obx( + () => (TabBar( + controller: _tabController, + tabs: [ + for (var i in _searchResultController.searchTabs) + Tab(text: "${i['label']} ${i['count'] ?? ''}") + ], + isScrollable: true, + indicatorWeight: 0, + indicatorPadding: + const EdgeInsets.symmetric(horizontal: 3, vertical: 8), + indicator: BoxDecoration( + color: Theme.of(context).colorScheme.secondaryContainer, + borderRadius: const BorderRadius.all(Radius.circular(20)), + ), + indicatorSize: TabBarIndicatorSize.tab, + labelColor: + Theme.of(context).colorScheme.onSecondaryContainer, + labelStyle: const TextStyle(fontSize: 13), + dividerColor: Colors.transparent, + unselectedLabelColor: Theme.of(context).colorScheme.outline, + tabAlignment: TabAlignment.start, + onTap: (index) { + if (index == _searchResultController.tabIndex) { + Get.find( + tag: SearchType.values[index].type + + _searchResultController.keyword!) + .animateToTop(); + } - _searchResultController!.tabIndex = index; - }, + _searchResultController.tabIndex = index; + }, + )), ), ), ), @@ -102,7 +106,7 @@ class _SearchResultPageState extends State children: [ for (var i in SearchType.values) ...{ SearchPanel( - keyword: _searchResultController!.keyword, + keyword: _searchResultController.keyword, searchType: i, tag: DateTime.now().millisecondsSinceEpoch.toString(), ) diff --git a/lib/pages/subscription/controller.dart b/lib/pages/subscription/controller.dart index 7be8d22c..d8a76d44 100644 --- a/lib/pages/subscription/controller.dart +++ b/lib/pages/subscription/controller.dart @@ -17,10 +17,15 @@ class SubController extends GetxController { int pageSize = 20; RxBool hasMore = true.obs; - Future querySubFolder({type = 'init'}) async { + @override + void onInit() { + super.onInit(); userInfo = userInfoCache.get('userInfoCache'); + } + + Future querySubFolder({type = 'init'}) async { if (userInfo == null) { - return {'status': false, 'msg': '账号未登录'}; + return {'status': false, 'msg': '账号未登录', 'code': -101}; } var res = await UserHttp.userSubFolder( pn: currentPage, diff --git a/lib/pages/subscription/view.dart b/lib/pages/subscription/view.dart index 2d7d0cb5..5e6e4664 100644 --- a/lib/pages/subscription/view.dart +++ b/lib/pages/subscription/view.dart @@ -2,6 +2,7 @@ import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/widgets/http_error.dart'; +import 'package:pilipala/utils/route_push.dart'; import 'controller.dart'; import 'widgets/item.dart'; @@ -68,8 +69,18 @@ class _SubPageState extends State { physics: const NeverScrollableScrollPhysics(), slivers: [ HttpError( - errMsg: data?['msg'], - fn: () => setState(() {}), + errMsg: data?['msg'] ?? '请求异常', + btnText: data?['code'] == -101 ? '去登录' : null, + fn: () { + if (data?['code'] == -101) { + RoutePush.loginRedirectPush(); + } else { + setState(() { + _futureBuilderFuture = + _subController.querySubFolder(); + }); + } + }, ), ], ); diff --git a/lib/pages/subscription/widgets/item.dart b/lib/pages/subscription/widgets/item.dart index 5b2a0134..0d424611 100644 --- a/lib/pages/subscription/widgets/item.dart +++ b/lib/pages/subscription/widgets/item.dart @@ -25,6 +25,7 @@ class SubItem extends StatelessWidget { parameters: { 'heroTag': heroTag, 'seasonId': subFolderItem.id.toString(), + 'type': subFolderItem.type.toString(), }, ), child: Padding( diff --git a/lib/pages/subscription_detail/controller.dart b/lib/pages/subscription_detail/controller.dart index 4245df2c..e69f4be0 100644 --- a/lib/pages/subscription_detail/controller.dart +++ b/lib/pages/subscription_detail/controller.dart @@ -14,13 +14,16 @@ class SubDetailController extends GetxController { RxList subList = [].obs; RxString loadingText = '加载中...'.obs; int mediaCount = 0; + late int channelType; @override void onInit() { item = Get.arguments; - if (Get.parameters.keys.isNotEmpty) { - seasonId = int.parse(Get.parameters['seasonId']!); - heroTag = Get.parameters['heroTag']!; + final parameters = Get.parameters; + if (parameters.isNotEmpty) { + seasonId = int.tryParse(parameters['seasonId'] ?? '') ?? 0; + heroTag = parameters['heroTag'] ?? ''; + channelType = int.tryParse(parameters['type'] ?? '') ?? 0; } super.onInit(); } @@ -31,7 +34,7 @@ class SubDetailController extends GetxController { return; } isLoadingMore = true; - var res = type == 21 + var res = channelType == 21 ? await UserHttp.userSeasonList( seasonId: seasonId, ps: 20, diff --git a/lib/pages/subscription_detail/view.dart b/lib/pages/subscription_detail/view.dart index 63352429..4ca241fe 100644 --- a/lib/pages/subscription_detail/view.dart +++ b/lib/pages/subscription_detail/view.dart @@ -198,8 +198,8 @@ class _SubDetailPageState extends State { future: _futureBuilderFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { - Map data = snapshot.data; - if (data['status']) { + Map? data = snapshot.data; + if (data != null && data['status']) { if (_subDetailController.item.mediaCount == 0) { return const NoData(); } else { @@ -219,7 +219,7 @@ class _SubDetailPageState extends State { } } else { return HttpError( - errMsg: data['msg'], + errMsg: data?['msg'] ?? '请求异常', fn: () => setState(() {}), ); } diff --git a/lib/pages/subscription_detail/widget/sub_video_card.dart b/lib/pages/subscription_detail/widget/sub_video_card.dart index 11aebc39..dcdee4ef 100644 --- a/lib/pages/subscription_detail/widget/sub_video_card.dart +++ b/lib/pages/subscription_detail/widget/sub_video_card.dart @@ -1,3 +1,4 @@ +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:flutter/material.dart'; import 'package:pilipala/common/constants.dart'; @@ -5,6 +6,7 @@ import 'package:pilipala/common/widgets/stat/danmu.dart'; import 'package:pilipala/common/widgets/stat/view.dart'; import 'package:pilipala/http/search.dart'; import 'package:pilipala/models/common/search_type.dart'; +import 'package:pilipala/utils/image_save.dart'; import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; import '../../../common/widgets/badge.dart'; @@ -40,6 +42,11 @@ class SubVideoCardH extends StatelessWidget { 'videoType': SearchType.video, }); }, + onLongPress: () => imageSaveDialog( + context, + videoItem, + SmartDialog.dismiss, + ), child: Column( children: [ Padding( diff --git a/lib/pages/video/detail/controller.dart b/lib/pages/video/detail/controller.dart index 6dd6425f..722af317 100644 --- a/lib/pages/video/detail/controller.dart +++ b/lib/pages/video/detail/controller.dart @@ -74,7 +74,7 @@ class VideoDetailController extends GetxController final scaffoldKey = GlobalKey(); RxString bgCover = ''.obs; RxString cover = ''.obs; - PlPlayerController plPlayerController = PlPlayerController.getInstance(); + PlPlayerController plPlayerController = PlPlayerController(); late VideoItem firstVideo; late AudioItem firstAudio; @@ -547,7 +547,7 @@ class VideoDetailController extends GetxController } void updateCover(String? pic) { - if (pic != null && pic != '') { + if (pic != null) { cover.value = videoItem['pic'] = pic; } } diff --git a/lib/pages/video/detail/introduction/controller.dart b/lib/pages/video/detail/introduction/controller.dart index 2ced79f0..9ed04870 100644 --- a/lib/pages/video/detail/introduction/controller.dart +++ b/lib/pages/video/detail/introduction/controller.dart @@ -450,6 +450,7 @@ class VideoIntroController extends GetxController { videoDetailCtr.danmakuCid.value = cid; videoDetailCtr.cover.value = cover; videoDetailCtr.queryVideoUrl(); + videoDetailCtr.getSubtitle(); // 重新请求评论 try { /// 未渲染回复组件时可能异常 diff --git a/lib/pages/video/detail/introduction/view.dart b/lib/pages/video/detail/introduction/view.dart index 168e6ea7..9103c130 100644 --- a/lib/pages/video/detail/introduction/view.dart +++ b/lib/pages/video/detail/introduction/view.dart @@ -1,4 +1,5 @@ import 'package:expandable/expandable.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:get/get.dart'; @@ -265,6 +266,12 @@ class _VideoInfoState extends State with TickerProviderStateMixin { GestureDetector( behavior: HitTestBehavior.translucent, onTap: () => showIntroDetail(), + onLongPress: () async { + feedBack(); + await Clipboard.setData( + ClipboardData(text: widget.videoDetail!.title!)); + SmartDialog.showToast('标题已复制'); + }, child: ExpandablePanel( controller: _expandableCtr, collapsed: Text( diff --git a/lib/pages/video/detail/introduction/widgets/intro_detail.dart b/lib/pages/video/detail/introduction/widgets/intro_detail.dart index 3ec92023..78c621c0 100644 --- a/lib/pages/video/detail/introduction/widgets/intro_detail.dart +++ b/lib/pages/video/detail/introduction/widgets/intro_detail.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; +import 'package:pilipala/utils/feed_back.dart'; import 'package:pilipala/utils/utils.dart'; class IntroDetail extends StatelessWidget { @@ -16,44 +17,47 @@ class IntroDetail extends StatelessWidget { Widget build(BuildContext context) { return SizedBox( width: double.infinity, - child: SelectableRegion( - focusNode: FocusNode(), - selectionControls: MaterialTextSelectionControls(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 4), - Row( - children: [ - GestureDetector( - onTap: () { - Clipboard.setData(ClipboardData(text: videoDetail!.bvid!)); - SmartDialog.showToast('已复制'); - }, - child: Text( - videoDetail!.bvid!, - style: TextStyle( - fontSize: 13, - color: Theme.of(context).colorScheme.primary), - ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 4), + Row( + children: [ + GestureDetector( + onTap: () { + feedBack(); + Clipboard.setData(ClipboardData(text: videoDetail!.bvid!)); + SmartDialog.showToast('已复制'); + }, + child: Text( + videoDetail!.bvid!, + style: TextStyle( + fontSize: 13, + color: Theme.of(context).colorScheme.primary), ), - const SizedBox(width: 10), - GestureDetector( - onTap: () { - Clipboard.setData(ClipboardData(text: videoDetail!.bvid!)); - SmartDialog.showToast('已复制'); - }, - child: Text( - videoDetail!.aid!.toString(), - style: TextStyle( - fontSize: 13, - color: Theme.of(context).colorScheme.primary), - ), - ) - ], - ), - const SizedBox(height: 4), - Text.rich( + ), + const SizedBox(width: 10), + GestureDetector( + onTap: () { + feedBack(); + Clipboard.setData( + ClipboardData(text: videoDetail!.aid!.toString())); + SmartDialog.showToast('已复制'); + }, + child: Text( + videoDetail!.aid!.toString(), + style: TextStyle( + fontSize: 13, + color: Theme.of(context).colorScheme.primary), + ), + ) + ], + ), + const SizedBox(height: 4), + SelectableRegion( + focusNode: FocusNode(), + selectionControls: MaterialTextSelectionControls(), + child: Text.rich( style: const TextStyle(height: 1.4), TextSpan( children: [ @@ -61,8 +65,8 @@ class IntroDetail extends StatelessWidget { ], ), ), - ], - ), + ), + ], ), ); } diff --git a/lib/pages/video/detail/related/view.dart b/lib/pages/video/detail/related/view.dart index 0912724e..fe3b0dca 100644 --- a/lib/pages/video/detail/related/view.dart +++ b/lib/pages/video/detail/related/view.dart @@ -1,9 +1,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/skeleton/video_card_h.dart'; -import 'package:pilipala/common/widgets/animated_dialog.dart'; import 'package:pilipala/common/widgets/http_error.dart'; -import 'package:pilipala/common/widgets/overlay_pop.dart'; import 'package:pilipala/common/widgets/video_card_h.dart'; import './controller.dart'; @@ -54,20 +52,6 @@ class _RelatedVideoPanelState extends State child: VideoCardH( videoItem: relatedVideoList[index], showPubdate: true, - longPress: () { - try { - _releatedController.popupDialog = - _createPopupDialog(_releatedController - .relatedVideoList[index]); - Overlay.of(context) - .insert(_releatedController.popupDialog!); - } catch (err) { - return {}; - } - }, - longPressEnd: () { - _releatedController.popupDialog?.remove(); - }, ), ); } @@ -89,15 +73,4 @@ class _RelatedVideoPanelState extends State }, ); } - - OverlayEntry _createPopupDialog(videoItem) { - return OverlayEntry( - builder: (BuildContext context) => AnimatedDialog( - closeFn: _releatedController.popupDialog?.remove, - child: OverlayPop( - videoItem: videoItem, - closeFn: _releatedController.popupDialog?.remove), - ), - ); - } } diff --git a/lib/pages/video/detail/reply/widgets/reply_item.dart b/lib/pages/video/detail/reply/widgets/reply_item.dart index 468d2541..4ff13065 100644 --- a/lib/pages/video/detail/reply/widgets/reply_item.dart +++ b/lib/pages/video/detail/reply/widgets/reply_item.dart @@ -12,6 +12,7 @@ import 'package:pilipala/pages/preview/index.dart'; import 'package:pilipala/pages/video/detail/index.dart'; import 'package:pilipala/pages/video/detail/reply_new/index.dart'; import 'package:pilipala/utils/feed_back.dart'; +import 'package:pilipala/utils/id_utils.dart'; import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/url_utils.dart'; import 'package:pilipala/utils/utils.dart'; @@ -44,7 +45,7 @@ class ReplyItem extends StatelessWidget { onTap: () { feedBack(); if (replyReply != null) { - replyReply!(replyItem, null); + replyReply!(replyItem); } }, onLongPress: () { @@ -358,7 +359,7 @@ class ReplyItemRow extends StatelessWidget { InkWell( // 一楼点击评论展开评论详情 // onTap: () { - // replyReply?.call(replyItem, replies![i]); + // replyReply?.call(replyItem); // }, onLongPress: () { feedBack(); @@ -642,23 +643,25 @@ InlineSpan buildContent( '', ); } else { - final String redirectUrl = - await UrlUtils.parseRedirectUrl(matchStr); - if (redirectUrl == matchStr) { - Clipboard.setData(ClipboardData(text: matchStr)); - SmartDialog.showToast('地址可能有误'); - return; - } - final String pathSegment = Uri.parse(redirectUrl).path; - final String lastPathSegment = - pathSegment.split('/').last; - if (lastPathSegment.startsWith('BV')) { + final String pathSegment = Uri.parse(matchStr).path; + Map matchRes = IdUtils.matchAvorBv(input: pathSegment); + List matchKeys = matchRes.keys.toList(); + if (matchKeys.isNotEmpty) { UrlUtils.matchUrlPush( - lastPathSegment, + matchRes.containsKey('AV') + ? matchRes['AV']! as int + : matchRes['BV'], title, - redirectUrl, + matchStr, ); } else { + final String redirectUrl = + await UrlUtils.parseRedirectUrl(matchStr); + // if (redirectUrl == matchStr) { + // Clipboard.setData(ClipboardData(text: matchStr)); + // SmartDialog.showToast('地址可能有误'); + // return; + // } Get.toNamed( '/webview', parameters: { diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index b87350a5..2c35858f 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -323,11 +323,13 @@ class _VideoDetailPageState extends State children: [ GestureDetector( onTap: handlePlay, - child: Image.network( - vdCtr.videoItem['pic'], - width: Get.width, - height: videoHeight, - fit: BoxFit.cover, // 适应方式根据需要调整 + child: Obx( + () => NetworkImgLayer( + src: vdCtr.cover.value, + width: Get.width, + height: videoHeight, + type: 'emote', + ), ), ), buildCustomAppBar(), @@ -535,20 +537,20 @@ class _VideoDetailPageState extends State controller: _extendNestCtr, headerSliverBuilder: (BuildContext context2, bool innerBoxIsScrolled) { - final Orientation orientation = - MediaQuery.of(context).orientation; - final bool isFullScreen = - plPlayerController?.isFullScreen.value == true; - final double expandedHeight = - orientation == Orientation.landscape || isFullScreen - ? (MediaQuery.sizeOf(context).height - - (orientation == Orientation.landscape - ? 0 - : MediaQuery.of(context).padding.top)) - : videoHeight.value; return [ Obx( () { + final Orientation orientation = + MediaQuery.of(context).orientation; + final bool isFullScreen = + plPlayerController?.isFullScreen.value == true; + final double expandedHeight = + orientation == Orientation.landscape || isFullScreen + ? (MediaQuery.sizeOf(context).height - + (orientation == Orientation.landscape + ? 0 + : MediaQuery.of(context).padding.top)) + : videoHeight.value; if (orientation == Orientation.landscape || isFullScreen) { enterFullScreen(); diff --git a/lib/pages/webview/controller.dart b/lib/pages/webview/controller.dart index f26f4284..bdacc652 100644 --- a/lib/pages/webview/controller.dart +++ b/lib/pages/webview/controller.dart @@ -52,15 +52,19 @@ class WebviewController extends GetxController { loadProgress.value = progress; }, onPageStarted: (String url) { - final String str = Uri.parse(url).pathSegments[0]; - final Map matchRes = IdUtils.matchAvorBv(input: str); - final List matchKeys = matchRes.keys.toList(); - if (matchKeys.isNotEmpty) { - if (matchKeys.first == 'BV') { - Get.offAndToNamed( - '/searchResult', - parameters: {'keyword': matchRes['BV']}, - ); + final List pathSegments = Uri.parse(url).pathSegments; + if (pathSegments.isNotEmpty && + url != 'https://passport.bilibili.com/h5-app/passport/login') { + final String str = pathSegments[0]; + final Map matchRes = IdUtils.matchAvorBv(input: str); + final List matchKeys = matchRes.keys.toList(); + if (matchKeys.isNotEmpty) { + if (matchKeys.first == 'BV') { + Get.offAndToNamed( + '/searchResult', + parameters: {'keyword': matchRes['BV']}, + ); + } } } }, @@ -106,6 +110,9 @@ class WebviewController extends GetxController { SmartDialog.showToast('登录成功'); try { Box userInfoCache = GStrorage.userInfo; + if (!userInfoCache.isOpen) { + userInfoCache = await Hive.openBox('userInfo'); + } await userInfoCache.put('userInfoCache', result['data']); final HomeController homeCtr = Get.find(); diff --git a/lib/pages/whisper_detail/widget/chat_item.dart b/lib/pages/whisper_detail/widget/chat_item.dart index 4fd49254..ad11e4c3 100644 --- a/lib/pages/whisper_detail/widget/chat_item.dart +++ b/lib/pages/whisper_detail/widget/chat_item.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; +import 'package:pilipala/utils/route_push.dart'; import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/storage.dart'; @@ -154,16 +155,33 @@ class ChatItem extends StatelessWidget { GestureDetector( onTap: () async { SmartDialog.showLoading(); - var bvid = content["bvid"]; + final String bvid = content["bvid"]; + // 16番剧 5投稿 + final int source = content["source"]; + final String? url = content["url"]; + final int cid = await SearchHttp.ab2c(bvid: bvid); final String heroTag = Utils.makeHeroTag(bvid); - SmartDialog.dismiss().then( - (e) => Get.toNamed('/video?bvid=$bvid&cid=$cid', - arguments: { - 'pic': content['thumb'], - 'heroTag': heroTag, - }), - ); + await SmartDialog.dismiss(); + if (source == 5) { + Get.toNamed( + '/video?bvid=$bvid&cid=$cid', + arguments: { + 'pic': content['thumb'], + 'heroTag': heroTag, + }, + ); + } + if (source == 16) { + if (url != null) { + final String area = url.split('/').last; + if (area.startsWith('ep')) { + RoutePush.bangumiPush(null, Utils.matchNum(area).first); + } else if (area.startsWith('ss')) { + RoutePush.bangumiPush(Utils.matchNum(area).first, null); + } + } + } }, child: NetworkImgLayer( width: 220, @@ -183,7 +201,7 @@ class ChatItem extends StatelessWidget { ), const SizedBox(height: 1), Text( - content['author'], + content['author'] ?? '', style: TextStyle( letterSpacing: 0.6, height: 1.5, @@ -206,7 +224,7 @@ class ChatItem extends StatelessWidget { SmartDialog.dismiss().then( (e) => Get.toNamed('/video?bvid=$bvid&cid=$cid', arguments: { - 'pic': content['thumb'], + 'pic': content['thumb'] ?? '', 'heroTag': heroTag, }), ); diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index 8ee6faa0..03c2efbe 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -123,6 +123,7 @@ class PlPlayerController { PreferredSizeWidget? bottomControl; Widget? danmuWidget; late RxList subtitles; + String videoType = 'archive'; /// 数据加载监听 Stream get onDataStatusChanged => dataStatus.status.stream; @@ -220,7 +221,7 @@ class PlPlayerController { Rx get playerCount => _playerCount; /// - Rx get videoType => _videoType; + // Rx get videoType => _videoType; /// 弹幕开关 Rx isOpenDanmu = false.obs; @@ -274,8 +275,7 @@ class PlPlayerController { } // 添加一个私有构造函数 - PlPlayerController._() { - _videoType = videoType; + PlPlayerController._internal(this.videoType) { isOpenDanmu.value = setting.get(SettingBoxKey.enableShowDanmaku, defaultValue: false); blockTypes = @@ -330,11 +330,11 @@ class PlPlayerController { } // 获取实例 传参 - static PlPlayerController getInstance({ + factory PlPlayerController({ String videoType = 'archive', }) { // 如果实例尚未创建,则创建一个新实例 - _instance ??= PlPlayerController._(); + _instance ??= PlPlayerController._internal(videoType); if (videoType != 'none') { _instance!._playerCount.value += 1; _videoType.value = videoType; @@ -395,7 +395,7 @@ class PlPlayerController { } // 配置Player 音轨、字幕等等 _videoPlayerController = await _createVideoController( - dataSource, _looping, enableHA, width, height); + dataSource, _looping, enableHA, width, height, seekTo); // 获取视频时长 00:00 _duration.value = duration ?? _videoPlayerController!.state.duration; updateDurationSecond(); @@ -406,7 +406,7 @@ class PlPlayerController { if (!_listenersInitialized) { startListeners(); } - await _initializePlayer(seekTo: seekTo, duration: _duration.value); + await _initializePlayer(duration: _duration.value); bool autoEnterFullcreen = setting.get(SettingBoxKey.enableAutoEnter, defaultValue: false); if (autoEnterFullcreen && _isFirstTime) { @@ -426,6 +426,7 @@ class PlPlayerController { bool enableHA, double? width, double? height, + Duration seekTo, ) async { // 每次配置时先移除监听 removeListeners(); @@ -442,7 +443,7 @@ class PlPlayerController { configuration: PlayerConfiguration( // 默认缓存 5M 大小 bufferSize: - videoType.value == 'live' ? 32 * 1024 * 1024 : 5 * 1024 * 1024, + videoType == 'live' ? 32 * 1024 * 1024 : 5 * 1024 * 1024, ), ); @@ -507,8 +508,9 @@ class PlPlayerController { play: false, ); } - player.open( - Media(dataSource.videoSource!, httpHeaders: dataSource.httpHeaders), + await player.open( + Media(dataSource.videoSource!, + httpHeaders: dataSource.httpHeaders, start: seekTo), play: false, ); // 音轨 @@ -521,7 +523,6 @@ class PlPlayerController { // 开始播放 Future _initializePlayer({ - Duration seekTo = Duration.zero, Duration? duration, }) async { getVideoFit(); @@ -530,9 +531,9 @@ class PlPlayerController { // } /// 跳转播放 - if (seekTo != Duration.zero) { - await this.seekTo(seekTo); - } + // if (seekTo != Duration.zero) { + // await this.seekTo(seekTo); + // } /// 自动播放 if (_autoPlay) { @@ -540,7 +541,7 @@ class PlPlayerController { } /// 设置倍速 - if (videoType.value == 'live') { + if (videoType == 'live') { await setPlaybackSpeed(1.0); } else { if (_playbackSpeed.value != 1.0) { @@ -932,7 +933,7 @@ class PlPlayerController { /// 设置长按倍速状态 live模式下禁用 void setDoubleSpeedStatus(bool val) { - if (videoType.value == 'live') { + if (videoType == 'live') { return; } if (controlsLock.value) { @@ -976,41 +977,8 @@ class PlPlayerController { } else { await landScape(); } - - // bool isValid = - // direction.value == 'vertical' || mode == FullScreenMode.vertical - // ? true - // : false; - // var result = await showDialog( - // context: Get.context!, - // useSafeArea: false, - // builder: (context) => Dialog.fullscreen( - // backgroundColor: Colors.black, - // child: SafeArea( - // // 忽略手机安全区域 - // top: isValid, - // left: false, - // right: false, - // bottom: isValid, - // child: PLVideoPlayer( - // controller: this, - // headerControl: headerControl, - // bottomControl: bottomControl, - // danmuWidget: danmuWidget, - // ), - // ), - // ), - // ); - // if (result == null) { - // // 退出全屏 - // StatusBarControl.setHidden(false, animation: StatusBarAnimation.FADE); - // exitFullScreen(); - // await verticalScreen(); - // toggleFullScreen(false); - // } - } else if (isFullScreen.value) { + } else if (isFullScreen.value && !status) { StatusBarControl.setHidden(false, animation: StatusBarAnimation.FADE); - // Get.back(); exitFullScreen(); await verticalScreen(); toggleFullScreen(false); @@ -1047,7 +1015,7 @@ class PlPlayerController { if (!_enableHeart) { return false; } - if (videoType.value == 'live') { + if (videoType == 'live') { return; } // 播放状态变化时,更新 @@ -1146,7 +1114,6 @@ class PlPlayerController { // _buffered.close(); // _showControls.close(); // _controlsLock.close(); - // playerStatus.status.close(); // dataStatus.status.close(); diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index 6a5f22ec..cff4611a 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -334,7 +334,7 @@ class _PLVideoPlayerState extends State color: Colors.white, ), ), - fuc: () => _.triggerFullScreen(), + fuc: () => _.triggerFullScreen(status: !_.isFullScreen.value), ), }; final List list = []; @@ -652,7 +652,7 @@ class _PLVideoPlayerState extends State }, onDoubleTapDown: (TapDownDetails details) { // live模式下禁用 锁定时🔒禁用 - if (_.videoType.value == 'live' || _.controlsLock.value) { + if (_.videoType == 'live' || _.controlsLock.value) { return; } final double totalWidth = MediaQuery.sizeOf(context).width; @@ -679,7 +679,7 @@ class _PLVideoPlayerState extends State /// 水平位置 快进 live模式下禁用 onHorizontalDragUpdate: (DragUpdateDetails details) { // live模式下禁用 锁定时🔒禁用 - if (_.videoType.value == 'live' || _.controlsLock.value) { + if (_.videoType == 'live' || _.controlsLock.value) { return; } // final double tapPosition = details.localPosition.dx; @@ -695,7 +695,7 @@ class _PLVideoPlayerState extends State _.onChangedSliderStart(); }, onHorizontalDragEnd: (DragEndDetails details) { - if (_.videoType.value == 'live' || _.controlsLock.value) { + if (_.videoType == 'live' || _.controlsLock.value) { return; } _.onChangedSliderEnd(); @@ -826,7 +826,7 @@ class _PLVideoPlayerState extends State return const SizedBox(); } - if (_.videoType.value == 'live') { + if (_.videoType == 'live') { return const SizedBox(); } if (value > max || max <= 0) { @@ -879,7 +879,7 @@ class _PLVideoPlayerState extends State // 锁 Obx( () => Visibility( - visible: _.videoType.value != 'live' && _.isFullScreen.value, + visible: _.videoType != 'live' && _.isFullScreen.value, child: Align( alignment: Alignment.centerLeft, child: FractionalTranslation( diff --git a/lib/services/audio_handler.dart b/lib/services/audio_handler.dart index b0ca8cd7..853c58d0 100644 --- a/lib/services/audio_handler.dart +++ b/lib/services/audio_handler.dart @@ -26,7 +26,7 @@ class VideoPlayerServiceHandler extends BaseAudioHandler with SeekHandler { static final List _item = []; Box setting = GStrorage.setting; bool enableBackgroundPlay = false; - PlPlayerController player = PlPlayerController.getInstance(videoType: 'none'); + PlPlayerController player = PlPlayerController(); VideoPlayerServiceHandler() { revalidateSetting(); diff --git a/lib/services/audio_session.dart b/lib/services/audio_session.dart index 53b497ae..d1d2a466 100644 --- a/lib/services/audio_session.dart +++ b/lib/services/audio_session.dart @@ -18,7 +18,7 @@ class AudioSessionHandler { session.configure(const AudioSessionConfiguration.music()); session.interruptionEventStream.listen((event) { - final player = PlPlayerController.getInstance(videoType: 'none'); + final player = PlPlayerController(videoType: 'none'); if (event.begin) { if (!player.playerStatus.playing) return; switch (event.type) { @@ -51,7 +51,7 @@ class AudioSessionHandler { // 耳机拔出暂停 session.becomingNoisyEventStream.listen((_) { - final player = PlPlayerController.getInstance(videoType: 'none'); + final player = PlPlayerController(videoType: 'none'); if (player.playerStatus.playing) { player.pause(); } diff --git a/lib/services/shutdown_timer_service.dart b/lib/services/shutdown_timer_service.dart index 156b63c8..06993f62 100644 --- a/lib/services/shutdown_timer_service.dart +++ b/lib/services/shutdown_timer_service.dart @@ -89,7 +89,7 @@ class ShutdownTimerService { return; } PlPlayerController plPlayerController = - PlPlayerController.getInstance(videoType: 'none'); + PlPlayerController(videoType: 'none'); if (!exitApp && !waitForPlayingCompleted) { if (!plPlayerController.playerStatus.playing) { //仅提示用户 @@ -124,7 +124,7 @@ class ShutdownTimerService { } else { //暂停播放 PlPlayerController plPlayerController = - PlPlayerController.getInstance(videoType: 'none'); + PlPlayerController(videoType: 'none'); if (plPlayerController.playerStatus.playing) { plPlayerController.pause(); waitForPlayingCompleted = true; diff --git a/lib/utils/app_scheme.dart b/lib/utils/app_scheme.dart index 69fa717d..8502dc7f 100644 --- a/lib/utils/app_scheme.dart +++ b/lib/utils/app_scheme.dart @@ -2,8 +2,8 @@ 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/utils/route_push.dart'; import '../http/search.dart'; -import '../models/common/search_type.dart'; import 'id_utils.dart'; import 'url_utils.dart'; import 'utils.dart'; @@ -68,7 +68,7 @@ class PiliSchame { } else if (host == 'bangumi') { if (path.startsWith('/season')) { final String seasonId = path.split('/').last; - _bangumiPush(int.parse(seasonId), null); + RoutePush.bangumiPush(int.parse(seasonId), null); } } else if (host == 'opus') { if (path.startsWith('/detail')) { @@ -126,35 +126,6 @@ class PiliSchame { } } - // 番剧跳转 - static Future _bangumiPush(int? seasonId, int? epId) async { - SmartDialog.showLoading(msg: '获取中...'); - try { - var result = await SearchHttp.bangumiInfo(seasonId: seasonId, epId: epId); - if (result['status']) { - var bangumiDetail = result['data']; - final int cid = bangumiDetail.episodes!.first.cid; - final String bvid = IdUtils.av2bv(bangumiDetail.episodes!.first.aid); - final 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, - }, - ), - ); - } else { - SmartDialog.showToast(result['msg']); - } - } catch (e) { - SmartDialog.showToast('番剧获取失败:$e'); - } - } - static Future _fullPathPush(SchemeEntity value) async { // https://m.bilibili.com/bangumi/play/ss39708 // https | m.bilibili.com | /bangumi/play/ss39708 @@ -167,8 +138,23 @@ class PiliSchame { print('bilibili.com host: $host'); print('bilibili.com path: $path'); final String lastPathSegment = path!.split('/').last; - if (lastPathSegment.contains('BV')) { - _videoPush(null, lastPathSegment); + if (path.startsWith('/video')) { + Map matchRes = IdUtils.matchAvorBv(input: path); + if (matchRes.containsKey('AV')) { + _videoPush(matchRes['AV']! as int, null); + } else if (matchRes.containsKey('BV')) { + _videoPush(null, matchRes['BV'] as String); + } else { + SmartDialog.showToast('投稿匹配失败'); + } + } + if (path.startsWith('/bangumi')) { + if (lastPathSegment.contains('ss')) { + RoutePush.bangumiPush(Utils.matchNum(lastPathSegment).first, null); + } + if (lastPathSegment.contains('ep')) { + RoutePush.bangumiPush(null, Utils.matchNum(lastPathSegment).first); + } } } else if (host.contains('live')) { int roomId = int.parse(path!.split('/').last); @@ -220,9 +206,9 @@ class PiliSchame { case 'bangumi': print('番剧'); if (area.startsWith('ep')) { - _bangumiPush(null, matchNum(area).first); + RoutePush.bangumiPush(null, Utils.matchNum(area).first); } else if (area.startsWith('ss')) { - _bangumiPush(matchNum(area).first, null); + RoutePush.bangumiPush(Utils.matchNum(area).first, null); } break; case 'video': @@ -238,7 +224,7 @@ class PiliSchame { break; case 'read': print('专栏'); - String id = 'cv${matchNum(query!['id']!).first}'; + String id = 'cv${Utils.matchNum(query!['id']!).first}'; Get.toNamed('/htmlRender', parameters: { 'url': value.dataString!, 'title': '', @@ -254,21 +240,14 @@ class PiliSchame { } } - static List matchNum(String str) { - final RegExp regExp = RegExp(r'\d+'); - final Iterable matches = regExp.allMatches(str); - - return matches.map((Match match) => int.parse(match.group(0)!)).toList(); - } - static void _handleEpisodePath(String lastPathSegment, String redirectUrl) { final String seasonId = _extractIdFromPath(lastPathSegment); - _bangumiPush(null, matchNum(seasonId).first); + RoutePush.bangumiPush(null, Utils.matchNum(seasonId).first); } static void _handleSeasonPath(String lastPathSegment, String redirectUrl) { final String seasonId = _extractIdFromPath(lastPathSegment); - _bangumiPush(matchNum(seasonId).first, null); + RoutePush.bangumiPush(Utils.matchNum(seasonId).first, null); } static String _extractIdFromPath(String lastPathSegment) { diff --git a/lib/utils/download.dart b/lib/utils/download.dart index 2aff8999..42dbbecf 100644 --- a/lib/utils/download.dart +++ b/lib/utils/download.dart @@ -15,24 +15,7 @@ class DownloadUtils { PermissionStatus status = await Permission.storage.status; if (status == PermissionStatus.denied || status == PermissionStatus.permanentlyDenied) { - SmartDialog.show( - useSystem: true, - animationType: SmartAnimationType.centerFade_otherSlide, - builder: (BuildContext context) { - return AlertDialog( - title: const Text('提示'), - content: const Text('存储权限未授权'), - actions: [ - TextButton( - onPressed: () async { - openAppSettings(); - }, - child: const Text('去授权'), - ) - ], - ); - }, - ); + await permissionDialog('提示', '存储权限未授权'); return false; } else { return true; @@ -45,24 +28,7 @@ class DownloadUtils { PermissionStatus status = await Permission.photos.status; if (status == PermissionStatus.denied || status == PermissionStatus.permanentlyDenied) { - SmartDialog.show( - useSystem: true, - animationType: SmartAnimationType.centerFade_otherSlide, - builder: (BuildContext context) { - return AlertDialog( - title: const Text('提示'), - content: const Text('相册权限未授权'), - actions: [ - TextButton( - onPressed: () async { - openAppSettings(); - }, - child: const Text('去授权'), - ) - ], - ); - }, - ); + await permissionDialog('提示', '相册权限未授权'); return false; } else { return true; @@ -72,17 +38,16 @@ class DownloadUtils { static Future downloadImg(String imgUrl, {String imgType = 'cover'}) async { try { - if (!Platform.isAndroid || !await requestPhotoPer()) { - return false; - } - final androidInfo = await DeviceInfoPlugin().androidInfo; - if (androidInfo.version.sdkInt <= 32) { - if (!await requestStoragePer()) { - return false; - } - } else { - if (!await requestPhotoPer()) { - return false; + if (Platform.isAndroid) { + final androidInfo = await DeviceInfoPlugin().androidInfo; + if (androidInfo.version.sdkInt <= 32) { + if (!await requestStoragePer()) { + return false; + } + } else { + if (!await requestPhotoPer()) { + return false; + } } } @@ -101,13 +66,38 @@ class DownloadUtils { ); SmartDialog.dismiss(); if (result.isSuccess) { - await SmartDialog.showToast('「${'$picName.$imgSuffix'}」已保存 '); + SmartDialog.showToast('「${'$picName.$imgSuffix'}」已保存 '); + return true; + } else { + await permissionDialog('保存失败', '相册权限未授权'); + return false; } - return true; } catch (err) { SmartDialog.dismiss(); SmartDialog.showToast(err.toString()); - return true; + return false; } } + + static Future permissionDialog(String title, String content, + {Function? onGranted}) async { + await SmartDialog.show( + useSystem: true, + animationType: SmartAnimationType.centerFade_otherSlide, + builder: (BuildContext context) { + return AlertDialog( + title: Text(title), + content: Text(content), + actions: [ + TextButton( + onPressed: () async { + openAppSettings(); + }, + child: const Text('去授权'), + ) + ], + ); + }, + ); + } } diff --git a/lib/utils/image_save.dart b/lib/utils/image_save.dart new file mode 100644 index 00000000..0cd6915c --- /dev/null +++ b/lib/utils/image_save.dart @@ -0,0 +1,88 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:pilipala/common/constants.dart'; +import 'package:pilipala/common/widgets/network_img_layer.dart'; +import 'package:pilipala/utils/download.dart'; + +Future imageSaveDialog(context, videoItem, closeFn) { + final double imgWidth = + MediaQuery.sizeOf(context).width - StyleString.safeSpace * 2; + return SmartDialog.show( + animationType: SmartAnimationType.centerScale_otherSlide, + builder: (context) => Container( + margin: const EdgeInsets.symmetric(horizontal: StyleString.safeSpace), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.background, + borderRadius: BorderRadius.circular(10.0), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Stack( + children: [ + NetworkImgLayer( + width: imgWidth, + height: imgWidth / StyleString.aspectRatio, + src: videoItem.pic! as String, + quality: 100, + ), + Positioned( + right: 8, + top: 8, + child: Container( + width: 30, + height: 30, + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.3), + borderRadius: + const BorderRadius.all(Radius.circular(20))), + child: IconButton( + style: ButtonStyle( + padding: MaterialStateProperty.all(EdgeInsets.zero), + ), + onPressed: () => closeFn!(), + icon: const Icon( + Icons.close, + size: 18, + color: Colors.white, + ), + ), + ), + ), + ], + ), + Padding( + padding: const EdgeInsets.fromLTRB(12, 10, 8, 10), + child: Row( + children: [ + Expanded( + child: Text( + videoItem.title! as String, + style: Theme.of(context).textTheme.titleSmall, + ), + ), + const SizedBox(width: 4), + IconButton( + tooltip: '保存封面图', + onPressed: () async { + bool saveStatus = await DownloadUtils.downloadImg( + videoItem.pic != null + ? videoItem.pic as String + : videoItem.cover as String, + ); + // 保存成功,自动关闭弹窗 + if (saveStatus) { + closeFn?.call(); + } + }, + icon: const Icon(Icons.download, size: 20), + ) + ], + ), + ), + ], + ), + ), + ); +} diff --git a/lib/utils/route_push.dart b/lib/utils/route_push.dart new file mode 100644 index 00000000..9ee28846 --- /dev/null +++ b/lib/utils/route_push.dart @@ -0,0 +1,68 @@ +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:get/get.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'; + +class RoutePush { + // 番剧跳转 + static Future bangumiPush(int? seasonId, int? epId, + {String? heroTag}) async { + SmartDialog.showLoading(msg: '获取中...'); + try { + var result = await SearchHttp.bangumiInfo(seasonId: seasonId, epId: epId); + await SmartDialog.dismiss(); + if (result['status']) { + if (result['data'].episodes.isEmpty) { + SmartDialog.showToast('资源获取失败'); + return; + } + final BangumiInfoModel bangumiDetail = result['data']; + final EpisodeItem episode = bangumiDetail.episodes!.first; + final int epId = episode.id!; + final int cid = episode.cid!; + final String bvid = episode.bvid!; + final String cover = episode.cover!; + final Map arguments = { + 'pic': cover, + 'videoType': SearchType.media_bangumi, + // 'bangumiItem': bangumiDetail, + }; + arguments['heroTag'] = heroTag ?? Utils.makeHeroTag(cid); + Get.toNamed( + '/video?bvid=$bvid&cid=$cid&epId=$epId', + arguments: arguments, + ); + } else { + SmartDialog.showToast(result['msg']); + } + } catch (e) { + SmartDialog.showToast('番剧获取失败:$e'); + } + } + + // 登录跳转 + static Future loginPush() async { + await Get.toNamed( + '/webview', + parameters: { + 'url': 'https://passport.bilibili.com/h5-app/passport/login', + 'type': 'login', + 'pageTitle': '登录bilibili', + }, + ); + } + + // 登录跳转 + static Future loginRedirectPush() async { + await Get.offAndToNamed( + '/webview', + parameters: { + 'url': 'https://passport.bilibili.com/h5-app/passport/login', + 'type': 'login', + 'pageTitle': '登录bilibili', + }, + ); + } +} diff --git a/lib/utils/url_utils.dart b/lib/utils/url_utils.dart index cf0ef9e2..99886e09 100644 --- a/lib/utils/url_utils.dart +++ b/lib/utils/url_utils.dart @@ -16,7 +16,7 @@ class UrlUtils { }; try { final response = await dio.get(url); - if (response.statusCode == 302) { + if (response.statusCode == 302 || response.statusCode == 301) { redirectUrl = response.headers['location']?.first as String; if (redirectUrl.endsWith('/')) { redirectUrl = redirectUrl.substring(0, redirectUrl.length - 1); @@ -42,12 +42,14 @@ class UrlUtils { final Map matchRes = IdUtils.matchAvorBv(input: pathSegment); if (matchRes.containsKey('BV')) { final String bv = matchRes['BV']; - final int cid = await SearchHttp.ab2c(bvid: bv); + final Map res = await SearchHttp.ab2cWithPic(bvid: bv); + final int cid = res['cid']; + final String pic = res['pic']; final String heroTag = Utils.makeHeroTag(bv); await Get.toNamed( '/video?bvid=$bv&cid=$cid', arguments: { - 'pic': '', + 'pic': pic, 'heroTag': heroTag, }, ); diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index b40e9bfe..e50c295c 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -383,4 +383,11 @@ class Utils { List randomBytes = generateRandomBytes(minLength, maxLength); return base64.encode(randomBytes); } + + static List matchNum(String str) { + final RegExp regExp = RegExp(r'\d+'); + final Iterable matches = regExp.allMatches(str); + + return matches.map((Match match) => int.parse(match.group(0)!)).toList(); + } } diff --git a/pubspec.lock b/pubspec.lock index a64c85c0..8f8a5cae 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -69,10 +69,10 @@ packages: dependency: "direct main" description: name: audio_service - sha256: a4d989f1225ea9621898d60f23236dcbfc04876fa316086c23c5c4af075dbac4 + sha256: "4547c312a94f9cb2c48b60823fb190767cbd63454a83c73049384d5d3cba4650" url: "https://pub.flutter-io.cn" source: hosted - version: "0.18.12" + version: "0.18.13" audio_service_platform_interface: dependency: transitive description: @@ -85,10 +85,10 @@ packages: dependency: transitive description: name: audio_service_web - sha256: "523e64ddc914c714d53eec2da85bba1074f08cf26c786d4efb322de510815ea7" + sha256: "9d7d5ae5f98a5727f2580fad73062f2484f400eef6cef42919413268e62a363e" url: "https://pub.flutter-io.cn" source: hosted - version: "0.1.1" + version: "0.1.2" audio_session: dependency: "direct main" description: @@ -873,10 +873,11 @@ packages: media_kit: dependency: "direct main" description: - name: media_kit - sha256: "3289062540e3b8b9746e5c50d95bd78a9289826b7227e253dff806d002b9e67a" - url: "https://pub.flutter-io.cn" - source: hosted + path: media_kit + ref: HEAD + resolved-ref: "285f7919bbf4a7d89a62615b14a3766a171ad575" + url: "https://github.com/media-kit/media-kit" + source: git version: "1.1.10+1" media_kit_libs_android_video: dependency: transitive @@ -913,10 +914,11 @@ packages: media_kit_libs_video: dependency: "direct main" description: - name: media_kit_libs_video - sha256: "3688e0c31482074578652bf038ce6301a5d21e1eda6b54fc3117ffeb4bdba067" - url: "https://pub.flutter-io.cn" - source: hosted + path: "libs/universal/media_kit_libs_video" + ref: HEAD + resolved-ref: "285f7919bbf4a7d89a62615b14a3766a171ad575" + url: "https://github.com/media-kit/media-kit" + source: git version: "1.0.4" media_kit_libs_windows_video: dependency: transitive @@ -937,10 +939,11 @@ packages: media_kit_video: dependency: "direct main" description: - name: media_kit_video - sha256: c048d11a19e379aebbe810647636e3fc6d18374637e2ae12def4ff8a4b99a882 - url: "https://pub.flutter-io.cn" - source: hosted + path: media_kit_video + ref: HEAD + resolved-ref: "285f7919bbf4a7d89a62615b14a3766a171ad575" + url: "https://github.com/media-kit/media-kit" + source: git version: "1.2.4" meta: dependency: transitive diff --git a/pubspec.yaml b/pubspec.yaml index 27b8b720..a269cd6f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.0.21+1021 +version: 1.0.23+1023 environment: sdk: ">=3.0.0 <4.0.0" @@ -90,8 +90,8 @@ dependencies: media_kit_libs_video: ^1.0.4 # 媒体通知 - audio_service: ^0.18.12 - audio_session: ^0.1.16 + audio_service: ^0.18.13 + audio_session: ^0.1.18 # 音量、亮度、屏幕控制 flutter_volume_controller: ^1.3.1 @@ -165,6 +165,20 @@ dev_dependencies: hive_generator: ^2.0.0 build_runner: ^2.4.8 +dependency_overrides: + media_kit: + git: + url: https://github.com/media-kit/media-kit + path: media_kit + media_kit_video: + git: + url: https://github.com/media-kit/media-kit + path: media_kit_video + media_kit_libs_video: + git: + url: https://github.com/media-kit/media-kit + path: libs/universal/media_kit_libs_video + flutter_launcher_icons: android: true ios: true