diff --git a/lib/pages/dynamics/controller.dart b/lib/pages/dynamics/controller.dart index 9ff9f9145..e849295ce 100644 --- a/lib/pages/dynamics/controller.dart +++ b/lib/pages/dynamics/controller.dart @@ -2,20 +2,13 @@ import 'package:PiliPlus/http/follow.dart'; import 'package:PiliPlus/pages/dynamics/tab/controller.dart'; import 'package:PiliPlus/pages/dynamics/tab/view.dart'; import 'package:PiliPlus/utils/extension.dart'; -import 'package:PiliPlus/utils/url_utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:PiliPlus/http/dynamics.dart'; -import 'package:PiliPlus/http/search.dart'; import 'package:PiliPlus/models/common/dynamics_type.dart'; -import 'package:PiliPlus/models/dynamics/result.dart'; import 'package:PiliPlus/models/dynamics/up.dart'; -import 'package:PiliPlus/models/live/item.dart'; -import 'package:PiliPlus/utils/feed_back.dart'; -import 'package:PiliPlus/utils/id_utils.dart'; import 'package:PiliPlus/utils/storage.dart'; -import 'package:PiliPlus/utils/utils.dart'; import '../../models/follow/result.dart'; @@ -68,165 +61,6 @@ class DynamicsController extends GetxController initialValue.value = value; } - pushDetail(item, floor, {action = 'all'}) async { - feedBack(); - - /// 点击评论action 直接查看评论 - if (action == 'comment') { - Utils.toDupNamed('/dynamicDetail', - arguments: {'item': item, 'floor': floor, 'action': action}); - return false; - } - switch (item!.type) { - /// 转发的动态 - case 'DYNAMIC_TYPE_FORWARD': - Utils.toDupNamed('/dynamicDetail', - arguments: {'item': item, 'floor': floor}); - break; - - /// 图文动态查看 - case 'DYNAMIC_TYPE_DRAW': - Utils.toDupNamed('/dynamicDetail', - arguments: {'item': item, 'floor': floor}); - break; - case 'DYNAMIC_TYPE_AV': - if (item.modules.moduleDynamic.major.archive.type == 2) { - if (item.modules.moduleDynamic.major.archive.jumpUrl - .startsWith('//')) { - item.modules.moduleDynamic.major.archive.jumpUrl = - 'https:${item.modules.moduleDynamic.major.archive.jumpUrl}'; - } - String? redirectUrl = await UrlUtils.parseRedirectUrl( - item.modules.moduleDynamic.major.archive.jumpUrl, false); - if (redirectUrl != null) { - Utils.viewPgcFromUri(redirectUrl); - return; - } - } - - String bvid = item.modules.moduleDynamic.major.archive.bvid; - String cover = item.modules.moduleDynamic.major.archive.cover; - try { - int cid = await SearchHttp.ab2c(bvid: bvid); - Utils.toDupNamed( - '/video?bvid=$bvid&cid=$cid', - arguments: { - 'pic': cover, - 'heroTag': Utils.makeHeroTag(bvid), - }, - ); - } catch (err) { - SmartDialog.showToast(err.toString()); - } - break; - - /// 专栏文章查看 - case 'DYNAMIC_TYPE_ARTICLE': - String title = item.modules.moduleDynamic.major.opus.title; - String url = item.modules.moduleDynamic.major.opus.jumpUrl; - if (url.contains('opus') || url.contains('read')) { - RegExp digitRegExp = RegExp(r'\d+'); - Iterable matches = digitRegExp.allMatches(url); - String number = matches.first.group(0)!; - if (url.contains('read')) { - number = 'cv$number'; - } - Utils.toDupNamed('/htmlRender', parameters: { - 'url': url.startsWith('//') ? url.split('//').last : url, - 'title': title, - 'id': number, - 'dynamicType': url.split('//').last.split('/')[1] - }); - } else { - Utils.handleWebview('https:$url'); - } - - break; - case 'DYNAMIC_TYPE_PGC': - debugPrint('番剧'); - SmartDialog.showToast('暂未支持的类型,请联系开发者'); - break; - - /// 纯文字动态查看 - case 'DYNAMIC_TYPE_WORD': - debugPrint('纯文本'); - Utils.toDupNamed('/dynamicDetail', - arguments: {'item': item, 'floor': floor}); - break; - case 'DYNAMIC_TYPE_LIVE_RCMD': - DynamicLiveModel liveRcmd = item.modules.moduleDynamic.major.liveRcmd; - ModuleAuthorModel author = item.modules.moduleAuthor; - LiveItemModel liveItem = LiveItemModel.fromJson({ - 'title': liveRcmd.title, - 'uname': author.name, - 'cover': liveRcmd.cover, - 'mid': author.mid, - 'face': author.face, - 'roomid': liveRcmd.roomId, - 'watched_show': liveRcmd.watchedShow, - }); - Utils.toDupNamed('/liveRoom?roomid=${liveItem.roomId}'); - break; - - /// 合集查看 - case 'DYNAMIC_TYPE_UGC_SEASON': - DynamicArchiveModel ugcSeason = - item.modules.moduleDynamic.major.ugcSeason; - int aid = ugcSeason.aid!; - String bvid = IdUtils.av2bv(aid); - String cover = ugcSeason.cover!; - int cid = await SearchHttp.ab2c(bvid: bvid); - Utils.toDupNamed( - '/video?bvid=$bvid&cid=$cid', - arguments: { - 'pic': cover, - 'heroTag': Utils.makeHeroTag(bvid), - }, - ); - break; - - /// 番剧查看 - case 'DYNAMIC_TYPE_PGC_UNION': - debugPrint('DYNAMIC_TYPE_PGC_UNION 番剧'); - DynamicArchiveModel pgc = item.modules.moduleDynamic.major.pgc; - if (pgc.epid != null) { - Utils.viewBangumi(epId: pgc.epid); - // SmartDialog.showLoading(msg: '获取中...'); - // var res = await SearchHttp.bangumiInfo(epId: pgc.epid); - // SmartDialog.dismiss(); - // if (res['status']) { - // // dynamic episode -> progress episode -> first episode - // EpisodeItem episode = (res['data'].episodes as List) - // .firstWhereOrNull( - // (item) => item.epId == pgc.epid, - // ) ?? - // (res['data'].episodes as List).firstWhereOrNull( - // (item) => - // item.epId == res['data'].userStatus?.progress?.lastEpId, - // ) ?? - // res['data'].episodes.first; - // dynamic epId = episode.epId; - // dynamic bvid = episode.bvid; - // dynamic cid = episode.cid; - // dynamic pic = episode.cover; - // dynamic heroTag = Utils.makeHeroTag(cid); - // Utils.toDupNamed( - // '/video?bvid=$bvid&cid=$cid&seasonId=${res['data'].seasonId}&epId=$epId', - // arguments: { - // 'pic': pic, - // 'heroTag': heroTag, - // 'videoType': SearchType.media_bangumi, - // 'bangumiItem': res['data'], - // }, - // ); - // } else { - // SmartDialog.showToast(res['msg']); - // } - } - break; - } - } - Future queryFollowing2() async { if (allFollowedUps.length >= allFollowedUpsTotal) { SmartDialog.showToast('没有更多了'); diff --git a/lib/pages/dynamics/widgets/action_panel.dart b/lib/pages/dynamics/widgets/action_panel.dart index 464dad2bc..2fe40d9c7 100644 --- a/lib/pages/dynamics/widgets/action_panel.dart +++ b/lib/pages/dynamics/widgets/action_panel.dart @@ -1,5 +1,3 @@ -// 操作栏 - import 'package:PiliPlus/common/widgets/network_img_layer.dart'; import 'package:PiliPlus/http/msg.dart'; import 'package:PiliPlus/utils/extension.dart'; @@ -11,7 +9,6 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:get/get.dart'; import 'package:PiliPlus/http/dynamics.dart'; import 'package:PiliPlus/models/dynamics/result.dart'; -import 'package:PiliPlus/pages/dynamics/index.dart'; import 'package:PiliPlus/utils/feed_back.dart'; class ActionPanel extends StatefulWidget { @@ -26,8 +23,6 @@ class ActionPanel extends StatefulWidget { } class _ActionPanelState extends State { - final DynamicsController _dynamicsController = Get.put(DynamicsController()); - // late ModuleStatModel stat; bool isProcessing = false; void handleState(Future Function() action) async { if (isProcessing.not) { @@ -37,12 +32,6 @@ class _ActionPanelState extends State { } } - // @override - // void initState() { - // super.initState(); - // stat = widget.item!.modules.moduleStat; - // } - // 动态点赞 Future onLikeDynamic() async { feedBack(); @@ -123,8 +112,8 @@ class _ActionPanelState extends State { Expanded( flex: 1, child: TextButton.icon( - onPressed: () => _dynamicsController.pushDetail(widget.item, 1, - action: 'comment'), + onPressed: () => + Utils.pushDynDetail(widget.item, 1, action: 'comment'), icon: Icon( FontAwesomeIcons.comment, size: 16, diff --git a/lib/pages/dynamics/widgets/dynamic_panel.dart b/lib/pages/dynamics/widgets/dynamic_panel.dart index 036f36e2a..f2ac40d22 100644 --- a/lib/pages/dynamics/widgets/dynamic_panel.dart +++ b/lib/pages/dynamics/widgets/dynamic_panel.dart @@ -1,6 +1,6 @@ +import 'package:PiliPlus/common/widgets/image_save.dart'; +import 'package:PiliPlus/utils/utils.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:PiliPlus/pages/dynamics/index.dart'; import 'action_panel.dart'; import 'author_panel.dart'; import 'content_panel.dart'; @@ -12,7 +12,7 @@ class DynamicPanel extends StatelessWidget { final Function? onRemove; final Function(List, int)? callback; - DynamicPanel({ + const DynamicPanel({ required this.item, this.source, this.onRemove, @@ -20,11 +20,9 @@ class DynamicPanel extends StatelessWidget { super.key, }); - final DynamicsController _dynamicsController = Get.put(DynamicsController()); - @override Widget build(BuildContext context) { - return Container( + return Padding( padding: source == 'detail' ? const EdgeInsets.only(bottom: 12) : EdgeInsets.zero, @@ -46,7 +44,29 @@ class DynamicPanel extends StatelessWidget { child: InkWell( onTap: source == 'detail' && item.type != 'DYNAMIC_TYPE_AV' ? null - : () => _dynamicsController.pushDetail(item, 1), + : () => Utils.pushDynDetail(item, 1), + onLongPress: () { + if (item.type == 'DYNAMIC_TYPE_AV') { + imageSaveDialog( + context: context, + title: item.modules.moduleDynamic.major.archive.title, + cover: item.modules.moduleDynamic.major.archive.cover, + ); + } else if (item.type == 'DYNAMIC_TYPE_UGC_SEASON') { + imageSaveDialog( + context: context, + title: item.modules.moduleDynamic.major.ugcSeason.title, + cover: item.modules.moduleDynamic.major.ugcSeason.cover, + ); + } else if (item.type == 'DYNAMIC_TYPE_PGC' || + item.type == 'DYNAMIC_TYPE_PGC_UNION') { + imageSaveDialog( + context: context, + title: item.modules.moduleDynamic.major.pgc.title, + cover: item.modules.moduleDynamic.major.pgc.cover, + ); + } + }, child: Column( mainAxisAlignment: MainAxisAlignment.start, children: [ @@ -61,7 +81,7 @@ class DynamicPanel extends StatelessWidget { if (item!.modules!.moduleDynamic!.desc != null || item!.modules!.moduleDynamic!.major != null) content(context, item, source, callback), - forWard(item, context, _dynamicsController, source, callback), + forWard(item, context, source, callback), const SizedBox(height: 2), if (source == null) ActionPanel(item: item), ], diff --git a/lib/pages/dynamics/widgets/dynamic_panel_grpc.dart b/lib/pages/dynamics/widgets/dynamic_panel_grpc.dart index e1c0c743c..bdf44430d 100644 --- a/lib/pages/dynamics/widgets/dynamic_panel_grpc.dart +++ b/lib/pages/dynamics/widgets/dynamic_panel_grpc.dart @@ -1,24 +1,21 @@ import 'package:PiliPlus/grpc/app/dynamic/v2/dynamic.pb.dart'; import 'package:PiliPlus/pages/dynamics/widgets/author_panel_grpc.dart'; import 'package:PiliPlus/pages/dynamics/widgets/content_panel_grpc.dart'; +import 'package:PiliPlus/utils/utils.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:PiliPlus/pages/dynamics/index.dart'; class DynamicPanelGrpc extends StatelessWidget { final DynamicItem item; final String? source; final Function? onRemove; - DynamicPanelGrpc({ + const DynamicPanelGrpc({ required this.item, this.source, this.onRemove, super.key, }); - final DynamicsController _dynamicsController = Get.put(DynamicsController()); - @override Widget build(BuildContext context) { return Container( @@ -43,7 +40,7 @@ class DynamicPanelGrpc extends StatelessWidget { child: InkWell( onTap: source == 'detail' && item.itemType == DynamicType.draw ? null - : () => _dynamicsController.pushDetail(item, 1), + : () => Utils.pushDynDetail(item, 1), child: Column( mainAxisAlignment: MainAxisAlignment.start, children: [ diff --git a/lib/pages/dynamics/widgets/forward_panel.dart b/lib/pages/dynamics/widgets/forward_panel.dart index ac0e2e485..5e26417e6 100644 --- a/lib/pages/dynamics/widgets/forward_panel.dart +++ b/lib/pages/dynamics/widgets/forward_panel.dart @@ -1,4 +1,5 @@ // 转发 +import 'package:PiliPlus/common/widgets/image_save.dart'; import 'package:PiliPlus/common/widgets/imageview.dart'; import 'package:PiliPlus/common/widgets/network_img_layer.dart'; import 'package:flutter/material.dart'; @@ -35,7 +36,7 @@ InlineSpan picsNodes(List pics, callback) { ); } -Widget forWard(item, context, ctr, source, callback, {floor = 1}) { +Widget forWard(item, context, source, callback, {floor = 1}) { TextStyle authorStyle = TextStyle(color: Theme.of(context).colorScheme.primary); @@ -140,13 +141,35 @@ Widget forWard(item, context, ctr, source, callback, {floor = 1}) { // 转发 case 'DYNAMIC_TYPE_FORWARD': return InkWell( - onTap: () => ctr.pushDetail(item.orig, floor + 1), + onTap: () => Utils.pushDynDetail(item.orig, floor + 1), + onLongPress: () { + if (item.orig.type == 'DYNAMIC_TYPE_AV') { + imageSaveDialog( + context: context, + title: item.orig.modules.moduleDynamic.major.archive.title, + cover: item.orig.modules.moduleDynamic.major.archive.cover, + ); + } else if (item.orig.type == 'DYNAMIC_TYPE_UGC_SEASON') { + imageSaveDialog( + context: context, + title: item.orig.modules.moduleDynamic.major.ugcSeason.title, + cover: item.orig.modules.moduleDynamic.major.ugcSeason.cover, + ); + } else if (item.orig.type == 'DYNAMIC_TYPE_PGC' || + item.orig.type == 'DYNAMIC_TYPE_PGC_UNION') { + imageSaveDialog( + context: context, + title: item.orig.modules.moduleDynamic.major.pgc.title, + cover: item.orig.modules.moduleDynamic.major.pgc.cover, + ); + } + }, child: Container( padding: const EdgeInsets.only(left: 15, top: 10, right: 15, bottom: 8), color: Theme.of(context).dividerColor.withOpacity(0.08), - child: forWard(item.orig, context, ctr, source, callback, - floor: floor + 1), + child: + forWard(item.orig, context, source, callback, floor: floor + 1), ), ); // 直播 diff --git a/lib/pages/dynamics/widgets/live_rcmd_panel.dart b/lib/pages/dynamics/widgets/live_rcmd_panel.dart index 465f8e620..dccd09b16 100644 --- a/lib/pages/dynamics/widgets/live_rcmd_panel.dart +++ b/lib/pages/dynamics/widgets/live_rcmd_panel.dart @@ -4,12 +4,10 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; import 'package:PiliPlus/common/widgets/network_img_layer.dart'; import 'package:PiliPlus/models/dynamics/result.dart'; -import 'package:PiliPlus/pages/dynamics/index.dart'; import 'package:PiliPlus/utils/utils.dart'; import 'rich_node_panel.dart'; -final DynamicsController _dynamicsController = Get.put(DynamicsController()); Widget liveRcmdPanel(item, context, {floor = 1}) { TextStyle authorStyle = TextStyle(color: Theme.of(context).colorScheme.primary); @@ -65,7 +63,7 @@ Widget liveRcmdPanel(item, context, {floor = 1}) { const EdgeInsets.symmetric(horizontal: StyleString.safeSpace), child: GestureDetector( onTap: () { - _dynamicsController.pushDetail(item, floor); + Utils.pushDynDetail(item, floor); }, child: LayoutBuilder(builder: (context, box) { double width = box.maxWidth; diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 35a3f028f..7c9499874 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -15,6 +15,8 @@ import 'package:PiliPlus/http/video.dart'; import 'package:PiliPlus/models/bangumi/info.dart'; import 'package:PiliPlus/models/common/dynamics_type.dart'; import 'package:PiliPlus/models/common/search_type.dart'; +import 'package:PiliPlus/models/dynamics/result.dart'; +import 'package:PiliPlus/models/live/item.dart'; import 'package:PiliPlus/pages/dynamics/controller.dart'; import 'package:PiliPlus/pages/dynamics/tab/controller.dart'; import 'package:PiliPlus/pages/home/controller.dart'; @@ -24,7 +26,9 @@ import 'package:PiliPlus/pages/mine/controller.dart'; import 'package:PiliPlus/pages/video/detail/introduction/widgets/group_panel.dart'; import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/feed_back.dart'; +import 'package:PiliPlus/utils/id_utils.dart'; import 'package:PiliPlus/utils/storage.dart'; +import 'package:PiliPlus/utils/url_utils.dart'; import 'package:crypto/crypto.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/material.dart'; @@ -43,6 +47,165 @@ class Utils { static const channel = MethodChannel("PiliPlus"); + static void pushDynDetail(item, floor, {action = 'all'}) async { + feedBack(); + + /// 点击评论action 直接查看评论 + if (action == 'comment') { + Utils.toDupNamed('/dynamicDetail', + arguments: {'item': item, 'floor': floor, 'action': action}); + return; + } + switch (item!.type) { + /// 转发的动态 + case 'DYNAMIC_TYPE_FORWARD': + Utils.toDupNamed('/dynamicDetail', + arguments: {'item': item, 'floor': floor}); + break; + + /// 图文动态查看 + case 'DYNAMIC_TYPE_DRAW': + Utils.toDupNamed('/dynamicDetail', + arguments: {'item': item, 'floor': floor}); + break; + case 'DYNAMIC_TYPE_AV': + if (item.modules.moduleDynamic.major.archive.type == 2) { + if (item.modules.moduleDynamic.major.archive.jumpUrl + .startsWith('//')) { + item.modules.moduleDynamic.major.archive.jumpUrl = + 'https:${item.modules.moduleDynamic.major.archive.jumpUrl}'; + } + String? redirectUrl = await UrlUtils.parseRedirectUrl( + item.modules.moduleDynamic.major.archive.jumpUrl, false); + if (redirectUrl != null) { + Utils.viewPgcFromUri(redirectUrl); + return; + } + } + + String bvid = item.modules.moduleDynamic.major.archive.bvid; + String cover = item.modules.moduleDynamic.major.archive.cover; + try { + int cid = await SearchHttp.ab2c(bvid: bvid); + Utils.toDupNamed( + '/video?bvid=$bvid&cid=$cid', + arguments: { + 'pic': cover, + 'heroTag': Utils.makeHeroTag(bvid), + }, + ); + } catch (err) { + SmartDialog.showToast(err.toString()); + } + break; + + /// 专栏文章查看 + case 'DYNAMIC_TYPE_ARTICLE': + String title = item.modules.moduleDynamic.major.opus.title; + String url = item.modules.moduleDynamic.major.opus.jumpUrl; + if (url.contains('opus') || url.contains('read')) { + RegExp digitRegExp = RegExp(r'\d+'); + Iterable matches = digitRegExp.allMatches(url); + String number = matches.first.group(0)!; + if (url.contains('read')) { + number = 'cv$number'; + } + Utils.toDupNamed('/htmlRender', parameters: { + 'url': url.startsWith('//') ? url.split('//').last : url, + 'title': title, + 'id': number, + 'dynamicType': url.split('//').last.split('/')[1] + }); + } else { + Utils.handleWebview('https:$url'); + } + + break; + case 'DYNAMIC_TYPE_PGC': + debugPrint('番剧'); + SmartDialog.showToast('暂未支持的类型,请联系开发者'); + break; + + /// 纯文字动态查看 + case 'DYNAMIC_TYPE_WORD': + debugPrint('纯文本'); + Utils.toDupNamed('/dynamicDetail', + arguments: {'item': item, 'floor': floor}); + break; + case 'DYNAMIC_TYPE_LIVE_RCMD': + DynamicLiveModel liveRcmd = item.modules.moduleDynamic.major.liveRcmd; + ModuleAuthorModel author = item.modules.moduleAuthor; + LiveItemModel liveItem = LiveItemModel.fromJson({ + 'title': liveRcmd.title, + 'uname': author.name, + 'cover': liveRcmd.cover, + 'mid': author.mid, + 'face': author.face, + 'roomid': liveRcmd.roomId, + 'watched_show': liveRcmd.watchedShow, + }); + Utils.toDupNamed('/liveRoom?roomid=${liveItem.roomId}'); + break; + + /// 合集查看 + case 'DYNAMIC_TYPE_UGC_SEASON': + DynamicArchiveModel ugcSeason = + item.modules.moduleDynamic.major.ugcSeason; + int aid = ugcSeason.aid!; + String bvid = IdUtils.av2bv(aid); + String cover = ugcSeason.cover!; + int cid = await SearchHttp.ab2c(bvid: bvid); + Utils.toDupNamed( + '/video?bvid=$bvid&cid=$cid', + arguments: { + 'pic': cover, + 'heroTag': Utils.makeHeroTag(bvid), + }, + ); + break; + + /// 番剧查看 + case 'DYNAMIC_TYPE_PGC_UNION': + debugPrint('DYNAMIC_TYPE_PGC_UNION 番剧'); + DynamicArchiveModel pgc = item.modules.moduleDynamic.major.pgc; + if (pgc.epid != null) { + Utils.viewBangumi(epId: pgc.epid); + // SmartDialog.showLoading(msg: '获取中...'); + // var res = await SearchHttp.bangumiInfo(epId: pgc.epid); + // SmartDialog.dismiss(); + // if (res['status']) { + // // dynamic episode -> progress episode -> first episode + // EpisodeItem episode = (res['data'].episodes as List) + // .firstWhereOrNull( + // (item) => item.epId == pgc.epid, + // ) ?? + // (res['data'].episodes as List).firstWhereOrNull( + // (item) => + // item.epId == res['data'].userStatus?.progress?.lastEpId, + // ) ?? + // res['data'].episodes.first; + // dynamic epId = episode.epId; + // dynamic bvid = episode.bvid; + // dynamic cid = episode.cid; + // dynamic pic = episode.cover; + // dynamic heroTag = Utils.makeHeroTag(cid); + // Utils.toDupNamed( + // '/video?bvid=$bvid&cid=$cid&seasonId=${res['data'].seasonId}&epId=$epId', + // arguments: { + // 'pic': pic, + // 'heroTag': heroTag, + // 'videoType': SearchType.media_bangumi, + // 'bangumiItem': res['data'], + // }, + // ); + // } else { + // SmartDialog.showToast(res['msg']); + // } + } + break; + } + } + static void onHorizontalPreview(GlobalKey key, transitionAnimationController, ctr, imgList, index, onClose) { key.currentState?.showBottomSheet(