From 06285a02ff4600ce47b92929460ba46691b88b91 Mon Sep 17 00:00:00 2001 From: Eric Poulsen Date: Sat, 13 Jun 2026 16:36:55 -0700 Subject: [PATCH 01/14] onSecondaryTap for map_screen --- lib/screens/map_screen.dart | 48 +++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/lib/screens/map_screen.dart b/lib/screens/map_screen.dart index 3f57dfac..36b536e1 100644 --- a/lib/screens/map_screen.dart +++ b/lib/screens/map_screen.dart @@ -285,6 +285,27 @@ class _MapScreenState extends State { ); } + void _handleMapTap(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 +729,10 @@ class _MapScreenState extends State { } }, 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, - ); + _handleMapTap(context, connector, latLng); + }, + onSecondaryTap: (_, latLng) { + _handleMapTap(context, connector, latLng); }, onPositionChanged: (camera, hasGesture) { // Track zoom in half-step buckets so cluster/marker @@ -1184,6 +1191,9 @@ class _MapScreenState extends State { onLongPress: () => _isBuildingPathTrace ? _showNodeInfo(context, guess.contact) : null, + onSecondaryTap: () => _isBuildingPathTrace + ? _showNodeInfo(context, guess.contact) + : null, onTap: () => _isBuildingPathTrace ? _addToPath(context, guess.contact, position: guess.position) : _selectNode(guess.contact, guessedPosition: guess.position), @@ -1385,6 +1395,8 @@ class _MapScreenState extends State { behavior: HitTestBehavior.opaque, onLongPress: () => _isBuildingPathTrace ? _showNodeInfo(context, contact) : null, + onSecondaryTap: () => + _isBuildingPathTrace ? _showNodeInfo(context, contact) : null, onTap: () => _isBuildingPathTrace ? _addToPath(context, contact) : _selectNode(contact), From c8672250733f1735d69e81c18ffc59f6ab000f88 Mon Sep 17 00:00:00 2001 From: Eric Poulsen Date: Sat, 13 Jun 2026 16:37:12 -0700 Subject: [PATCH 02/14] onSecondaryTap for line_of_sight_map_screen --- lib/screens/line_of_sight_map_screen.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/screens/line_of_sight_map_screen.dart b/lib/screens/line_of_sight_map_screen.dart index 57d7603f..fe935e43 100644 --- a/lib/screens/line_of_sight_map_screen.dart +++ b/lib/screens/line_of_sight_map_screen.dart @@ -430,6 +430,7 @@ class _LineOfSightMapScreenState extends State { minZoom: _mapMinZoom, maxZoom: _mapMaxZoom, onLongPress: (_, point) => _addCustomPoint(point), + onSecondaryTap: (_, point) => _addCustomPoint(point), onPositionChanged: (camera, hasGesture) { final shouldShow = camera.zoom >= _labelZoomThreshold; if (!_didReceivePositionUpdate || From f1478722b02aa539fafd1bdf2c3c13d24e1fcca2 Mon Sep 17 00:00:00 2001 From: Eric Poulsen Date: Sat, 13 Jun 2026 19:05:52 -0700 Subject: [PATCH 03/14] Fix for right-click on message bubbles (right click didn't work on the actual text of the bubble) --- lib/helpers/link_handler.dart | 11 ++++++++++- lib/screens/channel_chat_screen.dart | 3 +++ lib/screens/chat_screen.dart | 3 +++ lib/widgets/translated_message_content.dart | 4 ++++ 4 files changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/helpers/link_handler.dart b/lib/helpers/link_handler.dart index c2eae294..b1c97855 100644 --- a/lib/helpers/link_handler.dart +++ b/lib/helpers/link_handler.dart @@ -20,6 +20,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 +28,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 +36,14 @@ class LinkHandler { linkifiers: linkifiers, onOpen: onOpen, ); + if (onSecondaryTap == null) return linkify; + return Listener( + onPointerDown: (event) { + if (event.buttons == 2) onSecondaryTap(); + }, + behavior: HitTestBehavior.translucent, + child: linkify, + ); } return Linkify( text: text, diff --git a/lib/screens/channel_chat_screen.dart b/lib/screens/channel_chat_screen.dart index 94101ef3..7722c069 100644 --- a/lib/screens/channel_chat_screen.dart +++ b/lib/screens/channel_chat_screen.dart @@ -646,6 +646,9 @@ class _ChannelChatScreenState extends State { fontStyle: FontStyle.italic, color: textColor.withValues(alpha: 0.72), ), + onSecondaryTap: PlatformInfo.isDesktop + ? () => _showMessageActions(message) + : null, ), ), ], diff --git a/lib/screens/chat_screen.dart b/lib/screens/chat_screen.dart index 5bdd0fdc..7eca042b 100644 --- a/lib/screens/chat_screen.dart +++ b/lib/screens/chat_screen.dart @@ -1435,6 +1435,9 @@ class _MessageBubble extends StatelessWidget { color: textColor.withValues(alpha: 0.72), fontSize: bodyFontSize * textScale, ), + onSecondaryTap: PlatformInfo.isDesktop + ? onLongPress + : null, ), ), ], diff --git a/lib/widgets/translated_message_content.dart b/lib/widgets/translated_message_content.dart index 3495897d..a40bacf8 100644 --- a/lib/widgets/translated_message_content.dart +++ b/lib/widgets/translated_message_content.dart @@ -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) { From f34c2a92c31841cc7d1ae8b8ecec08c5f6ba7963 Mon Sep 17 00:00:00 2001 From: Eric Poulsen Date: Sat, 13 Jun 2026 19:22:16 -0700 Subject: [PATCH 04/14] Add onSecondaryTap to MeshCard and fix right-click on message text bubbles --- lib/screens/channels_screen.dart | 20 +++++++++----------- lib/screens/discovery_screen.dart | 10 +++------- lib/widgets/mesh_ui.dart | 3 +++ 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/lib/screens/channels_screen.dart b/lib/screens/channels_screen.dart index 94bbd65e..8a6b990a 100644 --- a/lib/screens/channels_screen.dart +++ b/lib/screens/channels_screen.dart @@ -429,16 +429,15 @@ class _ChannelsScreenState extends State channelMessageStore, channel, ), - child: GestureDetector( - onSecondaryTapUp: PlatformInfo.isDesktop - ? (_) => _showChannelActions( - this.context, - connector, - channelMessageStore, - channel, - ) - : null, - child: Row( + onSecondaryTap: PlatformInfo.isDesktop + ? () => _showChannelActions( + this.context, + connector, + channelMessageStore, + channel, + ) + : null, + child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ // Leading avatar with optional community badge @@ -566,7 +565,6 @@ class _ChannelsScreenState extends State ], ), ), - ), ); } diff --git a/lib/screens/discovery_screen.dart b/lib/screens/discovery_screen.dart index 415562c3..4a782d6f 100644 --- a/lib/screens/discovery_screen.dart +++ b/lib/screens/discovery_screen.dart @@ -147,13 +147,6 @@ class _DiscoveryScreenState extends State { connector, index, ); - if (PlatformInfo.isDesktop) { - return GestureDetector( - onSecondaryTapUp: (_) => - _showContactContextMenu(contact, connector), - child: tile, - ); - } return tile; }, ), @@ -204,6 +197,9 @@ class _DiscoveryScreenState extends State { } }, onLongPress: () => _showContactContextMenu(contact, connector), + onSecondaryTap: PlatformInfo.isDesktop + ? () => _showContactContextMenu(contact, connector) + : null, padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), child: Row( children: [ diff --git a/lib/widgets/mesh_ui.dart b/lib/widgets/mesh_ui.dart index 797af7a7..cb4698bb 100644 --- a/lib/widgets/mesh_ui.dart +++ b/lib/widgets/mesh_ui.dart @@ -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), ), ), From a1e6f6967c57f00e38d0e03faee41dad92523555 Mon Sep 17 00:00:00 2001 From: Eric Poulsen Date: Sat, 13 Jun 2026 19:30:27 -0700 Subject: [PATCH 05/14] Added support for left-click copy, and snackbar message about copying the data. --- lib/screens/ble_debug_log_screen.dart | 35 +++++++++++++++++---------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/lib/screens/ble_debug_log_screen.dart b/lib/screens/ble_debug_log_screen.dart index c229c2e5..2972b22a 100644 --- a/lib/screens/ble_debug_log_screen.dart +++ b/lib/screens/ble_debug_log_screen.dart @@ -110,20 +110,29 @@ class _BleDebugLogScreenState extends State { 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 { - await Clipboard.setData( - ClipboardData( - text: entry.payload - .map( - (b) => b - .toRadixString(16) - .padLeft(2, '0'), - ) - .join(''), - ), + Future copyHex() async { + await Clipboard.setData( + ClipboardData( + text: entry.payload + .map( + (b) => b + .toRadixString(16) + .padLeft(2, '0'), + ) + .join(''), + ), + ); + if (context.mounted) { + showDismissibleSnackBar( + context, + content: Text(context.l10n.debugLog_bleCopied), ); - }, + } + } + + return GestureDetector( + onTap: copyHex, + onLongPress: copyHex, child: Container( color: MeshPalette.bg, padding: const EdgeInsets.symmetric( From 1c183d7e677f6094194f94090476c9b319ab28c6 Mon Sep 17 00:00:00 2001 From: Eric Poulsen Date: Sat, 13 Jun 2026 19:46:00 -0700 Subject: [PATCH 06/14] Add right-click support to routing sheet path history rows --- lib/widgets/routing_sheet.dart | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/lib/widgets/routing_sheet.dart b/lib/widgets/routing_sheet.dart index 4c1a26f6..cc3bc783 100644 --- a/lib/widgets/routing_sheet.dart +++ b/lib/widgets/routing_sheet.dart @@ -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,11 +535,19 @@ class _RoutingSheetBodyState extends State<_RoutingSheetBody> { l10n.routing_deliveryCounts(record.successCount, record.failureCount), ]; - return Card( - margin: const EdgeInsets.symmetric(vertical: 4), - child: ListTile( - enabled: hasBytes, - leading: CircleAvatar( + return Listener( + onPointerDown: PlatformInfo.isDesktop && hasBytes + ? (event) { + if (event.buttons == 2) { + _showPathDetail(context, connector, contact, record.pathBytes); + } + } + : null, + child: Card( + margin: const EdgeInsets.symmetric(vertical: 4), + child: ListTile( + enabled: hasBytes, + leading: CircleAvatar( radius: 18, backgroundColor: bg, child: Icon( @@ -577,13 +586,14 @@ class _RoutingSheetBodyState extends State<_RoutingSheetBody> { ), ], ), - onTap: hasBytes && !inUse - ? () => _applyHistoryPath(connector, contact, record) - : null, - onLongPress: hasBytes - ? () => - _showPathDetail(context, connector, contact, record.pathBytes) - : null, + onTap: hasBytes && !inUse + ? () => _applyHistoryPath(connector, contact, record) + : null, + onLongPress: hasBytes + ? () => + _showPathDetail(context, connector, contact, record.pathBytes) + : null, + ), ), ); } From 321d4b9775965eca8aeeeb897772cd2eb3f91e88 Mon Sep 17 00:00:00 2001 From: Eric Poulsen Date: Sat, 13 Jun 2026 20:23:32 -0700 Subject: [PATCH 07/14] Use kSecondaryMouseButton bitmask check, per code review --- lib/helpers/link_handler.dart | 3 ++- lib/widgets/routing_sheet.dart | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/helpers/link_handler.dart b/lib/helpers/link_handler.dart index b1c97855..980369da 100644 --- a/lib/helpers/link_handler.dart +++ b/lib/helpers/link_handler.dart @@ -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'; @@ -39,7 +40,7 @@ class LinkHandler { if (onSecondaryTap == null) return linkify; return Listener( onPointerDown: (event) { - if (event.buttons == 2) onSecondaryTap(); + if (event.buttons & kSecondaryMouseButton != 0) onSecondaryTap(); }, behavior: HitTestBehavior.translucent, child: linkify, diff --git a/lib/widgets/routing_sheet.dart b/lib/widgets/routing_sheet.dart index cc3bc783..6d87a0df 100644 --- a/lib/widgets/routing_sheet.dart +++ b/lib/widgets/routing_sheet.dart @@ -1,4 +1,5 @@ import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -538,7 +539,7 @@ class _RoutingSheetBodyState extends State<_RoutingSheetBody> { return Listener( onPointerDown: PlatformInfo.isDesktop && hasBytes ? (event) { - if (event.buttons == 2) { + if (event.buttons & kSecondaryMouseButton != 0) { _showPathDetail(context, connector, contact, record.pathBytes); } } From 80f4fd52188756d9f49bed19120f1bb1b7d8ed5e Mon Sep 17 00:00:00 2001 From: Eric Poulsen Date: Sat, 13 Jun 2026 20:26:38 -0700 Subject: [PATCH 08/14] Added HitTestBehavior.opaque, per code review comments --- lib/widgets/routing_sheet.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/widgets/routing_sheet.dart b/lib/widgets/routing_sheet.dart index 6d87a0df..e1ebbbf2 100644 --- a/lib/widgets/routing_sheet.dart +++ b/lib/widgets/routing_sheet.dart @@ -537,6 +537,7 @@ class _RoutingSheetBodyState extends State<_RoutingSheetBody> { ]; return Listener( + behavior: HitTestBehavior.opaque, onPointerDown: PlatformInfo.isDesktop && hasBytes ? (event) { if (event.buttons & kSecondaryMouseButton != 0) { From 300903656528e51780a3019f6e40fb68c4b78038 Mon Sep 17 00:00:00 2001 From: Eric Poulsen Date: Sat, 13 Jun 2026 20:27:58 -0700 Subject: [PATCH 09/14] Remove white space per code review --- lib/screens/map_screen.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/screens/map_screen.dart b/lib/screens/map_screen.dart index 36b536e1..aa0b14f2 100644 --- a/lib/screens/map_screen.dart +++ b/lib/screens/map_screen.dart @@ -1191,8 +1191,8 @@ class _MapScreenState extends State { onLongPress: () => _isBuildingPathTrace ? _showNodeInfo(context, guess.contact) : null, - onSecondaryTap: () => _isBuildingPathTrace - ? _showNodeInfo(context, guess.contact) + onSecondaryTap: () => _isBuildingPathTrace + ? _showNodeInfo(context, guess.contact) : null, onTap: () => _isBuildingPathTrace ? _addToPath(context, guess.contact, position: guess.position) From dbd3a40bdc73b8f4a3c63adcc434cb6f0d4b61c1 Mon Sep 17 00:00:00 2001 From: Eric Poulsen Date: Sat, 13 Jun 2026 20:29:36 -0700 Subject: [PATCH 10/14] Rename _handleMapTap to _handleMapContextPress, per review comments. --- lib/screens/map_screen.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/screens/map_screen.dart b/lib/screens/map_screen.dart index aa0b14f2..8b428420 100644 --- a/lib/screens/map_screen.dart +++ b/lib/screens/map_screen.dart @@ -285,7 +285,7 @@ class _MapScreenState extends State { ); } - void _handleMapTap(BuildContext context, MeshCoreConnector connector, LatLng latLng) { + void _handleMapContextPress(BuildContext context, MeshCoreConnector connector, LatLng latLng) { if (_isSelectingPoi) { setState(() { _isSelectingPoi = false; @@ -729,10 +729,10 @@ class _MapScreenState extends State { } }, onLongPress: (_, latLng) { - _handleMapTap(context, connector, latLng); + _handleMapContextPress(context, connector, latLng); }, onSecondaryTap: (_, latLng) { - _handleMapTap(context, connector, latLng); + _handleMapContextPress(context, connector, latLng); }, onPositionChanged: (camera, hasGesture) { // Track zoom in half-step buckets so cluster/marker From 6813a72767568eb0adfcf07e2fcde108db19439f Mon Sep 17 00:00:00 2001 From: Eric Poulsen Date: Sat, 13 Jun 2026 20:32:05 -0700 Subject: [PATCH 11/14] Fix null-returning ternaries with if blocks, per code review comments --- lib/screens/map_screen.dart | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/screens/map_screen.dart b/lib/screens/map_screen.dart index 8b428420..0bc28674 100644 --- a/lib/screens/map_screen.dart +++ b/lib/screens/map_screen.dart @@ -1188,12 +1188,12 @@ class _MapScreenState extends State { width: 48, height: 48, child: GestureDetector( - onLongPress: () => _isBuildingPathTrace - ? _showNodeInfo(context, guess.contact) - : null, - onSecondaryTap: () => _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), @@ -1393,10 +1393,12 @@ class _MapScreenState extends State { height: size, child: GestureDetector( behavior: HitTestBehavior.opaque, - onLongPress: () => - _isBuildingPathTrace ? _showNodeInfo(context, contact) : null, - onSecondaryTap: () => - _isBuildingPathTrace ? _showNodeInfo(context, contact) : null, + onLongPress: () { + if (_isBuildingPathTrace) _showNodeInfo(context, contact); + }, + onSecondaryTap: () { + if (_isBuildingPathTrace) _showNodeInfo(context, contact); + }, onTap: () => _isBuildingPathTrace ? _addToPath(context, contact) : _selectNode(contact), From bfa62523dfa4cc55836c41198eb2d167d0975d4b Mon Sep 17 00:00:00 2001 From: Eric Poulsen Date: Sat, 13 Jun 2026 20:43:13 -0700 Subject: [PATCH 12/14] Prefer GestureDetector over Listener for right-click in routing sheet --- lib/widgets/routing_sheet.dart | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/lib/widgets/routing_sheet.dart b/lib/widgets/routing_sheet.dart index e1ebbbf2..78036a70 100644 --- a/lib/widgets/routing_sheet.dart +++ b/lib/widgets/routing_sheet.dart @@ -1,5 +1,4 @@ import 'package:flutter/foundation.dart'; -import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -536,14 +535,10 @@ class _RoutingSheetBodyState extends State<_RoutingSheetBody> { l10n.routing_deliveryCounts(record.successCount, record.failureCount), ]; - return Listener( + return GestureDetector( behavior: HitTestBehavior.opaque, - onPointerDown: PlatformInfo.isDesktop && hasBytes - ? (event) { - if (event.buttons & kSecondaryMouseButton != 0) { - _showPathDetail(context, connector, contact, record.pathBytes); - } - } + onSecondaryTapUp: PlatformInfo.isDesktop && hasBytes + ? (_) => _showPathDetail(context, connector, contact, record.pathBytes) : null, child: Card( margin: const EdgeInsets.symmetric(vertical: 4), From e1536c49b1e668baa418aca70062c1a5815680a9 Mon Sep 17 00:00:00 2001 From: Eric Poulsen Date: Sat, 13 Jun 2026 20:43:30 -0700 Subject: [PATCH 13/14] tap --> secondaryTap, per review comments --- lib/screens/ble_debug_log_screen.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/screens/ble_debug_log_screen.dart b/lib/screens/ble_debug_log_screen.dart index 2972b22a..54a2c68a 100644 --- a/lib/screens/ble_debug_log_screen.dart +++ b/lib/screens/ble_debug_log_screen.dart @@ -133,6 +133,7 @@ class _BleDebugLogScreenState extends State { return GestureDetector( onTap: copyHex, onLongPress: copyHex, + onSecondaryTap: copyHex, child: Container( color: MeshPalette.bg, padding: const EdgeInsets.symmetric( From 5f54a2cd1b81bea6d4df039d7b1fbf91cc612af6 Mon Sep 17 00:00:00 2001 From: Eric Poulsen Date: Sat, 13 Jun 2026 22:05:46 -0700 Subject: [PATCH 14/14] dart format --- lib/screens/ble_debug_log_screen.dart | 4 +- lib/screens/channels_screen.dart | 220 +++++++++++++------------- lib/screens/map_screen.dart | 6 +- lib/widgets/routing_sheet.dart | 81 +++++----- 4 files changed, 161 insertions(+), 150 deletions(-) diff --git a/lib/screens/ble_debug_log_screen.dart b/lib/screens/ble_debug_log_screen.dart index 54a2c68a..fc8fe2d0 100644 --- a/lib/screens/ble_debug_log_screen.dart +++ b/lib/screens/ble_debug_log_screen.dart @@ -125,7 +125,9 @@ class _BleDebugLogScreenState extends State { if (context.mounted) { showDismissibleSnackBar( context, - content: Text(context.l10n.debugLog_bleCopied), + content: Text( + context.l10n.debugLog_bleCopied, + ), ); } } diff --git a/lib/screens/channels_screen.dart b/lib/screens/channels_screen.dart index 8a6b990a..745445ee 100644 --- a/lib/screens/channels_screen.dart +++ b/lib/screens/channels_screen.dart @@ -438,133 +438,133 @@ class _ChannelsScreenState extends State ) : null, child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - // Leading avatar with optional community badge - Stack( - clipBehavior: Clip.none, - children: [ - AvatarCircle( - name: channelLabel, - size: 42, - color: iconColor, - icon: icon, - ), - if (isCommunityChannel) - Positioned( - right: -2, - bottom: -2, - child: Container( - width: 16, - height: 16, - decoration: BoxDecoration( - color: MeshPalette.magenta, - shape: BoxShape.circle, - border: Border.all( - color: Theme.of( - context, - ).colorScheme.surfaceContainerLow, - width: 2, - ), - ), - child: const Icon( - Icons.people, - size: 8, - color: Colors.white, - ), - ), - ), - ], - ), - const SizedBox(width: 12), - // Title + subtitle + ch chip - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Expanded( - child: Text( - channelLabel, - style: Theme.of(context).textTheme.bodyMedium - ?.copyWith(fontWeight: FontWeight.w500), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ), - const SizedBox(width: 6), - StatusChip( - label: 'CH ${channel.index}', - color: MeshPalette.blue, - fontSize: 10, - ), - ], - ), - if (lastPreview.isNotEmpty) ...[ - const SizedBox(height: 2), - Text( - lastPreview, - style: MeshTheme.mono( - fontSize: 11.5, - color: scheme.onSurfaceVariant, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ], - ], + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + // Leading avatar with optional community badge + Stack( + clipBehavior: Clip.none, + children: [ + AvatarCircle( + name: channelLabel, + size: 42, + color: iconColor, + icon: icon, ), - ), - const SizedBox(width: 8), - // Right side: time + unread badge + muted + drag handle - Column( - crossAxisAlignment: CrossAxisAlignment.end, + if (isCommunityChannel) + Positioned( + right: -2, + bottom: -2, + child: Container( + width: 16, + height: 16, + decoration: BoxDecoration( + color: MeshPalette.magenta, + shape: BoxShape.circle, + border: Border.all( + color: Theme.of( + context, + ).colorScheme.surfaceContainerLow, + width: 2, + ), + ), + child: const Icon( + Icons.people, + size: 8, + color: Colors.white, + ), + ), + ), + ], + ), + const SizedBox(width: 12), + // Title + subtitle + ch chip + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - if (lastTime != null) - Text( - _relativeTime(lastTime), - style: MeshTheme.mono( - fontSize: 11, - color: scheme.onSurfaceVariant, - ), - ), - const SizedBox(height: 4), Row( - mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, children: [ - if (isMuted) ...[ - Icon( - Icons.notifications_off, - size: 14, - color: scheme.onSurfaceVariant, + Expanded( + child: Text( + channelLabel, + style: Theme.of(context).textTheme.bodyMedium + ?.copyWith(fontWeight: FontWeight.w500), + maxLines: 1, + overflow: TextOverflow.ellipsis, ), - const SizedBox(width: 4), - ], - if (unreadCount > 0) UnreadBadge(count: unreadCount), + ), + const SizedBox(width: 6), + StatusChip( + label: 'CH ${channel.index}', + color: MeshPalette.blue, + fontSize: 10, + ), ], ), + if (lastPreview.isNotEmpty) ...[ + const SizedBox(height: 2), + Text( + lastPreview, + style: MeshTheme.mono( + fontSize: 11.5, + color: scheme.onSurfaceVariant, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], ], ), - if (showDragHandle && dragIndex != null) ...[ - const SizedBox(width: 4), - ReorderableDragStartListener( - index: dragIndex, - child: Padding( - padding: const EdgeInsets.all(8), - child: Icon( - Icons.drag_handle, + ), + const SizedBox(width: 8), + // Right side: time + unread badge + muted + drag handle + Column( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + if (lastTime != null) + Text( + _relativeTime(lastTime), + style: MeshTheme.mono( + fontSize: 11, color: scheme.onSurfaceVariant, ), ), + const SizedBox(height: 4), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (isMuted) ...[ + Icon( + Icons.notifications_off, + size: 14, + color: scheme.onSurfaceVariant, + ), + const SizedBox(width: 4), + ], + if (unreadCount > 0) UnreadBadge(count: unreadCount), + ], ), ], + ), + if (showDragHandle && dragIndex != null) ...[ + const SizedBox(width: 4), + ReorderableDragStartListener( + index: dragIndex, + child: Padding( + padding: const EdgeInsets.all(8), + child: Icon( + Icons.drag_handle, + color: scheme.onSurfaceVariant, + ), + ), + ), ], - ), + ], ), + ), ); } diff --git a/lib/screens/map_screen.dart b/lib/screens/map_screen.dart index 0bc28674..18e3ba9d 100644 --- a/lib/screens/map_screen.dart +++ b/lib/screens/map_screen.dart @@ -285,7 +285,11 @@ class _MapScreenState extends State { ); } - void _handleMapContextPress(BuildContext context, MeshCoreConnector connector, LatLng latLng) { + void _handleMapContextPress( + BuildContext context, + MeshCoreConnector connector, + LatLng latLng, + ) { if (_isSelectingPoi) { setState(() { _isSelectingPoi = false; diff --git a/lib/widgets/routing_sheet.dart b/lib/widgets/routing_sheet.dart index 78036a70..2cc55b8a 100644 --- a/lib/widgets/routing_sheet.dart +++ b/lib/widgets/routing_sheet.dart @@ -538,57 +538,62 @@ class _RoutingSheetBodyState extends State<_RoutingSheetBody> { return GestureDetector( behavior: HitTestBehavior.opaque, onSecondaryTapUp: PlatformInfo.isDesktop && hasBytes - ? (_) => _showPathDetail(context, connector, contact, record.pathBytes) + ? (_) => + _showPathDetail(context, connector, contact, record.pathBytes) : null, child: Card( margin: const EdgeInsets.symmetric(vertical: 4), child: ListTile( enabled: hasBytes, leading: CircleAvatar( - radius: 18, - backgroundColor: bg, - child: Icon( - _qualityIcon(quality), - size: 18, - color: fg, - semanticLabel: _qualityLabel(context, quality), + radius: 18, + backgroundColor: bg, + child: Icon( + _qualityIcon(quality), + size: 18, + color: fg, + semanticLabel: _qualityLabel(context, quality), + ), ), - ), - title: Text(title, maxLines: 1, overflow: TextOverflow.ellipsis), - subtitle: Text( - '$line1\n${line2Parts.join(' • ')}', - style: const TextStyle(fontSize: 11), - ), - isThreeLine: true, - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (inUse) - Tooltip( - message: l10n.routing_inUse, - child: Icon( - Icons.check_circle, - color: scheme.primary, - semanticLabel: l10n.routing_inUse, + title: Text(title, maxLines: 1, overflow: TextOverflow.ellipsis), + subtitle: Text( + '$line1\n${line2Parts.join(' • ')}', + style: const TextStyle(fontSize: 11), + ), + isThreeLine: true, + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (inUse) + Tooltip( + message: l10n.routing_inUse, + child: Icon( + Icons.check_circle, + color: scheme.primary, + semanticLabel: l10n.routing_inUse, + ), + ), + IconButton( + icon: const Icon(Icons.delete_outline, size: 20), + tooltip: l10n.chat_removePath, + constraints: const BoxConstraints(minWidth: 44, minHeight: 44), + onPressed: () => pathService.removePathRecord( + contact.publicKeyHex, + record.pathBytes, ), ), - IconButton( - icon: const Icon(Icons.delete_outline, size: 20), - tooltip: l10n.chat_removePath, - constraints: const BoxConstraints(minWidth: 44, minHeight: 44), - onPressed: () => pathService.removePathRecord( - contact.publicKeyHex, - record.pathBytes, - ), - ), - ], - ), + ], + ), onTap: hasBytes && !inUse ? () => _applyHistoryPath(connector, contact, record) : null, onLongPress: hasBytes - ? () => - _showPathDetail(context, connector, contact, record.pathBytes) + ? () => _showPathDetail( + context, + connector, + contact, + record.pathBytes, + ) : null, ), ),