context menu

This commit is contained in:
quantulr
2023-05-17 00:04:36 +08:00
parent eb918803d4
commit bfaff7f7fa
6 changed files with 277 additions and 143 deletions

View File

@ -0,0 +1,97 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
typedef ContextMenuBuilder = Widget Function(
BuildContext context, Offset offset);
/// Shows and hides the context menu based on user gestures.
///
/// By default, shows the menu on right clicks and long presses.
class ContextMenuRegion extends StatefulWidget {
/// Creates an instance of [ContextMenuRegion].
const ContextMenuRegion({
super.key,
required this.child,
required this.contextMenuBuilder,
});
/// Builds the context menu.
final ContextMenuBuilder contextMenuBuilder;
/// The child widget that will be listened to for gestures.
final Widget child;
@override
State<ContextMenuRegion> createState() => _ContextMenuRegionState();
}
class _ContextMenuRegionState extends State<ContextMenuRegion> {
Offset? _longPressOffset;
final ContextMenuController _contextMenuController = ContextMenuController();
static bool get _longPressEnabled {
switch (defaultTargetPlatform) {
case TargetPlatform.android:
case TargetPlatform.iOS:
return true;
case TargetPlatform.macOS:
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
return false;
}
}
void _onSecondaryTapUp(TapUpDetails details) {
_show(details.globalPosition);
}
void _onTap() {
if (!_contextMenuController.isShown) {
return;
}
_hide();
}
void _onLongPressStart(LongPressStartDetails details) {
_longPressOffset = details.globalPosition;
}
void _onLongPress() {
assert(_longPressOffset != null);
_show(_longPressOffset!);
_longPressOffset = null;
}
void _show(Offset position) {
_contextMenuController.show(
context: context,
contextMenuBuilder: (context) {
return widget.contextMenuBuilder(context, position);
},
);
}
void _hide() {
_contextMenuController.remove();
}
@override
void dispose() {
_hide();
super.dispose();
}
@override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onSecondaryTapUp: _onSecondaryTapUp,
onTap: _onTap,
onLongPress: _longPressEnabled ? _onLongPress : null,
onLongPressStart: _longPressEnabled ? _onLongPressStart : null,
child: widget.child,
);
}
}

View File

@ -1,5 +1,6 @@
import 'dart:io';
// import 'dart:html' as html;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@ -30,7 +31,7 @@ void main() async {
await windowManager.focus();
});
}
}else{
} else {
dio.options.baseUrl = '/api';
}
final prefs = await SharedPreferences.getInstance();

View File

