@ -399,7 +399,8 @@ class _MapScreenState extends State<MapScreen> {
/ / Re center map after removed markers have loaded
/ / Re center map after removed markers have loaded
if ( ! _hasInitializedMap & & _removedMarkersLoaded ) {
if ( ! _hasInitializedMap & & _removedMarkersLoaded ) {
_hasInitializedMap = true ;
_hasInitializedMap = true ;
_showNodeLabels = initialZoom > = _labelZoomThreshold ;
_showNodeLabels =
settings . mapAlwaysShowNames | | initialZoom > = _labelZoomThreshold ;
if ( hasMapContent ) {
if ( hasMapContent ) {
WidgetsBinding . instance . addPostFrameCallback ( ( _ ) {
WidgetsBinding . instance . addPostFrameCallback ( ( _ ) {
if ( mounted ) {
if ( mounted ) {
@ -409,257 +410,253 @@ class _MapScreenState extends State<MapScreen> {
}
}
}
}
final allowBack = ! connector . isConnected ;
return AppShell (
selectedIndex: 2 ,
return PopScope (
onDestinationSelected: ( index ) = > _handleQuickSwitch ( index , context ) ,
canPop: allowBack ,
contactsUnreadCount: connector . getTotalContactsUnreadCount ( ) ,
child: AppShell (
channelsUnreadCount: connector . getTotalChannelsUnreadCount ( ) ,
selectedIndex: 2 ,
drawerContent: const MapLayerPanel ( ) ,
onDestinationSelected: ( index ) = >
onDisconnect: ( ) = > _disconnect ( context , connector ) ,
_handleQuickSwitch ( index , context ) ,
onSettings: ( ) = > Navigator . push (
contactsUnreadCount: connector . getTotalContactsUnreadCount ( ) ,
context ,
channelsUnreadCount: connector . getTotalChannelsUnreadCount ( ) ,
MaterialPageRoute ( builder: ( context ) = > const SettingsScreen ( ) ) ,
drawerContent: const MapLayerPanel ( ) ,
) ,
onDisconnect: ( ) = > _disconnect ( context , connector ) ,
appBar: AppBar (
onSettings: ( ) = > Navigator . push (
title: AppBarTitle ( context . l10n . map_title ) ,
context ,
centerTitle: true ,
MaterialPageRoute ( builder: ( context ) = > const SettingsScreen ( ) ) ,
bottom: const SyncProgressAppBarBottom ( ) ,
) ,
actions: [
appBar: AppBar (
if ( ! _isBuildingPathTrace )
title: AppBarTitle ( context . l10n . map_title ) ,
IconButton (
centerTitle: true ,
icon: const Icon ( Icons . radar ) ,
bottom: const SyncProgressAppBarBottom ( ) ,
onPressed: ( ) = > _startPath (
actions: [
LatLng ( connector . selfLatitude ! , connector . selfLongitude ! ) ,
if ( ! _isBuildingPathTrace )
IconButton (
icon: const Icon ( Icons . radar ) ,
onPressed: ( ) = > _startPath (
LatLng ( connector . selfLatitude ! , connector . selfLongitude ! ) ,
) ,
tooltip: context . l10n . contacts_pathTrace ,
) ,
) ,
if ( ! _isBuildingPathTrace )
tooltip: context . l10n . contacts_pathTrace ,
IconButton (
) ,
icon: const LosIcon ( ) ,
if ( ! _isBuildingPathTrace )
onPressed: ( ) {
IconButton (
final candidates = < LineOfSightEndpoint > [ ] ;
icon: const LosIcon ( ) ,
if ( connector . selfLatitude ! = null & &
onPressed: ( ) {
connector . selfLongitude ! = null ) {
final candidates = < LineOfSightEndpoint > [ ] ;
candidates . add (
if ( connector . selfLatitude ! = null & &
LineOfSightEndpoint (
connector . selfLongitude ! = null ) {
label: context . l10n . pathTrace_you ,
candidates . add (
point: LatLng (
LineOfSightEndpoint (
connector . selfLatitude ! ,
label: context . l10n . pathTrace_you ,
connector . selfLongitude ! ,
point: LatLng (
) ,
connector . selfLatitude ! ,
color: Colors . teal ,
connector . selfLongitude ! ,
icon: Icons . person_pin_circle ,
) ,
) ;
}
for ( final c in contactsWithLocation ) {
candidates . add (
LineOfSightEndpoint (
label: c . name ,
point: LatLng ( c . latitude ! , c . longitude ! ) ,
color: _getNodeColor ( c . type ) ,
icon: _getNodeIcon ( c . type ) ,
) ,
) ;
}
Navigator . push (
context ,
MaterialPageRoute (
builder: ( context ) = > LineOfSightMapScreen (
title: context . l10n . map_losScreenTitle ,
candidates: candidates ,
) ,
) ,
color: Colors . teal ,
icon: Icons . person_pin_circle ,
) ,
) ,
) ;
) ;
} ,
}
tooltip: context . l10n . map_lineOfSight ,
for ( final c in contactsWithLocation ) {
candidates . add (
LineOfSightEndpoint (
label: c . name ,
point: LatLng ( c . latitude ! , c . longitude ! ) ,
color: _getNodeColor ( c . type ) ,
icon: _getNodeIcon ( c . type ) ,
) ,
) ;
}
Navigator . push (
context ,
MaterialPageRoute (
builder: ( context ) = > LineOfSightMapScreen (
title: context . l10n . map_losScreenTitle ,
candidates: candidates ,
) ,
) ,
) ;
} ,
tooltip: context . l10n . map_lineOfSight ,
) ,
] ,
) ,
body: Stack (
children: [
FlutterMap (
mapController: _mapController ,
options: MapOptions (
initialCenter: center ,
initialZoom: initialZoom ,
minZoom: _mapMinZoom ,
maxZoom: _mapMaxZoom ,
interactionOptions: InteractionOptions (
flags: ~ InteractiveFlag . rotate ,
scrollWheelVelocity: isDesktop ? 0.012 : 0.005 ,
cursorKeyboardRotationOptions:
CursorKeyboardRotationOptions . disabled ( ) ,
keyboardOptions: isDesktop
? const KeyboardOptions (
enableArrowKeysPanning: true ,
enableWASDPanning: true ,
enableRFZooming: true ,
)
: const KeyboardOptions . disabled ( ) ,
) ,
) ,
] ,
onTap: ( _ , latLng ) {
) ,
if ( _isSelectingPoi ) {
body: Stack (
setState ( ( ) {
children: [
_isSelectingPoi = false ;
FlutterMap (
} ) ;
mapController: _mapController ,
_shareMarker (
options: MapOptions (
initialCenter: center ,
initialZoom: initialZoom ,
minZoom: _mapMinZoom ,
maxZoom: _mapMaxZoom ,
interactionOptions: InteractionOptions (
flags: ~ InteractiveFlag . rotate ,
scrollWheelVelocity: isDesktop ? 0.012 : 0.005 ,
cursorKeyboardRotationOptions:
CursorKeyboardRotationOptions . disabled ( ) ,
keyboardOptions: isDesktop
? const KeyboardOptions (
enableArrowKeysPanning: true ,
enableWASDPanning: true ,
enableRFZooming: true ,
)
: const KeyboardOptions . disabled ( ) ,
) ,
onTap: ( _ , latLng ) {
if ( _isSelectingPoi ) {
setState ( ( ) {
_isSelectingPoi = false ;
} ) ;
_shareMarker (
context: context ,
connector: connector ,
position: latLng ,
defaultLabel: context . l10n . map_pointOfInterest ,
flags: ' poi ' ,
) ;
}
} ,
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 ,
context: context ,
connector: connector ,
connector: connector ,
position: latLng ,
position: latLng ,
defaultLabel: context . l10n . map_pointOfInterest ,
flags: ' poi ' ,
) ;
) ;
} ,
}
onPositionChanged: ( camera , hasGesture ) {
} ,
final shouldShow = camera . zoom > = _labelZoomThreshold ;
onLongPress: ( _ , latLng ) {
if ( shouldShow ! = _showNodeLabels & & mounted ) {
if ( _isSelectingPoi ) {
setState ( ( ) {
setState ( ( ) {
_showNodeLabels = shouldShow ;
_isSelectingPoi = false ;
} ) ;
} ) ;
}
_shareMarker (
} ,
context: context ,
connector: connector ,
position: latLng ,
defaultLabel: context . l10n . map_pointOfInterest ,
flags: ' poi ' ,
) ;
return ;
}
_showShareMarkerAtPositionSheet (
context: context ,
connector: connector ,
position: latLng ,
) ;
} ,
onPositionChanged: ( camera , hasGesture ) {
final shouldShow =
settings . mapAlwaysShowNames | |
camera . zoom > = _labelZoomThreshold ;
if ( shouldShow ! = _showNodeLabels & & mounted ) {
setState ( ( ) {
_showNodeLabels = shouldShow ;
} ) ;
}
} ,
) ,
children: [
TileLayer (
urlTemplate: kMapTileUrlTemplate ,
tileProvider: tileCache . tileProvider ,
userAgentPackageName:
MapTileCacheService . userAgentPackageName ,
maxZoom: 19 ,
) ,
) ,
children: [
if ( _polylines . isNotEmpty & & _isBuildingPathTrace )
TileLayer (
PolylineLayer ( polylines: _polylines ) ,
urlTemplate: kMapTileUrlTemplate ,
if ( sharedMarkerPolylines . isNotEmpty )
tileProvider: tileCache . tileProvider ,
PolylineLayer ( polylines: sharedMarkerPolylines ) ,
userAgentPackageName:
MarkerLayer (
MapTileCacheService . userAgentPackageName ,
markers: [
maxZoom: 19 ,
if ( highlightPosition ! = null )
) ,
Marker (
if ( _polylines . isNotEmpty & & _isBuildingPathTrace )
point: highlightPosition ,
PolylineLayer ( polylines: _polylines ) ,
width: 40 ,
if ( sharedMarkerPolylines . isNotEmpty )
height: 40 ,
PolylineLayer ( polylines: sharedMarkerPolylines ) ,
child: IgnorePointer (
MarkerLayer (
child: Icon (
markers: [
Icons . location_on_outlined ,
if ( highlightPosition ! = null )
color: Colors . red [ 600 ] ,
Marker (
size: 34 ,
point: highlightPosition ,
width: 40 ,
height: 40 ,
child: IgnorePointer (
child: Icon (
Icons . location_on_outlined ,
color: Colors . red [ 600 ] ,
size: 34 ,
) ,
) ,
) ,
) ,
) ,
if ( ! settings . mapShowOverlaps )
. . . _buildGuessedMarker (
guessedLocations ,
showLabels: _showNodeLabels ,
) ,
. . . _buildMarkers (
contactsWithLocation ,
settings ,
showLabels: _showNodeLabels ,
) ,
) ,
. . . sharedMarkers . map ( _buildSharedMarker ) ,
if ( ! settings . mapShowOverlaps )
if ( connector . selfLatitude ! = null & &
. . . _buildGuessedMarker (
connector . selfLongitude ! = null )
guessedLocations ,
Marker (
showLabels:
point: LatLng (
settings . mapAlwaysShowNames | | _showNodeLabels ,
connector . selfLatitude ! ,
) ,
connector . selfLongitude ! ,
. . . _buildMarkers (
) ,
contactsWithLocation ,
width: 40 ,
settings ,
height: 40 ,
showLabels:
child: IgnorePointer (
settings . mapAlwaysShowNames | | _showNodeLabels ,
ignoring: true ,
) ,
child: Container (
. . . sharedMarkers . map ( _buildSharedMarker ) ,
padding: const EdgeInsets . all ( 4 ) ,
if ( connector . selfLatitude ! = null & &
decoration: BoxDecoration (
connector . selfLongitude ! = null )
color: Colors . teal ,
Marker (
shape: BoxShape . circle ,
point: LatLng (
border: Border . all (
connector . selfLatitude ! ,
color: Colors . white ,
connector . selfLongitude ! ,
width: 2 ,
) ,
) ,
width: 40 ,
boxShadow: [
height: 40 ,
BoxShadow (
child: IgnorePointer (
color: Colors . black . withValues (
ignoring: true ,
alpha: 0.3 ,
child: Container (
) ,
padding: const EdgeInsets . all ( 4 ) ,
blurRadius: 4 ,
decoration: BoxDecoration (
offset: const Offset ( 0 , 2 ) ,
color: Colors . teal ,
) ,
shape: BoxShape . circle ,
] ,
border: Border . all (
) ,
alignment: Alignment . center ,
child: const Icon (
Icons . person_pin_circle ,
color: Colors . white ,
color: Colors . white ,
size: 20 ,
width: 2 ,
) ,
) ,
boxShadow: [
BoxShadow (
color: Colors . black . withValues ( alpha: 0.3 ) ,
blurRadius: 4 ,
offset: const Offset ( 0 , 2 ) ,
) ,
] ,
) ,
alignment: Alignment . center ,
child: const Icon (
Icons . person_pin_circle ,
color: Colors . white ,
size: 20 ,
) ,
) ,
) ,
) ,
) ,
) ,
if ( _showNodeLabels & &
) ,
connector . selfLatitude ! = null & &
if ( _showNodeLabels & &
connector . selfLongitude ! = null )
connector . selfLatitude ! = null & &
_buildNodeLabelMarker (
connector . selfLongitude ! = null )
point: LatLng (
_buildNodeLabelMarker (
connector . selfLatitude ! ,
point: LatLng (
connector . selfLongitude ! ,
connector . selfLatitude ! ,
) ,
connector . selfLongitude ! ,
label: context . l10n . pathTrace_you ,
) ,
) ,
] ,
label: context . l10n . pathTrace_you ,
) ,
) ,
] ,
] ,
) ,
if ( ! _isBuildingPathTrace )
_buildLegend (
contacts ,
contactsWithLocation ,
settings ,
sharedMarkers . length ,
guessedLocations . length ,
) ,
if ( isDesktop )
_buildDesktopMapControls (
context ,
center: center ,
zoom: initialZoom ,
hasPathSelector: _isBuildingPathTrace ,
) ,
) ,
if ( _isBuildingPathTrace ) _buildPathTraceOverlay ( ) ,
] ,
] ,
) ,
) ,
if ( ! _isBuildingPathTrace )
floatingActionButton: FloatingActionButton (
_buildLegend (
onPressed: ( ) = > _showFilterDialog ( context , settingsService ) ,
contacts ,
tooltip: context . l10n . map_filterNodes ,
contactsWithLocation ,
child: const Icon ( Icons . filter_list ) ,
settings ,
) ,
sharedMarkers . length ,
guessedLocations . length ,
) ,
if ( isDesktop )
_buildDesktopMapControls (
context ,
center: center ,
zoom: initialZoom ,
hasPathSelector: _isBuildingPathTrace ,
) ,
if ( _isBuildingPathTrace ) _buildPathTraceOverlay ( ) ,
] ,
) ,
floatingActionButton: FloatingActionButton (
onPressed: ( ) = > _showFilterDialog ( context , settingsService ) ,
tooltip: context . l10n . map_filterNodes ,
child: const Icon ( Icons . filter_list ) ,
) ,
) ,
) ;
) ;
} ,
} ,