diff --git a/lib/common/widgets/video_card_h.dart b/lib/common/widgets/video_card_h.dart index 38d6df2b..3fb07ae7 100644 --- a/lib/common/widgets/video_card_h.dart +++ b/lib/common/widgets/video_card_h.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import '../../http/search.dart'; @@ -41,11 +42,17 @@ class VideoCardH extends StatelessWidget { try { type = videoItem.type; } catch (_) {} + List actions = + VideoCustomActions(videoItem, context).actions; final String heroTag = Utils.makeHeroTag(aid); return Stack(children: [ Semantics( label: Utils.videoItemSemantics(videoItem), excludeSemantics: true, + customSemanticsActions: { + for (var item in actions) + CustomSemanticsAction(label: item.title): item.onTap!, + }, child: InkWell( borderRadius: BorderRadius.circular(12), onLongPress: () { @@ -134,7 +141,7 @@ class VideoCardH extends StatelessWidget { child: VideoPopupMenu( size: 29, iconSize: 17, - videoItem: videoItem, + actions: actions, ), ), ]); diff --git a/lib/common/widgets/video_card_v.dart b/lib/common/widgets/video_card_v.dart index 49fe25db..146a0013 100644 --- a/lib/common/widgets/video_card_v.dart +++ b/lib/common/widgets/video_card_v.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import '../../models/model_rec_video_item.dart'; @@ -127,10 +128,16 @@ class VideoCardV extends StatelessWidget { @override Widget build(BuildContext context) { String heroTag = Utils.makeHeroTag(videoItem.id); + List actions = + VideoCustomActions(videoItem, context).actions; return Stack(children: [ Semantics( label: Utils.videoItemSemantics(videoItem), excludeSemantics: true, + customSemanticsActions: { + for (var item in actions) + CustomSemanticsAction(label: item.title): item.onTap!, + }, child: Card( clipBehavior: Clip.hardEdge, margin: EdgeInsets.zero, @@ -185,7 +192,7 @@ class VideoCardV extends StatelessWidget { child: VideoPopupMenu( size: 29, iconSize: 17, - videoItem: videoItem, + actions: actions, )), ]); } diff --git a/lib/common/widgets/video_popup_menu.dart b/lib/common/widgets/video_popup_menu.dart index ef616476..8b7e4f93 100644 --- a/lib/common/widgets/video_popup_menu.dart +++ b/lib/common/widgets/video_popup_menu.dart @@ -1,4 +1,3 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; @@ -10,22 +9,258 @@ import '../../models/home/rcmd/result.dart'; import '../../pages/mine/controller.dart'; import '../../utils/storage.dart'; +class VideoCustomAction { + String title; + String value; + Icon icon; + VoidCallback? onTap; + VideoCustomAction(this.title, this.value, this.icon, this.onTap); +} + +class VideoCustomActions { + dynamic videoItem; + BuildContext context; + late List actions; + VideoCustomActions(this.videoItem, this.context) { + actions = [ + VideoCustomAction( + '稍后再看', 'pause', Icon(MdiIcons.clockTimeEightOutline, size: 16), + () async { + var res = await UserHttp.toViewLater(bvid: videoItem.bvid as String); + SmartDialog.showToast(res['msg']); + }), + VideoCustomAction('访问:${videoItem.owner.name}', 'visit', + Icon(MdiIcons.accountCircleOutline, size: 16), () async { + Get.toNamed('/member?mid=${videoItem.owner.mid}', arguments: { + // 'face': videoItem.owner.face, + 'heroTag': '${videoItem.owner.mid}', + }); + }), + VideoCustomAction( + '不感兴趣', 'dislike', Icon(MdiIcons.thumbDownOutline, size: 16), + () async { + String? accessKey = GStorage.localCache + .get(LocalCacheKey.accessKey, defaultValue: {})['value']; + if (accessKey == null || accessKey == "") { + SmartDialog.showToast("请退出账号后重新登录"); + return; + } + if (videoItem is RecVideoItemAppModel) { + RecVideoItemAppModel v = videoItem as RecVideoItemAppModel; + ThreePoint? tp = v.threePoint; + if (tp == null) { + SmartDialog.showToast("未能获取threePoint"); + return; + } + if (tp.dislikeReasons == null && tp.feedbacks == null) { + SmartDialog.showToast("未能获取dislikeReasons或feedbacks"); + return; + } + Widget actionButton(DislikeReason? r, FeedbackReason? f) { + return ElevatedButton( + style: ElevatedButton.styleFrom( + padding: + const EdgeInsets.symmetric(horizontal: 12.0, vertical: 0.0), + ), + onPressed: () async { + SmartDialog.showLoading(msg: '正在提交'); + var res = await VideoHttp.feedDislike( + reasonId: r?.id, + feedbackId: f?.id, + id: v.param!, + goto: v.goto!, + ); + SmartDialog.dismiss(); + SmartDialog.showToast( + res['status'] ? (r?.toast ?? f?.toast) : res['msg']); + Get.back(); + }, + child: Text(r?.name ?? f?.name ?? '未知'), + ); + } + + await showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: const Text('请选择'), + content: SingleChildScrollView( + child: Column( + children: [ + if (tp.dislikeReasons != null) + const Padding( + padding: EdgeInsets.symmetric(vertical: 8.0), + child: Text('我不想看'), + ), + if (tp.dislikeReasons != null) + Wrap( + spacing: 5.0, + runSpacing: 2.0, + children: tp.dislikeReasons!.map((item) { + return actionButton(item, null); + }).toList(), + ), + if (tp.feedbacks != null) + const Padding( + padding: EdgeInsets.symmetric(vertical: 8.0), + child: Text('反馈'), + ), + if (tp.feedbacks != null) + Wrap( + spacing: 5.0, + runSpacing: 2.0, + children: tp.feedbacks!.map((item) { + return actionButton(null, item); + }).toList(), + ), + //分割线 + const Divider(), + ElevatedButton( + onPressed: () async { + SmartDialog.showLoading(msg: '正在提交'); + var res = await VideoHttp.feedDislikeCancel( + // reasonId: r?.id, + // feedbackId: f?.id, + id: v.param!, + goto: v.goto!, + ); + SmartDialog.dismiss(); + SmartDialog.showToast( + res['status'] ? "成功" : res['msg']); + Get.back(); + }, + child: const Text("撤销"), + ), + ], + ), + ), + ); + }, + ); + } else { + await showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: const Text('点踩该视频?'), + content: SingleChildScrollView( + child: Column( + children: [ + const SizedBox(height: 5), + const Text("web端暂不支持精细选择"), + const SizedBox(height: 5), + Wrap( + spacing: 5.0, + runSpacing: 2.0, + children: [ + ElevatedButton( + onPressed: () async { + SmartDialog.showLoading(msg: '正在提交'); + var res = await VideoHttp.dislikeVideo( + bvid: videoItem.bvid as String, type: true); + SmartDialog.dismiss(); + SmartDialog.showToast( + res['status'] ? "点踩成功" : res['msg']); + Get.back(); + }, + child: const Text("点踩"), + ), + ElevatedButton( + onPressed: () async { + SmartDialog.showLoading(msg: '正在提交'); + var res = await VideoHttp.dislikeVideo( + bvid: videoItem.bvid as String, type: false); + SmartDialog.dismiss(); + SmartDialog.showToast( + res['status'] ? "取消踩" : res['msg']); + Get.back(); + }, + child: const Text("撤销"), + ), + ], + ) + ], + ), + ), + ); + }, + ); + } + }), + VideoCustomAction('拉黑:${videoItem.owner.name}', 'block', + Icon(MdiIcons.cancel, size: 16), () async { + await showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: const Text('提示'), + content: + Text('确定拉黑:${videoItem.owner.name}(${videoItem.owner.mid})?' + '\n\n注:被拉黑的Up可以在隐私设置-黑名单管理中解除'), + actions: [ + TextButton( + onPressed: () => Get.back(), + 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, + ); + List blackMidsList = GStorage.localCache + .get(LocalCacheKey.blackMidsList, defaultValue: [-1]) + .map((i) => i as int) + .toList(); + blackMidsList.insert(0, videoItem.owner.mid); + GStorage.localCache + .put(LocalCacheKey.blackMidsList, blackMidsList); + Get.back(); + SmartDialog.showToast(res['msg'] ?? '成功'); + }, + child: const Text('确认'), + ) + ], + ); + }, + ); + }), + VideoCustomAction( + "${MineController.anonymity ? '退出' : '进入'}无痕模式", + 'anonymity', + Icon( + MineController.anonymity + ? MdiIcons.incognitoOff + : MdiIcons.incognito, + size: 16, + ), + () => MineController.onChangeAnonymity(context)) + ]; + } +} + class VideoPopupMenu extends StatelessWidget { final double? size; final double? iconSize; - final dynamic videoItem; + final List actions; final double menuItemHeight = 45; const VideoPopupMenu({ - Key? key, + super.key, required this.size, required this.iconSize, - required this.videoItem, - }) : super(key: key); + required this.actions, + }); @override Widget build(BuildContext context) { - return SizedBox( + return ExcludeSemantics( + child: SizedBox( width: size, height: size, child: PopupMenuButton( @@ -37,296 +272,21 @@ class VideoPopupMenu extends StatelessWidget { ), position: PopupMenuPosition.under, 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', + itemBuilder: (BuildContext context) => actions.map((e) { + return PopupMenuItem( + value: e.value, height: menuItemHeight, + onTap: e.onTap, child: Row( children: [ - Icon(MdiIcons.clockTimeEightOutline, size: 16), + e.icon, const SizedBox(width: 6), - const Text('稍后再看', style: TextStyle(fontSize: 13)) + Text(e.title, style: const TextStyle(fontSize: 13)) ], ), - ), - PopupMenuItem( - onTap: () async { - Get.toNamed('/member?mid=${videoItem.owner.mid}', arguments: { - // 'face': videoItem.owner.face, - 'heroTag': '${videoItem.owner.mid}', - }); - }, - value: 'visit', - height: menuItemHeight, - child: Row( - children: [ - Icon(MdiIcons.accountCircleOutline, size: 16), - const SizedBox(width: 6), - Text('访问:${videoItem.owner.name}', - style: const TextStyle(fontSize: 13)) - ], - ), - ), - // 不感兴趣 - PopupMenuItem( - onTap: () async { - String? accessKey = GStorage.localCache - .get(LocalCacheKey.accessKey, defaultValue: {})['value']; - if (accessKey == null || accessKey == "") { - SmartDialog.showToast("请退出账号后重新登录"); - return; - } - if (videoItem is RecVideoItemAppModel) { - RecVideoItemAppModel v = videoItem as RecVideoItemAppModel; - ThreePoint? tp = v.threePoint; - if (tp == null) { - SmartDialog.showToast("未能获取threePoint"); - return; - } - if (tp.dislikeReasons == null && tp.feedbacks == null) { - SmartDialog.showToast("未能获取dislikeReasons或feedbacks"); - return; - } - Widget actionButton(DislikeReason? r, FeedbackReason? f) { - return ElevatedButton( - style: ElevatedButton.styleFrom( - padding: const EdgeInsets.symmetric( - horizontal: 12.0, vertical: 0.0), - ), - onPressed: () async { - SmartDialog.showLoading(msg: '正在提交'); - var res = await VideoHttp.feedDislike( - reasonId: r?.id, - feedbackId: f?.id, - id: v.param!, - goto: v.goto!, - ); - SmartDialog.dismiss(); - SmartDialog.showToast( - res['status'] ? (r?.toast ?? f?.toast) : res['msg']); - Get.back(); - }, - child: Text(r?.name ?? f?.name ?? '未知'), - ); - } - - await showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: const Text('请选择'), - content: SingleChildScrollView( - child: Column( - children: [ - if (tp.dislikeReasons != null) - const Padding( - padding: EdgeInsets.symmetric(vertical: 8.0), - child: Text('我不想看'), - ), - if (tp.dislikeReasons != null) - Wrap( - spacing: 5.0, - runSpacing: 2.0, - children: tp.dislikeReasons!.map((item) { - return actionButton(item, null); - }).toList(), - ), - if (tp.feedbacks != null) - const Padding( - padding: EdgeInsets.symmetric(vertical: 8.0), - child: Text('反馈'), - ), - if (tp.feedbacks != null) - Wrap( - spacing: 5.0, - runSpacing: 2.0, - children: tp.feedbacks!.map((item) { - return actionButton(null, item); - }).toList(), - ), - //分割线 - const Divider(), - ElevatedButton( - onPressed: () async { - SmartDialog.showLoading(msg: '正在提交'); - var res = await VideoHttp.feedDislikeCancel( - // reasonId: r?.id, - // feedbackId: f?.id, - id: v.param!, - goto: v.goto!, - ); - SmartDialog.dismiss(); - SmartDialog.showToast( - res['status'] ? "成功" : res['msg']); - Get.back(); - }, - child: const Text("撤销"), - ), - ], - ), - ), - ); - }, - ); - } else { - await showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: const Text('点踩该视频?'), - content: SingleChildScrollView( - child: Column( - children: [ - const SizedBox(height: 5), - const Text("web端暂不支持精细选择"), - const SizedBox(height: 5), - Wrap( - spacing: 5.0, - runSpacing: 2.0, - children: [ - ElevatedButton( - onPressed: () async { - SmartDialog.showLoading(msg: '正在提交'); - var res = await VideoHttp.dislikeVideo( - bvid: videoItem.bvid as String, - type: true); - SmartDialog.dismiss(); - SmartDialog.showToast( - res['status'] ? "点踩成功" : res['msg']); - Get.back(); - }, - child: const Text("点踩"), - ), - ElevatedButton( - onPressed: () async { - SmartDialog.showLoading(msg: '正在提交'); - var res = await VideoHttp.dislikeVideo( - bvid: videoItem.bvid as String, - type: false); - SmartDialog.dismiss(); - SmartDialog.showToast( - res['status'] ? "取消踩" : res['msg']); - Get.back(); - }, - child: const Text("撤销"), - ), - ], - ) - ], - ), - ), - ); - }, - ); - } - }, - value: 'dislike', - height: menuItemHeight, - child: Row( - children: [ - Icon(MdiIcons.thumbDownOutline, size: 16), - const SizedBox(width: 6), - const Text('不感兴趣', style: TextStyle(fontSize: 13)) - ], - ), - ), - PopupMenuItem( - onTap: () async { - await showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: const Text('提示'), - content: Text( - '确定拉黑:${videoItem.owner.name}(${videoItem.owner.mid})?' - '\n\n注:被拉黑的Up可以在隐私设置-黑名单管理中解除'), - actions: [ - TextButton( - onPressed: () => Get.back(), - 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, - ); - List blackMidsList = GStorage.localCache - .get(LocalCacheKey.blackMidsList, - defaultValue: [-1]) - .map((i) => i as int) - .toList(); - blackMidsList.insert(0, videoItem.owner.mid); - GStorage.localCache - .put(LocalCacheKey.blackMidsList, blackMidsList); - Get.back(); - SmartDialog.showToast(res['msg'] ?? '成功'); - }, - child: const Text('确认'), - ) - ], - ); - }, - ); - }, - value: 'block', - height: menuItemHeight, - child: Row( - children: [ - Icon(MdiIcons.cancel, size: 16), - const SizedBox(width: 6), - Text('拉黑:${videoItem.owner.name}', - style: const TextStyle(fontSize: 13)) - ], - ), - ), - // PopupMenuItem( - // onTap: () async { - // SmartDialog.showToast("还没做"); - // }, - // value: 'anonymize', - // height: menuItemHeight, - // child: const Row( - // children: [ - // Icon(Icons.visibility_off_outlined, size: 16), - // SizedBox(width: 6), - // Text('无痕播放', - // style: TextStyle(fontSize: 13)) - // ], - // ), - // ), - PopupMenuItem( - onTap: () { - MineController.onChangeAnonymity(context); - }, - value: 'anonymous', - height: menuItemHeight, - child: Row( - children: [ - Icon( - MineController.anonymity - ? MdiIcons.incognitoOff - : MdiIcons.incognito, - size: 16, - ), - const SizedBox(width: 6), - Text("${MineController.anonymity ? '退出' : '进入'}无痕模式", - style: const TextStyle(fontSize: 13)) - ], - ), - ), - ], + ); + }).toList(), ), - ); + )); } }