mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
Add custom cave map mode
This commit is contained in:
@@ -108,6 +108,8 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
14.5058,
|
14.5058,
|
||||||
); // Ljubljana, Slovenia
|
); // Ljubljana, Slovenia
|
||||||
static const double _defaultZoom = 13.0;
|
static const double _defaultZoom = 13.0;
|
||||||
|
static const double _customMapMinZoom = -4.0;
|
||||||
|
static const double _customMapMaxZoom = 40.0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get wantKeepAlive => true;
|
bool get wantKeepAlive => true;
|
||||||
@@ -404,7 +406,9 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
: mapProvider.targetLocation!;
|
: mapProvider.targetLocation!;
|
||||||
final zoom =
|
final zoom =
|
||||||
mapProvider.targetCoordinateSpace == MapCoordinateSpace.customMap
|
mapProvider.targetCoordinateSpace == MapCoordinateSpace.customMap
|
||||||
? (mapProvider.targetZoom ?? 2.0).clamp(-4.0, 8.0).toDouble()
|
? (mapProvider.targetZoom ?? 2.0)
|
||||||
|
.clamp(_customMapMinZoom, _customMapMaxZoom)
|
||||||
|
.toDouble()
|
||||||
: mapProvider.targetZoom ?? _defaultZoom;
|
: mapProvider.targetZoom ?? _defaultZoom;
|
||||||
|
|
||||||
_mapController.move(location, zoom);
|
_mapController.move(location, zoom);
|
||||||
@@ -482,6 +486,25 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _selectMapLayer(MapProvider mapProvider, MapLayer layer) async {
|
||||||
|
await mapProvider.exitCustomMapMode();
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_currentLayer = layer;
|
||||||
|
if (_isMapReady && _mapController.camera.zoom > layer.maxZoom) {
|
||||||
|
_mapController.move(
|
||||||
|
_mapController.camera.center,
|
||||||
|
layer.isWms ? 11.0 : layer.maxZoom,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await _saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
void _showLayerSelector(BuildContext context) {
|
void _showLayerSelector(BuildContext context) {
|
||||||
final rootContext = this.context;
|
final rootContext = this.context;
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
@@ -513,6 +536,53 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
child: ListView(
|
child: ListView(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
children: [
|
children: [
|
||||||
|
Consumer<MapProvider>(
|
||||||
|
builder: (context, mapProvider, _) {
|
||||||
|
final customMapConfig = mapProvider.customMapConfig;
|
||||||
|
if (customMapConfig == null) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
vertical: 8,
|
||||||
|
),
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Text(
|
||||||
|
'Saved maps',
|
||||||
|
style: Theme.of(context).textTheme.labelLarge
|
||||||
|
?.copyWith(color: Colors.grey[600]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: mapProvider.isUsingCustomMap
|
||||||
|
? const Icon(
|
||||||
|
Icons.check_circle,
|
||||||
|
color: Colors.green,
|
||||||
|
)
|
||||||
|
: const Icon(Icons.radio_button_unchecked),
|
||||||
|
title: Text(customMapConfig.displayName),
|
||||||
|
subtitle: Text(
|
||||||
|
'Custom picture map • Map ID ${customMapConfig.mapId}',
|
||||||
|
),
|
||||||
|
onTap: () async {
|
||||||
|
await mapProvider.enterCustomMapMode();
|
||||||
|
if (!context.mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
// Online layers section
|
// Online layers section
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
@@ -528,24 +598,21 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
),
|
),
|
||||||
...MapLayer.allLayers.map(
|
...MapLayer.allLayers.map(
|
||||||
(layer) => ListTile(
|
(layer) => ListTile(
|
||||||
leading: _currentLayer == layer
|
leading:
|
||||||
|
!rootContext.read<MapProvider>().isUsingCustomMap &&
|
||||||
|
_currentLayer == layer
|
||||||
? const Icon(Icons.check_circle, color: Colors.green)
|
? const Icon(Icons.check_circle, color: Colors.green)
|
||||||
: const Icon(Icons.radio_button_unchecked),
|
: const Icon(Icons.radio_button_unchecked),
|
||||||
title: Text(layer.getLocalizedName(context)),
|
title: Text(layer.getLocalizedName(context)),
|
||||||
subtitle: Text(layer.attribution),
|
subtitle: Text(layer.attribution),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
setState(() {
|
await _selectMapLayer(
|
||||||
_currentLayer = layer;
|
rootContext.read<MapProvider>(),
|
||||||
// Clamp zoom level if current zoom exceeds new layer's max
|
layer,
|
||||||
if (_isMapReady &&
|
);
|
||||||
_mapController.camera.zoom > layer.maxZoom) {
|
if (!context.mounted) {
|
||||||
_mapController.move(
|
return;
|
||||||
_mapController.camera.center,
|
}
|
||||||
layer.maxZoom,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
_saveSettings();
|
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -554,50 +621,40 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
if (AppLocalizations.of(context)!.localeName == 'sl' ||
|
if (AppLocalizations.of(context)!.localeName == 'sl' ||
|
||||||
AppLocalizations.of(context)!.localeName == 'hr') ...[
|
AppLocalizations.of(context)!.localeName == 'hr') ...[
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: _currentLayer == _slovenianAerialLayer
|
leading:
|
||||||
|
!rootContext.read<MapProvider>().isUsingCustomMap &&
|
||||||
|
_currentLayer == _slovenianAerialLayer
|
||||||
? const Icon(Icons.check_circle, color: Colors.green)
|
? const Icon(Icons.check_circle, color: Colors.green)
|
||||||
: const Icon(Icons.radio_button_unchecked),
|
: const Icon(Icons.radio_button_unchecked),
|
||||||
title: Text(_slovenianAerialLayer.name),
|
title: Text(_slovenianAerialLayer.name),
|
||||||
subtitle: Text(_slovenianAerialLayer.attribution),
|
subtitle: Text(_slovenianAerialLayer.attribution),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
setState(() {
|
await _selectMapLayer(
|
||||||
_currentLayer = _slovenianAerialLayer;
|
rootContext.read<MapProvider>(),
|
||||||
// Clamp zoom level if current zoom exceeds new layer's max
|
_slovenianAerialLayer,
|
||||||
// For WMS layers, use a middle zoom (11) instead of max zoom to avoid extreme close-up
|
);
|
||||||
if (_isMapReady &&
|
if (!context.mounted) {
|
||||||
_mapController.camera.zoom >
|
return;
|
||||||
_slovenianAerialLayer.maxZoom) {
|
}
|
||||||
_mapController.move(
|
|
||||||
_mapController.camera.center,
|
|
||||||
11.0, // Middle zoom for WMS
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
_saveSettings();
|
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: _currentLayer == _dtk25Layer
|
leading:
|
||||||
|
!rootContext.read<MapProvider>().isUsingCustomMap &&
|
||||||
|
_currentLayer == _dtk25Layer
|
||||||
? const Icon(Icons.check_circle, color: Colors.green)
|
? const Icon(Icons.check_circle, color: Colors.green)
|
||||||
: const Icon(Icons.radio_button_unchecked),
|
: const Icon(Icons.radio_button_unchecked),
|
||||||
title: Text(AppLocalizations.of(context)!.topographicMap),
|
title: Text(AppLocalizations.of(context)!.topographicMap),
|
||||||
subtitle: Text(_dtk25Layer.attribution),
|
subtitle: Text(_dtk25Layer.attribution),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
setState(() {
|
await _selectMapLayer(
|
||||||
_currentLayer = _dtk25Layer;
|
rootContext.read<MapProvider>(),
|
||||||
// Clamp zoom level if current zoom exceeds new layer's max
|
_dtk25Layer,
|
||||||
// For WMS layers, use a middle zoom (11) instead of max zoom to avoid extreme close-up
|
);
|
||||||
if (_isMapReady &&
|
if (!context.mounted) {
|
||||||
_mapController.camera.zoom >
|
return;
|
||||||
_dtk25Layer.maxZoom) {
|
}
|
||||||
_mapController.move(
|
|
||||||
_mapController.camera.center,
|
|
||||||
11.0, // Middle zoom for WMS
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
_saveSettings();
|
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -605,6 +662,7 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
// WMS Overlays section (only for Slovenian/Croatian regions and when WMS base layer is selected)
|
// WMS Overlays section (only for Slovenian/Croatian regions and when WMS base layer is selected)
|
||||||
if ((AppLocalizations.of(context)!.localeName == 'sl' ||
|
if ((AppLocalizations.of(context)!.localeName == 'sl' ||
|
||||||
AppLocalizations.of(context)!.localeName == 'hr') &&
|
AppLocalizations.of(context)!.localeName == 'hr') &&
|
||||||
|
!rootContext.read<MapProvider>().isUsingCustomMap &&
|
||||||
_currentLayer.isWms) ...[
|
_currentLayer.isWms) ...[
|
||||||
const Divider(),
|
const Divider(),
|
||||||
Padding(
|
Padding(
|
||||||
@@ -845,23 +903,6 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
'Map ID ${customMapConfig.mapId}${customMapConfig.isCalibrated ? ' • Scale set' : ' • Not calibrated'}',
|
'Map ID ${customMapConfig.mapId}${customMapConfig.isCalibrated ? ' • Scale set' : ' • Not calibrated'}',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SwitchListTile(
|
|
||||||
secondary: const Icon(Icons.map_outlined),
|
|
||||||
title: const Text('Use custom map'),
|
|
||||||
subtitle: const Text(
|
|
||||||
'Hide GPS-based layers and work in image space',
|
|
||||||
),
|
|
||||||
value: mapProvider.isUsingCustomMap,
|
|
||||||
onChanged: (value) async {
|
|
||||||
if (value) {
|
|
||||||
await mapProvider.enterCustomMapMode();
|
|
||||||
} else {
|
|
||||||
await mapProvider.exitCustomMapMode();
|
|
||||||
}
|
|
||||||
if (!context.mounted) return;
|
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.swap_horizontal_circle),
|
leading: const Icon(Icons.swap_horizontal_circle),
|
||||||
title: const Text('Replace image'),
|
title: const Text('Replace image'),
|
||||||
@@ -1919,8 +1960,10 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
padding: const EdgeInsets.all(24),
|
padding: const EdgeInsets.all(24),
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
minZoom: isCustomMapMode ? -4 : 0,
|
minZoom: isCustomMapMode ? _customMapMinZoom : 0,
|
||||||
maxZoom: isCustomMapMode ? 8 : _currentLayer.maxZoom,
|
maxZoom: isCustomMapMode
|
||||||
|
? _customMapMaxZoom
|
||||||
|
: _currentLayer.maxZoom,
|
||||||
cameraConstraint: isCustomMapMode
|
cameraConstraint: isCustomMapMode
|
||||||
? CameraConstraint.contain(
|
? CameraConstraint.contain(
|
||||||
bounds: customMapConfig.displayBounds,
|
bounds: customMapConfig.displayBounds,
|
||||||
@@ -2099,6 +2142,7 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
imageProvider: FileImage(
|
imageProvider: FileImage(
|
||||||
File(customMapConfig.filePath),
|
File(customMapConfig.filePath),
|
||||||
),
|
),
|
||||||
|
filterQuality: FilterQuality.none,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user