import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; typedef ContextMenuBuilder = Widget Function(BuildContext context, Offset offset); class ContextMenuRegion extends HookWidget { final Offset? mobileAnchor; final Widget child; final ContextMenuBuilder contextMenuBuilder; const ContextMenuRegion({ super.key, required this.child, required this.contextMenuBuilder, this.mobileAnchor, }); @override Widget build(BuildContext context) { final contextMenuController = useMemoized(() => ContextMenuController()); final mobileOffset = useState(null); bool canBeTouchScreen = switch (defaultTargetPlatform) { TargetPlatform.android || TargetPlatform.iOS => true, _ => false, }; void showMenu(Offset position) { contextMenuController.show( context: context, contextMenuBuilder: (BuildContext context) { return contextMenuBuilder(context, position); }, ); } void hideMenu() { contextMenuController.remove(); } void onSecondaryTapUp(TapUpDetails details) { showMenu(details.globalPosition); } void onTap() { if (!contextMenuController.isShown) { return; } hideMenu(); } void onLongPressStart(LongPressStartDetails details) { mobileOffset.value = details.globalPosition; } void onLongPress() { assert(mobileOffset.value != null); showMenu(mobileAnchor ?? mobileOffset.value!); mobileOffset.value = null; } useEffect(() { return () { hideMenu(); }; }, []); return TapRegion( behavior: HitTestBehavior.opaque, child: GestureDetector( behavior: HitTestBehavior.opaque, onSecondaryTapUp: onSecondaryTapUp, onTap: onTap, onLongPress: canBeTouchScreen ? onLongPress : null, onLongPressStart: canBeTouchScreen ? onLongPressStart : null, child: child, ), onTapOutside: (_) { hideMenu(); }, ); } }