opt mouse control

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-09-28 15:33:09 +08:00
parent 2031604ea2
commit 5f8dc76891
19 changed files with 268 additions and 191 deletions

View File

@@ -270,6 +270,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
itemCount: widget.sources.length,
itemBuilder: (BuildContext context, int index) {
final item = widget.sources[index];
final isFileImg = item.sourceType == SourceType.fileImage;
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => EasyThrottle.throttle(
@@ -285,9 +286,12 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
const Duration(milliseconds: 555),
onDoubleTap,
),
onLongPress: item.sourceType == SourceType.fileImage
? null
: () => onLongPress(item),
onLongPress: !isFileImg && Utils.isMobile
? () => onLongPress(item)
: null,
onSecondaryTap: !isFileImg && !Utils.isMobile
? () => onLongPress(item)
: null,
child: widget.itemBuilder != null
? widget.itemBuilder!(
context,

View File

@@ -67,6 +67,11 @@ class VideoCardV extends StatelessWidget {
@override
Widget build(BuildContext context) {
void onLongPress() => imageSaveDialog(
title: videoItem.title,
cover: videoItem.cover,
bvid: videoItem.bvid,
);
return Stack(
clipBehavior: Clip.none,
children: [
@@ -74,11 +79,8 @@ class VideoCardV extends StatelessWidget {
clipBehavior: Clip.hardEdge,
child: InkWell(
onTap: () => onPushDetail(Utils.makeHeroTag(videoItem.aid)),
onLongPress: () => imageSaveDialog(
title: videoItem.title,
cover: videoItem.cover,
bvid: videoItem.bvid,
),
onLongPress: Utils.isMobile ? onLongPress : null,
onSecondaryTap: Utils.isMobile ? null : onLongPress,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [

View File

@@ -107,7 +107,8 @@ abstract class CommonRichTextPubPageState<T extends CommonRichTextPubPage>
);
controller.restoreChatPanel();
},
onLongPress: onClear,
onLongPress: Utils.isMobile ? onClear : null,
onSecondaryTap: Utils.isMobile ? null : onClear,
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(4)),
child: Image(

View File

@@ -5,6 +5,7 @@ import 'package:PiliPlus/pages/dynamics/widgets/action_panel.dart';
import 'package:PiliPlus/pages/dynamics/widgets/author_panel.dart';
import 'package:PiliPlus/pages/dynamics/widgets/dyn_content.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart' hide InkWell;
class DynamicPanel extends StatelessWidget {
@@ -45,6 +46,9 @@ class DynamicPanel extends StatelessWidget {
onSetTop: onSetTop,
onBlock: onBlock,
);
void showMore() => _imageSaveDialog(context, authorWidget.morePanel);
final child = Material(
type: MaterialType.transparency,
child: InkWell(
@@ -62,7 +66,8 @@ class DynamicPanel extends StatelessWidget {
}.contains(item.type)
? null
: () => PageUtils.pushDynDetail(item),
onLongPress: () => _imageSaveDialog(context, authorWidget.morePanel),
onLongPress: Utils.isMobile ? showMore : null,
onSecondaryTap: Utils.isMobile ? null : showMore,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,

View File

@@ -5,6 +5,7 @@ import 'package:PiliPlus/pages/dynamics/widgets/dyn_content.dart';
import 'package:PiliPlus/pages/dynamics/widgets/module_panel.dart';
import 'package:PiliPlus/utils/date_utils.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart' hide InkWell;
import 'package:get/get.dart';
@@ -59,44 +60,47 @@ Widget forwardPanel(
return child;
}
void showMore() {
String? title, cover, bvid;
switch (orig.type) {
case 'DYNAMIC_TYPE_AV':
title = major?.archive?.title;
cover = major?.archive?.cover;
bvid = major?.archive?.bvid;
break;
case 'DYNAMIC_TYPE_UGC_SEASON':
title = major?.ugcSeason?.title;
cover = major?.ugcSeason?.cover;
bvid = major?.ugcSeason?.bvid;
break;
case 'DYNAMIC_TYPE_PGC' || 'DYNAMIC_TYPE_PGC_UNION':
title = major?.pgc?.title;
cover = major?.pgc?.cover;
break;
case 'DYNAMIC_TYPE_LIVE_RCMD':
title = major?.liveRcmd?.title;
cover = major?.liveRcmd?.cover;
break;
case 'DYNAMIC_TYPE_LIVE':
title = major?.live?.title;
cover = major?.live?.cover;
break;
default:
return;
}
if (cover != null) {
imageSaveDialog(
title: title,
cover: cover,
bvid: bvid,
);
}
}
return InkWell(
onTap: () => PageUtils.pushDynDetail(orig),
onLongPress: () {
String? title, cover, bvid;
switch (orig.type) {
case 'DYNAMIC_TYPE_AV':
title = major?.archive?.title;
cover = major?.archive?.cover;
bvid = major?.archive?.bvid;
break;
case 'DYNAMIC_TYPE_UGC_SEASON':
title = major?.ugcSeason?.title;
cover = major?.ugcSeason?.cover;
bvid = major?.ugcSeason?.bvid;
break;
case 'DYNAMIC_TYPE_PGC' || 'DYNAMIC_TYPE_PGC_UNION':
title = major?.pgc?.title;
cover = major?.pgc?.cover;
break;
case 'DYNAMIC_TYPE_LIVE_RCMD':
title = major?.liveRcmd?.title;
cover = major?.liveRcmd?.cover;
break;
case 'DYNAMIC_TYPE_LIVE':
title = major?.live?.title;
cover = major?.live?.cover;
break;
default:
return;
}
if (cover != null) {
imageSaveDialog(
title: title,
cover: cover,
bvid: bvid,
);
}
},
onLongPress: Utils.isMobile ? showMore : null,
onSecondaryTap: Utils.isMobile ? null : showMore,
child: child,
);
}

View File

@@ -7,6 +7,7 @@ import 'package:PiliPlus/pages/dynamics/controller.dart';
import 'package:PiliPlus/pages/live_follow/view.dart';
import 'package:PiliPlus/utils/feed_back.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart' hide InkWell;
import 'package:get/get.dart';
@@ -25,6 +26,8 @@ class _UpPanelState extends State<UpPanel> {
late final controller = widget.dynamicsController;
late final isTop = controller.upPanelPosition == UpPanelPosition.top;
void toFollowPage() => Get.to(const LiveFollowPage());
@override
Widget build(BuildContext context) {
final accountService = controller.accountService;
@@ -45,7 +48,8 @@ class _UpPanelState extends State<UpPanel> {
onTap: () => setState(() {
controller.showLiveUp = !controller.showLiveUp;
}),
onLongPress: () => Get.to(const LiveFollowPage()),
onLongPress: Utils.isMobile ? toFollowPage : null,
onSecondaryTap: Utils.isMobile ? null : toFollowPage,
child: Container(
alignment: Alignment.center,
height: isTop ? 76 : 60,
@@ -138,6 +142,10 @@ class _UpPanelState extends State<UpPanel> {
final currentMid = controller.currentMid;
final isLive = data is LiveUserItem;
bool isCurrent = isLive || currentMid == data.mid || currentMid == -1;
final isAll = data.mid == -1;
void toMemberPage() => Get.toNamed('/member?mid=${data.mid}');
return SizedBox(
height: 76,
width: isTop ? 70 : null,
@@ -153,9 +161,8 @@ class _UpPanelState extends State<UpPanel> {
}
},
// onDoubleTap: isLive ? () => _onSelect(data) : null,
onLongPress: data.mid == -1
? null
: () => Get.toNamed('/member?mid=${data.mid}'),
onLongPress: !isAll && Utils.isMobile ? toMemberPage : null,
onSecondaryTap: !isAll && !Utils.isMobile ? toMemberPage : null,
child: AnimatedOpacity(
opacity: isCurrent ? 1 : 0.6,
duration: const Duration(milliseconds: 200),

View File

@@ -111,6 +111,14 @@ class _FansPageState extends State<FansPage> {
}
Widget _buildItem(ColorScheme theme, int index, FansItemModel item) {
final isSelect = widget.onSelect != null;
void onRemove() => showConfirmDialog(
context: context,
title: '确定移除 ${item.uname} ',
onConfirm: () => _fansController.onRemoveFan(index, item.mid!),
);
final flag = !isSelect && isOwner;
return SizedBox(
height: 66,
child: InkWell(
@@ -127,15 +135,8 @@ class _FansPageState extends State<FansPage> {
}
Get.toNamed('/member?mid=${item.mid}');
},
onLongPress: widget.onSelect != null
? null
: isOwner
? () => showConfirmDialog(
context: context,
title: '确定移除 ${item.uname} ',
onConfirm: () => _fansController.onRemoveFan(index, item.mid!),
)
: null,
onLongPress: flag && Utils.isMobile ? onRemove : null,
onSecondaryTap: flag && !Utils.isMobile ? onRemove : null,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12,

View File

@@ -4,6 +4,7 @@ import 'package:PiliPlus/common/widgets/select_mask.dart';
import 'package:PiliPlus/models_new/fav/fav_note/list.dart';
import 'package:PiliPlus/pages/fav/note/controller.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
class FavNoteItem extends StatelessWidget {
@@ -18,6 +19,13 @@ class FavNoteItem extends StatelessWidget {
final FavNoteController ctr;
final VoidCallback onSelect;
void onLongPress() {
if (!ctr.enableMultiSelect.value) {
ctr.enableMultiSelect.value = true;
onSelect();
}
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
@@ -36,12 +44,8 @@ class FavNoteItem extends StatelessWidget {
);
}
},
onLongPress: () {
if (!ctr.enableMultiSelect.value) {
ctr.enableMultiSelect.value = true;
onSelect();
}
},
onLongPress: Utils.isMobile ? onLongPress : null,
onSecondaryTap: Utils.isMobile ? null : onLongPress,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: StyleString.safeSpace,

View File

@@ -7,6 +7,7 @@ import 'package:PiliPlus/models/common/badge_type.dart';
import 'package:PiliPlus/models_new/fav/fav_pgc/list.dart';
import 'package:PiliPlus/pages/common/multi_select/base.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
class FavPgcItem extends StatelessWidget {
@@ -23,6 +24,13 @@ class FavPgcItem extends StatelessWidget {
final VoidCallback onSelect;
final VoidCallback onUpdateStatus;
void onLongPress() {
if (!ctr.enableMultiSelect.value) {
ctr.enableMultiSelect.value = true;
onSelect();
}
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
@@ -39,12 +47,8 @@ class FavPgcItem extends StatelessWidget {
}
PageUtils.viewPgc(seasonId: item.seasonId);
},
onLongPress: () {
if (!ctr.enableMultiSelect.value) {
ctr.enableMultiSelect.value = true;
onSelect();
}
},
onLongPress: Utils.isMobile ? onLongPress : null,
onSecondaryTap: Utils.isMobile ? null : onLongPress,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: StyleString.safeSpace,

View File

@@ -6,6 +6,7 @@ import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models_new/fav/fav_topic/topic_item.dart';
import 'package:PiliPlus/pages/fav/topic/controller.dart';
import 'package:PiliPlus/utils/grid.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
@@ -78,6 +79,13 @@ class _FavTopicPageState extends State<FavTopicPage>
_controller.onLoadMore();
}
final item = response[index];
void onLongPress() => showConfirmDialog(
context: context,
title: '确定取消收藏?',
onConfirm: () => _controller.onRemove(index, item.id),
);
return Material(
color: theme.colorScheme.onInverseSurface,
borderRadius: const BorderRadius.all(Radius.circular(6)),
@@ -89,13 +97,8 @@ class _FavTopicPageState extends State<FavTopicPage>
'name': item.name!,
},
),
onLongPress: () => showConfirmDialog(
context: context,
title: '确定取消收藏?',
onConfirm: () {
_controller.onRemove(index, item.id);
},
),
onLongPress: Utils.isMobile ? onLongPress : null,
onSecondaryTap: Utils.isMobile ? null : onLongPress,
borderRadius: const BorderRadius.all(
Radius.circular(6),
),

View File

@@ -106,10 +106,15 @@ class _FollowPageState extends State<FollowPage> {
if (_isCustomTag(item.tagid)) {
return GestureDetector(
behavior: HitTestBehavior.translucent,
onLongPress: () {
Feedback.forLongPress(context);
_onHandleTag(index, item);
},
onLongPress: Utils.isMobile
? () {
Feedback.forLongPress(context);
_onHandleTag(index, item);
}
: null,
onSecondaryTap: Utils.isMobile
? null
: () => _onHandleTag(index, item),
child: Tab(
child: Row(
children: [

View File

@@ -18,6 +18,7 @@ import 'package:PiliPlus/pages/live_follow/view.dart';
import 'package:PiliPlus/pages/search/widgets/search_text.dart';
import 'package:PiliPlus/utils/grid.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
@@ -287,10 +288,15 @@ class _LivePageState extends CommonPageState<LivePage, LiveController>
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => PageUtils.toLiveRoom(item.roomid),
onLongPress: () {
Feedback.forLongPress(context);
Get.toNamed('/member?mid=${item.uid}');
},
onLongPress: Utils.isMobile
? () {
Feedback.forLongPress(context);
Get.toNamed('/member?mid=${item.uid}');
}
: null,
onSecondaryTap: Utils.isMobile
? null
: () => Get.toNamed('/member?mid=${item.uid}'),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [

View File

@@ -223,10 +223,15 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: controller.onLogin,
onLongPress: () {
Feedback.forLongPress(context);
controller.onLogin(true);
},
onLongPress: Utils.isMobile
? () {
Feedback.forLongPress(context);
controller.onLogin(true);
}
: null,
onSecondaryTap: Utils.isMobile
? null
: () => controller.onLogin(true),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [

View File

@@ -14,6 +14,7 @@ import 'package:PiliPlus/pages/pgc_review/post/view.dart';
import 'package:PiliPlus/utils/accounts.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/num_utils.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart';
@@ -108,6 +109,76 @@ class _PgcReviewChildPageState extends State<PgcReviewChildPage>
}
Widget _itemWidget(ThemeData theme, int index, PgcReviewItemModel item) {
void showMore() => showDialog(
context: context,
builder: (context) => AlertDialog(
clipBehavior: Clip.hardEdge,
contentPadding: const EdgeInsets.symmetric(vertical: 12),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (item.author!.mid == Accounts.main.mid) ...[
ListTile(
dense: true,
title: const Text(
'编辑',
style: TextStyle(fontSize: 14),
),
onTap: () {
Get.back();
showModalBottomSheet(
context: context,
useSafeArea: true,
isScrollControlled: true,
builder: (context) {
return PgcReviewPostPanel(
name: widget.name,
mediaId: widget.mediaId,
reviewId: item.reviewId,
content: item.content,
score: item.score,
);
},
);
},
),
ListTile(
dense: true,
title: const Text(
'删除',
style: TextStyle(fontSize: 14),
),
onTap: () {
Get.back();
showConfirmDialog(
context: context,
title: '删除短评,同时删除评分?',
onConfirm: () => _controller.onDel(index, item.reviewId),
);
},
),
],
ListTile(
dense: true,
title: const Text(
'举报',
style: TextStyle(fontSize: 14),
),
onTap: () => Get
..back()
..toNamed(
'/webview',
parameters: {
'url':
'https://www.bilibili.com/appeal/?reviewId=${item.reviewId}&type=shortComment&mediaId=${widget.mediaId}',
},
),
),
],
),
),
);
return Material(
type: MaterialType.transparency,
child: InkWell(
@@ -120,78 +191,8 @@ class _PgcReviewChildPageState extends State<PgcReviewChildPage>
},
)
: null,
onLongPress: isLongReview
? null
: () => showDialog(
context: context,
builder: (context) => AlertDialog(
clipBehavior: Clip.hardEdge,
contentPadding: const EdgeInsets.symmetric(vertical: 12),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (item.author!.mid == Accounts.main.mid) ...[
ListTile(
dense: true,
title: const Text(
'编辑',
style: TextStyle(fontSize: 14),
),
onTap: () {
Get.back();
showModalBottomSheet(
context: context,
useSafeArea: true,
isScrollControlled: true,
builder: (context) {
return PgcReviewPostPanel(
name: widget.name,
mediaId: widget.mediaId,
reviewId: item.reviewId,
content: item.content,
score: item.score,
);
},
);
},
),
ListTile(
dense: true,
title: const Text(
'删除',
style: TextStyle(fontSize: 14),
),
onTap: () {
Get.back();
showConfirmDialog(
context: context,
title: '删除短评,同时删除评分?',
onConfirm: () =>
_controller.onDel(index, item.reviewId),
);
},
),
],
ListTile(
dense: true,
title: const Text(
'举报',
style: TextStyle(fontSize: 14),
),
onTap: () => Get
..back()
..toNamed(
'/webview',
parameters: {
'url':
'https://www.bilibili.com/appeal/?reviewId=${item.reviewId}&type=shortComment&mediaId=${widget.mediaId}',
},
),
),
],
),
),
),
onLongPress: !isLongReview && Utils.isMobile ? showMore : null,
onSecondaryTap: !isLongReview && !Utils.isMobile ? showMore : null,
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(

View File

@@ -1,3 +1,4 @@
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
class SearchText extends StatelessWidget {
@@ -25,12 +26,18 @@ class SearchText extends StatelessWidget {
@override
Widget build(BuildContext context) {
late final colorScheme = Theme.of(context).colorScheme;
final hasLongPress = onLongPress != null;
return Material(
color: bgColor ?? colorScheme.onInverseSurface,
borderRadius: const BorderRadius.all(Radius.circular(6)),
child: InkWell(
onTap: () => onTap?.call(text),
onLongPress: onLongPress != null ? () => onLongPress!(text) : null,
onLongPress: hasLongPress && Utils.isMobile
? () => onLongPress!(text)
: null,
onSecondaryTap: hasLongPress && !Utils.isMobile
? () => onLongPress!(text)
: null,
borderRadius: const BorderRadius.all(Radius.circular(6)),
child: Padding(
padding:

View File

@@ -78,6 +78,25 @@ class ReplyItemGrpc extends StatelessWidget {
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final isMobile = Utils.isMobile;
void showMore() => showModalBottomSheet(
context: context,
useSafeArea: true,
isScrollControlled: true,
constraints: BoxConstraints(
maxWidth: min(640, context.mediaQueryShortestSide),
),
builder: (context) {
return morePanel(
context: context,
item: replyItem,
onDelete: () => onDelete?.call(replyItem, null),
isSubReply: false,
);
},
);
return Material(
type: MaterialType.transparency,
child: InkWell(
@@ -85,25 +104,13 @@ class ReplyItemGrpc extends StatelessWidget {
feedBack();
replyReply?.call(replyItem, null);
},
onLongPress: () {
feedBack();
showModalBottomSheet(
context: context,
useSafeArea: true,
isScrollControlled: true,
constraints: BoxConstraints(
maxWidth: min(640, context.mediaQueryShortestSide),
),
builder: (context) {
return morePanel(
context: context,
item: replyItem,
onDelete: () => onDelete?.call(replyItem, null),
isSubReply: false,
);
},
);
},
onLongPress: isMobile
? () {
feedBack();
showMore();
}
: null,
onSecondaryTap: isMobile ? null : showMore,
child: _buildContent(context, theme),
),
);

View File

@@ -16,6 +16,7 @@ import 'package:PiliPlus/utils/duration_utils.dart';
import 'package:PiliPlus/utils/id_utils.dart';
import 'package:PiliPlus/utils/image_utils.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
@@ -83,12 +84,13 @@ class ChatItem extends StatelessWidget {
textColor: textColor,
)
: GestureDetector(
onLongPress: onLongPress == null
? null
: () {
onLongPress: onLongPress != null && Utils.isMobile
? () {
Feedback.forLongPress(context);
onLongPress!();
},
}
: null,
onSecondaryTap: !Utils.isMobile ? onLongPress : null,
child: Row(
mainAxisAlignment: isOwner
? MainAxisAlignment.end

View File

@@ -411,11 +411,17 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
),
),
onTap: widget.showViewPoints,
onLongPress: () {
Feedback.forLongPress(context);
videoDetailController.showVP.value =
!videoDetailController.showVP.value;
},
onLongPress: Utils.isMobile
? () {
Feedback.forLongPress(context);
videoDetailController.showVP.value =
!videoDetailController.showVP.value;
}
: null,
onSecondaryTap: Utils.isMobile
? null
: () => videoDetailController.showVP.value =
!videoDetailController.showVP.value,
),
),

View File

@@ -4,6 +4,7 @@ class ComBtn extends StatelessWidget {
final Widget icon;
final VoidCallback? onTap;
final VoidCallback? onLongPress;
final VoidCallback? onSecondaryTap;
final double width;
final double height;
final String? tooltip;
@@ -13,6 +14,7 @@ class ComBtn extends StatelessWidget {
required this.icon,
this.onTap,
this.onLongPress,
this.onSecondaryTap,
this.width = 34,
this.height = 34,
this.tooltip,
@@ -26,6 +28,7 @@ class ComBtn extends StatelessWidget {
child: GestureDetector(
onTap: onTap,
onLongPress: onLongPress,
onSecondaryTap: onSecondaryTap,
behavior: HitTestBehavior.opaque,
child: icon,
),