mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-08-04 23:12:58 +10:00
Merge pull request #479 from MrSurly/478-implement-right-click
Issue 478: Implement right click for desktop use
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
@@ -20,6 +21,7 @@ class LinkHandler {
|
||||
required String text,
|
||||
required TextStyle style,
|
||||
TextStyle? linkStyle,
|
||||
VoidCallback? onSecondaryTap,
|
||||
}) {
|
||||
final effectiveLinkStyle = linkStyle ?? defaultLinkStyle(context, style);
|
||||
const options = LinkifyOptions(humanize: false, defaultToHttps: false);
|
||||
@@ -27,7 +29,7 @@ class LinkHandler {
|
||||
void onOpen(LinkableElement link) => handleLinkTap(context, link.url);
|
||||
|
||||
if (PlatformInfo.isDesktop) {
|
||||
return SelectableLinkify(
|
||||
final linkify = SelectableLinkify(
|
||||
text: text,
|
||||
style: style,
|
||||
linkStyle: effectiveLinkStyle,
|
||||
@@ -35,6 +37,14 @@ class LinkHandler {
|
||||
linkifiers: linkifiers,
|
||||
onOpen: onOpen,
|
||||
);
|
||||
if (onSecondaryTap == null) return linkify;
|
||||
return Listener(
|
||||
onPointerDown: (event) {
|
||||
if (event.buttons & kSecondaryMouseButton != 0) onSecondaryTap();
|
||||
},
|
||||
behavior: HitTestBehavior.translucent,
|
||||
child: linkify,
|
||||
);
|
||||
}
|
||||
return Linkify(
|
||||
text: text,
|
||||
|
||||
@@ -110,8 +110,7 @@ class _BleDebugLogScreenState extends State<BleDebugLogScreen> {
|
||||
final entry = entries[index];
|
||||
final time =
|
||||
'${entry.timestamp.hour.toString().padLeft(2, '0')}:${entry.timestamp.minute.toString().padLeft(2, '0')}:${entry.timestamp.second.toString().padLeft(2, '0')}';
|
||||
return GestureDetector(
|
||||
onLongPress: () async {
|
||||
Future<void> copyHex() async {
|
||||
await Clipboard.setData(
|
||||
ClipboardData(
|
||||
text: entry.payload
|
||||
@@ -123,7 +122,20 @@ class _BleDebugLogScreenState extends State<BleDebugLogScreen> {
|
||||
.join(''),
|
||||
),
|
||||
);
|
||||
},
|
||||
if (context.mounted) {
|
||||
showDismissibleSnackBar(
|
||||
context,
|
||||
content: Text(
|
||||
context.l10n.debugLog_bleCopied,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
onTap: copyHex,
|
||||
onLongPress: copyHex,
|
||||
onSecondaryTap: copyHex,
|
||||
child: Container(
|
||||
color: MeshPalette.bg,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
|
||||
@@ -646,6 +646,9 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
fontStyle: FontStyle.italic,
|
||||
color: textColor.withValues(alpha: 0.72),
|
||||
),
|
||||
onSecondaryTap: PlatformInfo.isDesktop
|
||||
? () => _showMessageActions(message)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -429,9 +429,8 @@ class _ChannelsScreenState extends State<ChannelsScreen>
|
||||
channelMessageStore,
|
||||
channel,
|
||||
),
|
||||
child: GestureDetector(
|
||||
onSecondaryTapUp: PlatformInfo.isDesktop
|
||||
? (_) => _showChannelActions(
|
||||
onSecondaryTap: PlatformInfo.isDesktop
|
||||
? () => _showChannelActions(
|
||||
this.context,
|
||||
connector,
|
||||
channelMessageStore,
|
||||
@@ -566,7 +565,6 @@ class _ChannelsScreenState extends State<ChannelsScreen>
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1435,6 +1435,9 @@ class _MessageBubble extends StatelessWidget {
|
||||
color: textColor.withValues(alpha: 0.72),
|
||||
fontSize: bodyFontSize * textScale,
|
||||
),
|
||||
onSecondaryTap: PlatformInfo.isDesktop
|
||||
? onLongPress
|
||||
: null,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -147,13 +147,6 @@ class _DiscoveryScreenState extends State<DiscoveryScreen> {
|
||||
connector,
|
||||
index,
|
||||
);
|
||||
if (PlatformInfo.isDesktop) {
|
||||
return GestureDetector(
|
||||
onSecondaryTapUp: (_) =>
|
||||
_showContactContextMenu(contact, connector),
|
||||
child: tile,
|
||||
);
|
||||
}
|
||||
return tile;
|
||||
},
|
||||
),
|
||||
@@ -204,6 +197,9 @@ class _DiscoveryScreenState extends State<DiscoveryScreen> {
|
||||
}
|
||||
},
|
||||
onLongPress: () => _showContactContextMenu(contact, connector),
|
||||
onSecondaryTap: PlatformInfo.isDesktop
|
||||
? () => _showContactContextMenu(contact, connector)
|
||||
: null,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
|
||||
child: Row(
|
||||
children: [
|
||||
|
||||
@@ -430,6 +430,7 @@ class _LineOfSightMapScreenState extends State<LineOfSightMapScreen> {
|
||||
minZoom: _mapMinZoom,
|
||||
maxZoom: _mapMaxZoom,
|
||||
onLongPress: (_, point) => _addCustomPoint(point),
|
||||
onSecondaryTap: (_, point) => _addCustomPoint(point),
|
||||
onPositionChanged: (camera, hasGesture) {
|
||||
final shouldShow = camera.zoom >= _labelZoomThreshold;
|
||||
if (!_didReceivePositionUpdate ||
|
||||
|
||||
+41
-23
@@ -285,6 +285,31 @@ class _MapScreenState extends State<MapScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
void _handleMapContextPress(
|
||||
BuildContext context,
|
||||
MeshCoreConnector connector,
|
||||
LatLng latLng,
|
||||
) {
|
||||
if (_isSelectingPoi) {
|
||||
setState(() {
|
||||
_isSelectingPoi = false;
|
||||
});
|
||||
_shareMarker(
|
||||
context: context,
|
||||
connector: connector,
|
||||
position: latLng,
|
||||
defaultLabel: context.l10n.map_pointOfInterest,
|
||||
flags: 'poi',
|
||||
);
|
||||
return;
|
||||
}
|
||||
_showShareMarkerAtPositionSheet(
|
||||
context: context,
|
||||
connector: connector,
|
||||
position: latLng,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Builder(
|
||||
@@ -708,24 +733,10 @@ class _MapScreenState extends State<MapScreen> {
|
||||
}
|
||||
},
|
||||
onLongPress: (_, latLng) {
|
||||
if (_isSelectingPoi) {
|
||||
setState(() {
|
||||
_isSelectingPoi = false;
|
||||
});
|
||||
_shareMarker(
|
||||
context: context,
|
||||
connector: connector,
|
||||
position: latLng,
|
||||
defaultLabel: context.l10n.map_pointOfInterest,
|
||||
flags: 'poi',
|
||||
);
|
||||
return;
|
||||
}
|
||||
_showShareMarkerAtPositionSheet(
|
||||
context: context,
|
||||
connector: connector,
|
||||
position: latLng,
|
||||
);
|
||||
_handleMapContextPress(context, connector, latLng);
|
||||
},
|
||||
onSecondaryTap: (_, latLng) {
|
||||
_handleMapContextPress(context, connector, latLng);
|
||||
},
|
||||
onPositionChanged: (camera, hasGesture) {
|
||||
// Track zoom in half-step buckets so cluster/marker
|
||||
@@ -1181,9 +1192,12 @@ class _MapScreenState extends State<MapScreen> {
|
||||
width: 48,
|
||||
height: 48,
|
||||
child: GestureDetector(
|
||||
onLongPress: () => _isBuildingPathTrace
|
||||
? _showNodeInfo(context, guess.contact)
|
||||
: null,
|
||||
onLongPress: () {
|
||||
if (_isBuildingPathTrace) _showNodeInfo(context, guess.contact);
|
||||
},
|
||||
onSecondaryTap: () {
|
||||
if (_isBuildingPathTrace) _showNodeInfo(context, guess.contact);
|
||||
},
|
||||
onTap: () => _isBuildingPathTrace
|
||||
? _addToPath(context, guess.contact, position: guess.position)
|
||||
: _selectNode(guess.contact, guessedPosition: guess.position),
|
||||
@@ -1383,8 +1397,12 @@ class _MapScreenState extends State<MapScreen> {
|
||||
height: size,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onLongPress: () =>
|
||||
_isBuildingPathTrace ? _showNodeInfo(context, contact) : null,
|
||||
onLongPress: () {
|
||||
if (_isBuildingPathTrace) _showNodeInfo(context, contact);
|
||||
},
|
||||
onSecondaryTap: () {
|
||||
if (_isBuildingPathTrace) _showNodeInfo(context, contact);
|
||||
},
|
||||
onTap: () => _isBuildingPathTrace
|
||||
? _addToPath(context, contact)
|
||||
: _selectNode(contact),
|
||||
|
||||
@@ -50,6 +50,7 @@ class MeshCard extends StatelessWidget {
|
||||
final Widget child;
|
||||
final VoidCallback? onTap;
|
||||
final VoidCallback? onLongPress;
|
||||
final VoidCallback? onSecondaryTap;
|
||||
final EdgeInsetsGeometry padding;
|
||||
final EdgeInsetsGeometry margin;
|
||||
final Color? color;
|
||||
@@ -61,6 +62,7 @@ class MeshCard extends StatelessWidget {
|
||||
required this.child,
|
||||
this.onTap,
|
||||
this.onLongPress,
|
||||
this.onSecondaryTap,
|
||||
this.padding = const EdgeInsets.all(14),
|
||||
this.margin = const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
this.color,
|
||||
@@ -89,6 +91,7 @@ class MeshCard extends StatelessWidget {
|
||||
HapticFeedback.selectionClick();
|
||||
onLongPress!();
|
||||
},
|
||||
onSecondaryTap: onSecondaryTap,
|
||||
child: Padding(padding: padding, child: child),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../connector/meshcore_connector.dart';
|
||||
import '../utils/platform_info.dart';
|
||||
import '../helpers/path_helper.dart';
|
||||
import '../l10n/l10n.dart';
|
||||
import '../models/contact.dart';
|
||||
@@ -534,7 +535,13 @@ class _RoutingSheetBodyState extends State<_RoutingSheetBody> {
|
||||
l10n.routing_deliveryCounts(record.successCount, record.failureCount),
|
||||
];
|
||||
|
||||
return Card(
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onSecondaryTapUp: PlatformInfo.isDesktop && hasBytes
|
||||
? (_) =>
|
||||
_showPathDetail(context, connector, contact, record.pathBytes)
|
||||
: null,
|
||||
child: Card(
|
||||
margin: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: ListTile(
|
||||
enabled: hasBytes,
|
||||
@@ -581,10 +588,15 @@ class _RoutingSheetBodyState extends State<_RoutingSheetBody> {
|
||||
? () => _applyHistoryPath(connector, contact, record)
|
||||
: null,
|
||||
onLongPress: hasBytes
|
||||
? () =>
|
||||
_showPathDetail(context, connector, contact, record.pathBytes)
|
||||
? () => _showPathDetail(
|
||||
context,
|
||||
connector,
|
||||
contact,
|
||||
record.pathBytes,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ class TranslatedMessageContent extends StatelessWidget {
|
||||
final TextStyle style;
|
||||
final TextStyle? originalStyle;
|
||||
final bool showOriginalFirst;
|
||||
final VoidCallback? onSecondaryTap;
|
||||
|
||||
const TranslatedMessageContent({
|
||||
super.key,
|
||||
@@ -16,6 +17,7 @@ class TranslatedMessageContent extends StatelessWidget {
|
||||
this.originalText,
|
||||
this.originalStyle,
|
||||
this.showOriginalFirst = true,
|
||||
this.onSecondaryTap,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -36,12 +38,14 @@ class TranslatedMessageContent extends StatelessWidget {
|
||||
fontStyle: FontStyle.italic,
|
||||
fontSize: style.fontSize,
|
||||
),
|
||||
onSecondaryTap: onSecondaryTap,
|
||||
)
|
||||
: null;
|
||||
final translatedWidget = LinkHandler.buildLinkifyText(
|
||||
context: context,
|
||||
text: trimmedDisplay,
|
||||
style: style,
|
||||
onSecondaryTap: onSecondaryTap,
|
||||
);
|
||||
|
||||
if (!shouldShowOriginal) {
|
||||
|
||||
Reference in New Issue
Block a user