@ -5,14 +5,15 @@ import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import 'package:momo/lib/context_menu_region.dart';
import 'package:momo/models/image_list_resp.dart';
import 'package:momo/models/image_resp.dart';
import 'package:momo/provider/rerender.dart';
import 'package:momo/request/http_client.dart';
/// A builder that includes an Offset to draw the context menu at.
typedef ContextMenuBuilder = Widget Function(
BuildContext context, Offset offset);
// typedef ContextMenuBuilder = Widget Function(
// BuildContext context, Offset offset);
class Gallery extends ConsumerWidget {
const Gallery({Key? key}) : super(key: key);
@ -92,118 +93,151 @@ class ImageItem extends ConsumerStatefulWidget {
}
class _ImageItemState extends ConsumerState<ImageItem> {
final ContextMenuController _contextMenuController = ContextMenuController();
// final ContextMenuController _contextMenuController = ContextMenuController();
@override
void dispose() {
// TODO: implement dispose
if (_contextMenuController.isShown) {
_contextMenuController.remove();
}
// if (_contextMenuController.isShown) {
// _contextMenuController.remove();
// }
super.dispose();
}
void _showContextMenu(Offset position) {
_contextMenuController.show(
context: context,
contextMenuBuilder: (BuildContext context) {
return AdaptiveTextSelectionToolbar.buttonItems(
anchors: TextSelectionToolbarAnchors(
primaryAnchor: position,
),
buttonItems: <ContextMenuButtonItem>[
ContextMenuButtonItem(
onPressed: () {
ContextMenuController.removeAny();
showDialog(
context: context,
builder: (BuildContext context) => AlertDialog(
title: const Text('删除图片'),
content: const Text("确认删除图片"),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, 'Cancel'),
child: const Text('Cancel'),
),
TextButton(
onPressed: () {
Navigator.pop(context, 'OK');
dio
.delete("/image/delete/${widget.image.id}")
.then((resp) {
ref
.read(uniqueIdProvider.notifier)
.updateId();
// context.go("/");
});
},
child: const Text('OK'),
),
],
));
// _showDialog(context);
},
label: '删除',
),
ContextMenuButtonItem(
onPressed: () {
ContextMenuController.removeAny();
// _showDialog(context);
Clipboard.setData(ClipboardData(
text:
"${dio.options.baseUrl}/image/${widget.image.file_path}"));
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(
Icons.check_circle,
color: Colors.white,
),
SizedBox(
width: 10,
),
Text("已拷贝到剪贴板")
],
),
backgroundColor: Colors.lightGreen,
));
},
label: '复制链接',
),
ContextMenuButtonItem(
onPressed: () {
ContextMenuController.removeAny();
// _showDialog(context);
},
label: '查看详情',
),
],
);
},
);
}
// void _showContextMenu(Offset position) {
// _contextMenuController.show(
// context: context,
// contextMenuBuilder: (BuildContext context) {
// return AdaptiveTextSelectionToolbar.buttonItems(
// anchors: TextSelectionToolbarAnchors(
// primaryAnchor: position,
// ),
// buttonItems: <ContextMenuButtonItem>[
// ContextMenuButtonItem(
// onPressed: () {
// ContextMenuController.removeAny();
// showDialog(
// context: context,
// builder: (BuildContext context) =>
// AlertDialog(
// title: const Text('删除图片'),
// content: const Text("确认删除图片"),
// actions: [
// TextButton(
// onPressed: () => Navigator.pop(context, 'Cancel'),
// child: const Text('Cancel'),
// ),
// TextButton(
// onPressed: () {
// Navigator.pop(context, 'OK');
// dio
// .delete("/image/delete/${widget.image.id}")
// .then((resp) {
// ref
// .read(uniqueIdProvider.notifier)
// .updateId();
// // context.go("/");
// });
// },
// child: const Text('OK'),
// ),
// ],
// ));
// // _showDialog(context);
// },
// label: '删除',
// ),
// ContextMenuButtonItem(
// onPressed: () {
// ContextMenuController.removeAny();
// // _showDialog(context);
// Clipboard.setData(ClipboardData(
// text:
// "${dio.options.baseUrl}/image/${widget.image.file_path}"));
// ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
// content: Row(
// crossAxisAlignment: CrossAxisAlignment.center,
// children: [
// Icon(
// Icons.check_circle,
// color: Colors.white,
// ),
// SizedBox(
// width: 10,
// ),
// Text("已拷贝到剪贴板")
// ],
// ),
// backgroundColor: Colors.lightGreen,
// ));
// },
// label: '复制链接',
// ),
// ContextMenuButtonItem(
// onPressed: () {
// ContextMenuController.removeAny();
// // _showDialog(context);
// },
// label: '查看详情',
// ),
// ],
// );
// },
// );
// }
@override
Widget build(BuildContext context) {
return GestureDetector(
onSecondaryTapUp: (TapUpDetails details) {
_showContextMenu(details.globalPosition);
},
onTap: () {
context.go(Uri(
path: "/detail",
queryParameters: {"id": "${widget.image.id}"}).toString());
},
child: Image.network(
kIsWeb
? "/images-api/?url=${dio.options.baseUrl}/image/${widget.image.file_path}&w=512"
: 'https://raichi.hodokencho.com/images-api/?url=${dio.options.baseUrl}/image/${widget.image.file_path}&w=512',
fit: BoxFit.cover,
errorBuilder: (BuildContext context, o, err) {
return const Icon(Icons.error_outline);
return ContextMenuRegion(
contextMenuBuilder: (BuildContext context, Offset offset) {
return AdaptiveTextSelectionToolbar.buttonItems(
anchors: TextSelectionToolbarAnchors(
primaryAnchor: offset,
),
buttonItems: <ContextMenuButtonItem>[
ContextMenuButtonItem(
onPressed: () {
ContextMenuController.removeAny();
Clipboard.setData(ClipboardData(
text:
"https://raichi.hodokencho.com/api/image/${widget.image.file_path}"));
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(
Icons.check_circle,
color: Colors.white,
),
SizedBox(
width: 10,
),
Text("已拷贝到剪贴板")
],
),
backgroundColor: Colors.lightGreen,
));
},
label: '复制链接',
),
],
);
},
),
);
child: GestureDetector(
onTap: () {
context.go(Uri(
path: "/detail",
queryParameters: {"id": "${widget.image.id}"}).toString());
},
child: Image.network(
kIsWeb
? "/images-api/?url=${dio.options.baseUrl}/image/${widget.image.file_path}&w=512"
: 'https://raichi.hodokencho.com/images-api/?url=${dio.options.baseUrl}/image/${widget.image.file_path}&w=512',
fit: BoxFit.cover,
errorBuilder: (BuildContext context, o, err) {
return const Icon(Icons.error_outline);
},
),
));
}
}

View File

@ -169,14 +169,6 @@ packages:
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted
version: "4.1.0"
context_menus:
dependency: "direct main"
description:
name: context_menus
sha256: "25313f2a17dc936f541f8012761648cb58d936c5d6f6bf7282f137a4b9dedddb"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted
version: "1.0.2"
convert:
dependency: transitive
description:

View File

@ -49,7 +49,6 @@ dependencies:
dio: ^5.0.0
http_parser: ^4.0.2
mime: ^1.0.4
context_menus: ^1.0.2
extended_image:
git:
url: https://github.com/fluttercandies/extended_image.git

View File

@ -1,46 +1,49 @@
<!DOCTYPE html>
<html>
<head>
<!--
If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from.
<!--
If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from.
The path provided below has to start and end with a slash "/" in order for
it to work correctly.
The path provided below has to start and end with a slash "/" in order for
it to work correctly.
For more details:
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
For more details:
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
This is a placeholder for base href that will be replaced by the value of
the `--base-href` argument provided to `flutter build`.
-->
<base href="$FLUTTER_BASE_HREF">
This is a placeholder for base href that will be replaced by the value of
the `--base-href` argument provided to `flutter build`.
-->
<base href="$FLUTTER_BASE_HREF">
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A new Flutter project.">
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A new Flutter project.">
<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="momo">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="momo">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/>
<!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/>
<title>momo</title>
<link rel="manifest" href="manifest.json">
<title>momo</title>
<link rel="manifest" href="manifest.json">
<script>
<script>
// The value below is injected by flutter build, do not touch.
var serviceWorkerVersion = null;
</script>
<!-- This script adds the flutter initialization JS code -->
<script src="flutter.js" defer></script>
</script>
<!-- This script adds the flutter initialization JS code -->
<script src="flutter.js" defer></script>
</head>
<body>
<script>
<script>
window.addEventListener('load', function(ev) {
// Download main.dart.js
_flutter.loader.loadEntrypoint({
@ -53,6 +56,14 @@
return appRunner.runApp();
});
});
</script>
</script>
<script>
document.body.addEventListener('contextmenu', (event) => {
event.preventDefault();
});
</script>
</body>
</html>