diff --git a/Components/Pages/Home.razor b/Components/Pages/Home.razor
index 5002212..41155b3 100644
--- a/Components/Pages/Home.razor
+++ b/Components/Pages/Home.razor
@@ -109,13 +109,18 @@
}
-
+
GPS non attivo
Nessuna particella selezionata
-
@(GpsActive ? "Ferma GPS" : "Avvia GPS")
-
Centra
+
+ @(GpsActive ? "Ferma GPS" : "Avvia GPS")
+ Centra
+
+
+ @(GpsOverlayCollapsed ? "+" : "−")
+
@if (ActiveTab is not "map" and not "search")
@@ -553,6 +558,11 @@
Trasparenza catasto
+
+ Durata messaggi mappa (secondi)
+
+
+
Imposta 0 per lasciare i messaggi visibili fino alla chiusura manuale.
@@ -689,7 +699,10 @@
}
- Pronto
+
+ Pronto
+ ×
+
@@ -743,8 +756,10 @@
private string CatalogMessage { get; set; } = string.Empty;
private double? CatalogFraction { get; set; }
private double CadastralOpacity { get; set; } = 0.75;
+ private int MapMessageTimeoutSeconds { get; set; } = 3;
private bool CatalogBusy { get; set; }
private bool GpsActive { get; set; }
+ private bool GpsOverlayCollapsed { get; set; }
private bool ShowGpsPosition { get; set; } = true;
private bool ShowSelectedParcels { get; set; } = true;
private bool ShowImportedData { get; set; } = true;
@@ -1305,7 +1320,7 @@
Interlocked.Exchange(ref LastGpsLookupAt, now);
try
{
- var radius = Math.Clamp(location.Accuracy ?? 5, 2, 25);
+ var radius = Math.Clamp(location.Accuracy ?? 12, 12, 30);
var xml = await OfficialCadastreService.GetParcelsNearAsync(
location.Latitude,
location.Longitude,
@@ -2094,6 +2109,12 @@
private Task CenterOnGps() => JS.InvokeVoidAsync("lmeMap.centerOnGps").AsTask();
+ private void ToggleGpsOverlay()
+ {
+ GpsOverlayCollapsed = !GpsOverlayCollapsed;
+ SaveFlagPreferences();
+ }
+
private string L(string key) => L10n.T(key);
private bool CanUse(string feature) => Entitlements.CanUse(feature);
@@ -2356,6 +2377,11 @@
OfflineExtraZoomLevels = Preferences.Default.Get($"{FlagPreferencePrefix}offline_zoom_levels", 2);
GpsProximityEnabled = GetFlagPreference("gps_proximity", true);
GpsBoundaryAlertEnabled = GetFlagPreference("gps_boundary_alert", false);
+ GpsOverlayCollapsed = GetFlagPreference("gps_overlay_collapsed", false);
+ MapMessageTimeoutSeconds = Math.Clamp(
+ Preferences.Default.Get($"{FlagPreferencePrefix}map_message_timeout", 3),
+ 0,
+ 30);
foreach (var layer in CadastralLayers)
{
@@ -2391,6 +2417,8 @@
Preferences.Default.Set($"{FlagPreferencePrefix}offline_zoom_levels", OfflineExtraZoomLevels);
SetFlagPreference("gps_proximity", GpsProximityEnabled);
SetFlagPreference("gps_boundary_alert", GpsBoundaryAlertEnabled);
+ SetFlagPreference("gps_overlay_collapsed", GpsOverlayCollapsed);
+ Preferences.Default.Set($"{FlagPreferencePrefix}map_message_timeout", MapMessageTimeoutSeconds);
foreach (var layer in CadastralLayers)
{
@@ -2400,6 +2428,7 @@
private async Task ApplyFlagPreferencesToMapAsync()
{
+ await JS.InvokeVoidAsync("lmeMap.setInfoTimeout", MapMessageTimeoutSeconds);
await JS.InvokeVoidAsync("lmeMap.setIncludeKmlSheetLayer", IncludeKmlSheetLayer);
await JS.InvokeVoidAsync("lmeMap.setImportedLayerVisible", ShowImportedData);
await JS.InvokeVoidAsync("lmeMap.setImportedSheetLayerVisible", ShowImportedSheetLayer);
@@ -2416,6 +2445,13 @@
}
}
+ private async Task ApplyMapMessageOptions()
+ {
+ MapMessageTimeoutSeconds = Math.Clamp(MapMessageTimeoutSeconds, 0, 30);
+ SaveFlagPreferences();
+ await JS.InvokeVoidAsync("lmeMap.setInfoTimeout", MapMessageTimeoutSeconds);
+ }
+
private async Task ApplyAdvancedOptions()
{
SaveFlagPreferences();
diff --git a/LMEMappeCatastaliApp.csproj b/LMEMappeCatastaliApp.csproj
index d74b66f..478cb37 100644
--- a/LMEMappeCatastaliApp.csproj
+++ b/LMEMappeCatastaliApp.csproj
@@ -28,8 +28,8 @@
it.liviomerola.lmemappecatastali.pro
- 1.0.9
- 109
+ 1.0.10
+ 110
$(ApplicationDisplayVersion)
diff --git a/PLAY_STORE_PRO_CHECKLIST.md b/PLAY_STORE_PRO_CHECKLIST.md
index df7144c..5a7dea7 100644
--- a/PLAY_STORE_PRO_CHECKLIST.md
+++ b/PLAY_STORE_PRO_CHECKLIST.md
@@ -4,7 +4,7 @@
- Nome: LME Mappe Catastali Pro.
- Package name Android: `it.liviomerola.lmemappecatastali.pro`.
-- Versione corrente da pubblicare: `1.0.9` / version code `109`.
+- Versione corrente da pubblicare: `1.0.10` / version code `110`.
- Formato Release Android: Android App Bundle (`.aab`).
- Target SDK Release: Android 16 / API 36.
diff --git a/README.md b/README.md
index 46c25ac..7bf6eea 100644
--- a/README.md
+++ b/README.md
@@ -4,6 +4,12 @@ App .NET MAUI Blazor Hybrid per Android, iOS, Mac Catalyst e Windows, pensata pe
Informativa privacy: [PRIVACY_POLICY.md](PRIVACY_POLICY.md).
+## Versione Pro 1.0.10 test
+
+- Migliora la compatibilita del servizio catastale AdE sui dispositivi Android meno recenti con HTTP/1.1, tentativi automatici e una ricerca GPS piu tollerante.
+- I messaggi sulla mappa si chiudono automaticamente dopo un intervallo configurabile, predefinito a 3 secondi, e includono un pulsante di chiusura.
+- La barra GPS mantiene sempre disponibili i comandi Avvia/Ferma e Centra, impilati sugli schermi stretti, e puo essere ridotta.
+
## Versione Pro 1.0.9 test
- Corregge la lettura del catalogo dal wrapper asincrono .NET di Google Play Billing, usando la raccolta managed `ProductDetails` popolata dal binding.
diff --git a/Services/OfficialCadastreService.cs b/Services/OfficialCadastreService.cs
index 594dc46..50215f5 100644
--- a/Services/OfficialCadastreService.cs
+++ b/Services/OfficialCadastreService.cs
@@ -1,4 +1,6 @@
using System.Globalization;
+using System.Net;
+using System.Net.Http.Headers;
using System.Xml.Linq;
namespace LMEMappeCatastaliApp.Services;
@@ -13,8 +15,9 @@ public sealed class OfficialCadastreService
public OfficialCadastreService()
{
- _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("LMEMappeCatastali/0.2");
- _httpClient.DefaultRequestHeaders.Accept.ParseAdd("application/gml+xml");
+ _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("LMEMappeCatastali/1.0 (Android)");
+ _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/gml+xml"));
+ _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml", 0.9));
}
public async Task GetParcelsNearAsync(
@@ -22,14 +25,30 @@ public sealed class OfficialCadastreService
double longitude,
double radiusMeters,
CancellationToken cancellationToken = default)
- => await GetFeaturesNearAsync(
+ {
+ var firstRadius = Math.Clamp(radiusMeters, 12, 30);
+ var xml = await GetFeaturesNearAsync(
latitude,
longitude,
- radiusMeters,
+ firstRadius,
"CP:CadastralParcel",
100,
cancellationToken);
+ if (firstRadius >= 30 || ContainsFeatures(xml))
+ {
+ return xml;
+ }
+
+ return await GetFeaturesNearAsync(
+ latitude,
+ longitude,
+ 30,
+ "CP:CadastralParcel",
+ 100,
+ cancellationToken);
+ }
+
public async Task GetSheetsNearAsync(
double latitude,
double longitude,
@@ -78,15 +97,65 @@ public sealed class OfficialCadastreService
var url = WfsEndpoint + "?" + string.Join("&", query.Select(item =>
$"{Uri.EscapeDataString(item.Key)}={Uri.EscapeDataString(item.Value)}"));
- using var response = await _httpClient.GetAsync(url, HttpCompletionOption.ResponseContentRead, cancellationToken);
- if (!response.IsSuccessStatusCode)
+ for (var attempt = 0; attempt < 3; attempt++)
{
- throw new HttpRequestException($"Il servizio WFS AdE ha risposto {((int)response.StatusCode)}.");
+ try
+ {
+ using var request = new HttpRequestMessage(HttpMethod.Get, url)
+ {
+ Version = HttpVersion.Version11,
+ VersionPolicy = HttpVersionPolicy.RequestVersionOrLower
+ };
+ request.Headers.CacheControl = new CacheControlHeaderValue { NoCache = true };
+
+ using var response = await _httpClient.SendAsync(
+ request,
+ HttpCompletionOption.ResponseContentRead,
+ cancellationToken);
+ if (response.IsSuccessStatusCode)
+ {
+ var xml = await response.Content.ReadAsStringAsync(cancellationToken);
+ ThrowIfExceptionReport(xml);
+ return xml;
+ }
+
+ if (attempt < 2 && IsTransient(response.StatusCode))
+ {
+ await Task.Delay(TimeSpan.FromMilliseconds(500 * (attempt + 1)), cancellationToken);
+ continue;
+ }
+
+ throw new HttpRequestException($"Il servizio WFS AdE ha risposto {((int)response.StatusCode)}.");
+ }
+ catch (HttpRequestException) when (attempt < 2)
+ {
+ await Task.Delay(TimeSpan.FromMilliseconds(500 * (attempt + 1)), cancellationToken);
+ }
+ catch (TaskCanceledException) when (!cancellationToken.IsCancellationRequested && attempt < 2)
+ {
+ await Task.Delay(TimeSpan.FromMilliseconds(500 * (attempt + 1)), cancellationToken);
+ }
}
- var xml = await response.Content.ReadAsStringAsync(cancellationToken);
- ThrowIfExceptionReport(xml);
- return xml;
+ throw new HttpRequestException("Il servizio WFS AdE non ha risposto dopo tre tentativi.");
+ }
+
+ private static bool IsTransient(HttpStatusCode statusCode) =>
+ statusCode is HttpStatusCode.RequestTimeout or HttpStatusCode.TooManyRequests
+ || (int)statusCode >= 500;
+
+ private static bool ContainsFeatures(string xml)
+ {
+ try
+ {
+ var document = XDocument.Parse(xml);
+ return document.Descendants().Any(element =>
+ element.Name.LocalName is "member" or "featureMember");
+ }
+ catch (System.Xml.XmlException)
+ {
+ return false;
+ }
}
private static string Format(double value) => value.ToString("R", CultureInfo.InvariantCulture);
diff --git a/wwwroot/css/app.css b/wwwroot/css/app.css
index b749a94..81c1eaf 100644
--- a/wwwroot/css/app.css
+++ b/wwwroot/css/app.css
@@ -1245,6 +1245,16 @@ input[type="checkbox"] {
transition: top 140ms ease;
}
+.gps-overlay.is-collapsed {
+ grid-template-columns: auto;
+ padding: 5px;
+}
+
+.gps-overlay.is-collapsed .gps-state,
+.gps-overlay.is-collapsed .gps-actions {
+ display: none;
+}
+
.gps-overlay.search-open {
top: 140px;
}
@@ -1267,8 +1277,14 @@ input[type="checkbox"] {
color: #a33227;
}
+.gps-actions {
+ display: grid;
+ gap: 5px;
+}
+
.gps-main,
-.gps-center {
+.gps-center,
+.gps-collapse {
min-height: 38px;
padding: 0 10px;
white-space: nowrap;
@@ -1279,6 +1295,15 @@ input[type="checkbox"] {
color: var(--ink);
}
+.gps-collapse {
+ width: 38px;
+ padding: 0;
+ background: #e7ecef;
+ color: var(--ink);
+ font-size: 22px;
+ line-height: 1;
+}
+
.leaflet-top.leaflet-right {
top: 68px;
right: 2px;
@@ -1566,11 +1591,36 @@ label {
z-index: 820;
max-width: min(620px, calc(100% - 460px));
border-radius: 8px;
- padding: 9px 11px;
+ display: flex;
+ align-items: flex-start;
+ gap: 8px;
+ padding: 7px 7px 7px 11px;
background: rgba(22, 31, 38, 0.88);
color: #fff;
font-size: 12px;
- pointer-events: none;
+ pointer-events: auto;
+}
+
+.map-info[hidden] {
+ display: none;
+}
+
+.map-info-text {
+ min-width: 0;
+ padding: 2px 0;
+ overflow-wrap: anywhere;
+}
+
+.map-info-close {
+ flex: 0 0 28px;
+ width: 28px;
+ min-height: 28px;
+ padding: 0;
+ border: 0;
+ background: transparent;
+ color: #fff;
+ font-size: 20px;
+ line-height: 1;
}
.wms-preview[hidden] {
@@ -1817,12 +1867,12 @@ footer {
}
.leaflet-top.leaflet-right {
- top: 122px;
+ top: 174px;
right: 0;
}
.map-stage.search-active .leaflet-top.leaflet-right {
- top: 190px;
+ top: 242px;
}
.gps-main,
@@ -1835,12 +1885,15 @@ footer {
right: 8px;
bottom: calc(var(--nav-height) + 116px);
left: 8px;
- display: -webkit-box;
max-width: none;
overflow: hidden;
line-height: 1.35;
- overflow-wrap: anywhere;
white-space: normal;
+ }
+
+ .map-info-text {
+ display: -webkit-box;
+ overflow: hidden;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
}
@@ -1864,8 +1917,13 @@ footer {
min-width: 0;
}
+ .gps-overlay {
+ grid-template-columns: minmax(0, 1fr) auto auto;
+ }
+
+ .gps-main,
.gps-center {
- display: none;
+ min-width: 86px;
}
.bottom-tabs button {
diff --git a/wwwroot/js/lme-map.js b/wwwroot/js/lme-map.js
index 06311e5..66f6f22 100644
--- a/wwwroot/js/lme-map.js
+++ b/wwwroot/js/lme-map.js
@@ -50,6 +50,8 @@ const lmeState = {
gpsTrailStartedAt: null,
gpsWatchId: null,
lastPosition: null,
+ infoTimeoutSeconds: 3,
+ infoTimer: null,
fileImportBound: false,
resizeBound: false,
googleMapsLoading: null,
@@ -185,7 +187,9 @@ window.lmeMap = {
setLocalization,
setEntitlements,
importExternalGisFile,
- showInfo: setInfo
+ showInfo: setInfo,
+ hideInfo,
+ setInfoTimeout
};
function setLocalization(options = {}) {
@@ -5433,13 +5437,24 @@ function refreshCadastreLayer() {
});
let errorShown = false;
- cadastreLayer.on("tileerror", () => {
+ cadastreLayer.on("tileerror", event => {
+ const tile = event?.tile;
+ const retryCount = Number(tile?.dataset?.lmeRetry || 0);
+ if (tile && retryCount < 2) {
+ tile.dataset.lmeRetry = String(retryCount + 1);
+ window.setTimeout(() => {
+ const separator = tile.src.includes("?") ? "&" : "?";
+ tile.src = `${tile.src}${separator}_lme_retry=${Date.now()}`;
+ }, 450 * (retryCount + 1));
+ return;
+ }
+
if (errorShown) {
return;
}
errorShown = true;
- setInfo("Layer catastali non caricati dal servizio AdE. Controlla la connessione e riprova.");
+ setInfo("Il servizio cartografico AdE non ha risposto. La connessione internet puo essere attiva: attendi qualche secondo e riprova.");
});
cadastreLayer.on("load", () => {
errorShown = false;
@@ -5905,7 +5920,36 @@ function cadastreUrl() {
}
function setInfo(text) {
- setText("mapInfo", text);
+ const container = document.getElementById("mapInfo");
+ if (!container) return;
+
+ setText("mapInfoText", text);
+ container.hidden = false;
+ if (lmeState.infoTimer) {
+ window.clearTimeout(lmeState.infoTimer);
+ lmeState.infoTimer = null;
+ }
+ if (lmeState.infoTimeoutSeconds > 0) {
+ lmeState.infoTimer = window.setTimeout(hideInfo, lmeState.infoTimeoutSeconds * 1000);
+ }
+}
+
+function hideInfo() {
+ const container = document.getElementById("mapInfo");
+ if (container) container.hidden = true;
+ if (lmeState.infoTimer) {
+ window.clearTimeout(lmeState.infoTimer);
+ lmeState.infoTimer = null;
+ }
+}
+
+function setInfoTimeout(seconds) {
+ const value = Number(seconds);
+ lmeState.infoTimeoutSeconds = Number.isFinite(value) ? Math.min(30, Math.max(0, value)) : 3;
+ const container = document.getElementById("mapInfo");
+ if (container && !container.hidden) {
+ setInfo(document.getElementById("mapInfoText")?.textContent || "");
+ }
}
function setText(id, text) {