Location: say why it failed, retry once, show permission status
Publish Container / publish (push) Successful in 2m15s
Publish Container / publish (push) Successful in 2m15s
The ⌖ button reported 'Location access denied' for every failure, including timeouts and no-GPS-fix errors that have nothing to do with permission. - Specific messages for denied / no fix / timeout, with the exact iPhone steps (Safari tab vs installed web app) and the error code for diagnosis - One patient high-accuracy retry for slow or cold GPS fixes (a real denial is not retried) - Live permission status line under the search row (allowed / not asked / blocked) - Mentions Safari content blockers as a possible cause Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0cbebfef2a
commit
eaff2f55c8
4 files changed
+83
-8
No files matched your search
@@ -324,3 +324,9 @@ body.map-fs #tab-unified-map { padding: 0; }
|
|||||||
.alert-full p { margin: 0 0 0.6rem; }
|
.alert-full p { margin: 0 0 0.6rem; }
|
||||||
.alert-pre { white-space: pre-wrap; }
|
.alert-pre { white-space: pre-wrap; }
|
||||||
.alert-meta { font-size: 0.74rem; color: var(--ink-light); }
|
.alert-meta { font-size: 0.74rem; color: var(--ink-light); }
|
||||||
|
|
||||||
|
/* Location permission status line */
|
||||||
|
.geo-status { font-size: 0.8rem; line-height: 1.45; color: var(--ink-light); margin: 0.5rem 0.15rem 0; padding: 0.5rem 0.75rem; border-radius: 12px; background: var(--paper-darker); }
|
||||||
|
.geo-status.hidden { display: none; }
|
||||||
|
.geo-status.ok { color: #22c55e; }
|
||||||
|
.geo-status.warn { color: var(--ink); background: color-mix(in srgb, #ef4444 14%, var(--paper-darker)); border: 1px solid color-mix(in srgb, #ef4444 45%, transparent); }
|
||||||
@@ -405,6 +405,36 @@ function centerMap(city, force) {
|
|||||||
$('map-recenter-btn')?.addEventListener('click', () => { if (state.currentCity) centerMap(state.currentCity, true); });
|
$('map-recenter-btn')?.addEventListener('click', () => { if (state.currentCity) centerMap(state.currentCity, true); });
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
// ── Location permission status (so "blocked" is visible, not guessed) ────────
|
||||||
|
const GeoStatus = (() => {
|
||||||
|
const el = $('geo-status');
|
||||||
|
let status = null;
|
||||||
|
async function render() {
|
||||||
|
if (!el || !navigator.permissions || !navigator.geolocation) return;
|
||||||
|
try {
|
||||||
|
if (!status) {
|
||||||
|
status = await navigator.permissions.query({ name: 'geolocation' });
|
||||||
|
status.addEventListener('change', render);
|
||||||
|
}
|
||||||
|
} catch { return; }
|
||||||
|
const s = status.state;
|
||||||
|
const ios = /iPad|iPhone|iPod/.test(navigator.userAgent) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
|
||||||
|
const standalone = window.matchMedia('(display-mode: standalone)').matches || navigator.standalone === true;
|
||||||
|
if (s === 'granted') { el.className = 'geo-status ok'; el.textContent = '📍 Location: allowed. Tap ⌖ to use it.'; }
|
||||||
|
else if (s === 'prompt') { el.className = 'geo-status'; el.textContent = '📍 Location: not asked yet. Tap ⌖ and choose Allow when your phone asks.'; }
|
||||||
|
else if (s === 'denied') {
|
||||||
|
el.className = 'geo-status warn';
|
||||||
|
el.textContent = ios
|
||||||
|
? `📍 Location: your phone has this ${standalone ? 'app' : 'site'} set to Don't Allow. Fix it in Settings → Privacy & Security → Location Services → ${standalone ? '"NeCloud Weather"' : 'Safari Websites'}. A Safari content blocker can also do this: try aA → Turn Off Content Blockers.`
|
||||||
|
: '📍 Location: blocked for this site. Click the lock icon in the address bar → Location → Allow.';
|
||||||
|
} else { el.classList.add('hidden'); return; }
|
||||||
|
el.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
render();
|
||||||
|
return { refresh: render };
|
||||||
|
})();
|
||||||
|
|
||||||
// ── Public hooks ─────────────────────────────────────────────────────────────
|
// ── Public hooks ─────────────────────────────────────────────────────────────
|
||||||
let lastOutlook = null;
|
let lastOutlook = null;
|
||||||
window.AppUI = {
|
window.AppUI = {
|
||||||
@@ -427,6 +457,7 @@ window.AppUI = {
|
|||||||
if (stationFresh) renderStationContext();
|
if (stationFresh) renderStationContext();
|
||||||
},
|
},
|
||||||
centerMap,
|
centerMap,
|
||||||
|
refreshGeoStatus: () => GeoStatus.refresh(),
|
||||||
onAir(air) { renderAqi(air); },
|
onAir(air) { renderAqi(air); },
|
||||||
refreshFx() { const c = $('sky-fx'); if (c && fx.kind) { cancelAnimationFrame(fx.raf); fxResize(c); fxSeed(fx.kind); if (fx.parts.length) fxFrame(performance.now()); } },
|
refreshFx() { const c = $('sky-fx'); if (c && fx.kind) { cancelAnimationFrame(fx.raf); fxResize(c); fxSeed(fx.kind); if (fx.parts.length) fxFrame(performance.now()); } },
|
||||||
onStation() { syncTopbarLocation(); renderStationContext(); },
|
onStation() { syncTopbarLocation(); renderStationContext(); },
|
||||||
|
|||||||
+45
-8
@@ -2273,26 +2273,63 @@ searchBtn.addEventListener('click', doSearch);
|
|||||||
searchInput.addEventListener('keydown', e => { if (e.key === 'Enter') doSearch(); });
|
searchInput.addEventListener('keydown', e => { if (e.key === 'Enter') doSearch(); });
|
||||||
|
|
||||||
// ── Geolocation ───────────────────────────────────────────────────────────────
|
// ── Geolocation ───────────────────────────────────────────────────────────────
|
||||||
locateBtn.addEventListener('click', () => {
|
// Why a location request failed, in plain language (the browser tells us: 1 denied, 2 unavailable, 3 timeout)
|
||||||
|
const IS_IOS = /iPad|iPhone|iPod/.test(navigator.userAgent) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
|
||||||
|
const IS_STANDALONE = window.matchMedia('(display-mode: standalone)').matches || navigator.standalone === true;
|
||||||
|
async function geoPermissionState() {
|
||||||
|
try { return (await navigator.permissions.query({ name: 'geolocation' })).state; } catch { return 'unknown'; }
|
||||||
|
}
|
||||||
|
function geoErrorHelp(err, perm) {
|
||||||
|
const code = err && err.code;
|
||||||
|
const tag = ` (error ${code ?? '?'}${perm && perm !== 'unknown' ? ', permission: ' + perm : ''})`;
|
||||||
|
if (code === 1) {
|
||||||
|
if (IS_IOS) {
|
||||||
|
const where = IS_STANDALONE
|
||||||
|
? 'Settings → Privacy & Security → Location Services → "NeCloud Weather" → While Using the App'
|
||||||
|
: 'Settings → Privacy & Security → Location Services → Safari Websites → While Using, then in Safari tap aA → Website Settings → Location → Allow';
|
||||||
|
return `Your phone says location is off for ${IS_STANDALONE ? 'this app' : 'this site'}. Turn it on in ${where}. If you use a Safari content blocker, also try aA → Turn Off Content Blockers for this site.${tag}`;
|
||||||
|
}
|
||||||
|
return `Location is blocked for this site. Click the lock icon in the address bar → Location → Allow, then try again.${tag}`;
|
||||||
|
}
|
||||||
|
if (code === 2) return `Your device couldn't find your position yet (no GPS or Wi-Fi fix). Make sure Location Services is on, turn off Low Power Mode, and try again, ideally on Wi-Fi or outdoors.${tag}`;
|
||||||
|
if (code === 3) return `Timed out waiting for your location. Try again in a moment.${tag}`;
|
||||||
|
return `Could not get your location.${tag}`;
|
||||||
|
}
|
||||||
|
function geoOnce(opts) {
|
||||||
|
return new Promise((resolve, reject) => navigator.geolocation.getCurrentPosition(resolve, reject, opts));
|
||||||
|
}
|
||||||
|
// A slow or cold GPS often times out the first time: retry once with a patient, high-accuracy request.
|
||||||
|
// A real "denied" is final, so don't retry that.
|
||||||
|
async function getPositionRobust() {
|
||||||
|
try {
|
||||||
|
return await geoOnce({ enableHighAccuracy: false, timeout: 12000, maximumAge: 5 * 60 * 1000 });
|
||||||
|
} catch (e) {
|
||||||
|
if (e && e.code === 1) throw e;
|
||||||
|
return await geoOnce({ enableHighAccuracy: true, timeout: 20000, maximumAge: 0 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
locateBtn.addEventListener('click', async () => {
|
||||||
if (!navigator.geolocation) { showErr('Geolocation not supported.'); return; }
|
if (!navigator.geolocation) { showErr('Geolocation not supported.'); return; }
|
||||||
clearErr();
|
clearErr();
|
||||||
hide(weatherDiv);
|
hide(weatherDiv);
|
||||||
show(loadingEl);
|
show(loadingEl);
|
||||||
|
try {
|
||||||
navigator.geolocation.getCurrentPosition(async pos => {
|
const pos = await getPositionRobust();
|
||||||
const { latitude: lat, longitude: lon } = pos.coords;
|
const { latitude: lat, longitude: lon } = pos.coords;
|
||||||
try {
|
try {
|
||||||
const city = await buildDetectedCity(lat, lon);
|
const city = await buildDetectedCity(lat, lon);
|
||||||
await loadAll(city, 'gps-button');
|
await loadAll(city, 'gps-button');
|
||||||
} catch {
|
} catch {
|
||||||
showErr('Could not fetch weather for your location.');
|
showErr('Could not fetch weather for your location.');
|
||||||
} finally {
|
|
||||||
hide(loadingEl);
|
|
||||||
}
|
}
|
||||||
}, () => {
|
} catch (err) {
|
||||||
|
if (state.currentWeather) show(weatherDiv);
|
||||||
|
showErr(geoErrorHelp(err, await geoPermissionState()));
|
||||||
|
} finally {
|
||||||
hide(loadingEl);
|
hide(loadingEl);
|
||||||
showErr('Location access denied.');
|
if (window.AppUI && window.AppUI.refreshGeoStatus) window.AppUI.refreshGeoStatus();
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
unitsBtn.addEventListener('click', () => {
|
unitsBtn.addEventListener('click', () => {
|
||||||
|
|||||||
@@ -449,6 +449,7 @@
|
|||||||
<button id="units-btn" title="Toggle units">US</button>
|
<button id="units-btn" title="Toggle units">US</button>
|
||||||
</div>
|
</div>
|
||||||
<ul id="suggestions" class="suggestions hidden"></ul>
|
<ul id="suggestions" class="suggestions hidden"></ul>
|
||||||
|
<p id="geo-status" class="geo-status hidden" role="status"></p>
|
||||||
<p id="error-msg" class="error hidden"></p>
|
<p id="error-msg" class="error hidden"></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user