diff --git a/e2e-khairat-lifestyle.cjs b/e2e-khairat-lifestyle.cjs index a12b00d..0b7ff49 100644 --- a/e2e-khairat-lifestyle.cjs +++ b/e2e-khairat-lifestyle.cjs @@ -60,6 +60,22 @@ async function main() { const distanceVisible = await page.locator('.distance-note', { hasText: 'km to Makkah' }).isVisible().catch(() => false); record('Qibla: shows distance to Makkah', distanceVisible); + // Regression guard: many Android browsers fire plain 'deviceorientation' + // with absolute:false even though alpha is a usable heading — a prior + // version required absolute:true and silently never detected the compass + // on those devices. Simulate exactly that event shape. + const staticNoteBeforeSensor = await page.locator('.static-note').isVisible().catch(() => false); + record('Qibla: shows the no-sensor fallback note before any orientation event', staticNoteBeforeSensor); + await page.evaluate(() => { + const evt = new Event('deviceorientation'); + Object.defineProperty(evt, 'alpha', { value: 90 }); + Object.defineProperty(evt, 'absolute', { value: false }); + window.dispatchEvent(evt); + }); + await page.waitForTimeout(500); + const staticNoteAfterSensor = await page.locator('.static-note').isVisible().catch(() => false); + record('Qibla: accepts a non-absolute deviceorientation event as a valid compass reading (Android)', !staticNoteAfterSensor); + // ── Prayer Times ── await gotoTab(page, 'Prayer Times'); await page.waitForTimeout(500); diff --git a/src/lib/QiblaFinder.svelte b/src/lib/QiblaFinder.svelte index 061bdbc..d5fdd12 100644 --- a/src/lib/QiblaFinder.svelte +++ b/src/lib/QiblaFinder.svelte @@ -33,34 +33,58 @@ distanceKm = Math.round(R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))); } + let listenersAttached = false; + function handleOrientation(e) { - const heading = e.webkitCompassHeading ?? (e.absolute && e.alpha != null ? 360 - e.alpha : null); + let heading = e.webkitCompassHeading; + if (heading == null && e.alpha != null) { + // Prefer a true geomagnetic reading when the browser provides one + // (absolute: true), but don't require it — many Android browsers + // never set absolute:true even though alpha is still a usable + // heading, so requiring it strictly left the compass permanently + // "undetected" on those devices. + heading = 360 - e.alpha; + } if (heading != null) { compassHeading = heading; compassSupported = true; } } + function attachOrientationListeners() { + if (listenersAttached) return; + listenersAttached = true; + window.addEventListener('deviceorientationabsolute', handleOrientation, true); + window.addEventListener('deviceorientation', handleOrientation, true); + } + async function findQibla() { status = 'locating'; errorMsg = ''; + compassSupported = false; + compassHeading = null; + + // Request device-orientation permission (iOS 13+) synchronously here, + // inside this click handler — Safari expires the user-activation grant + // almost immediately, so requesting it *after* the async geolocation + // round-trip (which can take several seconds) reliably fails silently + // with no prompt ever shown. Registering listeners for everyone else + // immediately too, so no early orientation events are missed while + // still waiting on the GPS fix. + if (typeof DeviceOrientationEvent !== 'undefined') { + if (typeof DeviceOrientationEvent.requestPermission === 'function') { + try { + const perm = await DeviceOrientationEvent.requestPermission(); + if (perm === 'granted') attachOrientationListeners(); + } catch { /* denied or unsupported — static bearing still shown */ } + } else { + attachOrientationListeners(); + } + } + if (!navigator.geolocation) { status = 'error'; errorMsg = 'Geolocation is not available on this device/browser.'; return; } navigator.geolocation.getCurrentPosition( - async (pos) => { - computeQibla(pos.coords.latitude, pos.coords.longitude); - status = 'ready'; - // Compass heading requires an explicit permission prompt on iOS 13+, - // which must be triggered by this same user gesture. - if (typeof DeviceOrientationEvent !== 'undefined' && typeof DeviceOrientationEvent.requestPermission === 'function') { - try { - const perm = await DeviceOrientationEvent.requestPermission(); - if (perm === 'granted') window.addEventListener('deviceorientation', handleOrientation, true); - } catch { /* permission denied or unsupported — static bearing still shown */ } - } else if (typeof DeviceOrientationEvent !== 'undefined') { - window.addEventListener('deviceorientationabsolute', handleOrientation, true); - window.addEventListener('deviceorientation', handleOrientation, true); - } - }, + (pos) => { computeQibla(pos.coords.latitude, pos.coords.longitude); status = 'ready'; }, (err) => { status = 'error'; errorMsg = err.message || 'Could not get your location.'; }, { enableHighAccuracy: true, timeout: 10000 } );