reworked app screens and added vibration

This commit is contained in:
Florian Zumpe
2026-06-16 17:30:55 +02:00
parent 40f230864b
commit 44b1700c48
13 changed files with 418 additions and 53 deletions
+125 -27
View File
@@ -155,6 +155,7 @@
function stopRouteSensors() {
ns.Geo.stop();
ns.Orientation.stop();
ns.Vibration.stop();
state.deviceHeading = null;
}
@@ -242,20 +243,27 @@
<button type="button" data-poi-id="${poi.id}">
<strong>${escapeHtml(poi.title)}</strong>
<span>${escapeHtml(poi.description || '')}</span>
<span class="poi-proximity" aria-label="Entfernung wird ermittelt">
<svg viewBox="0 0 32 32" aria-hidden="true" focusable="false">
<path d="M16 2 27 28 16 23 5 28Z"/>
</svg>
<small>–</small>
</span>
</button>
</li>
`);
});
updatePoiIndicators(state.position);
}
function showPoi(poi, automatic) {
function showPoi(poi) {
setActivePoi(poi.id);
$('#poi-title').text(poi.title);
$('#poi-description').text(poi.description || '');
ns.Slideshow.show(poi.images);
ns.AudioPlayer.load(poi.audioUrl);
navigate('#poi-page');
if (automatic && poi.audioUrl) ns.AudioPlayer.play().catch(() => {});
}
function nearestRoutePointIndex(position) {
@@ -282,12 +290,63 @@
return nearestIndex;
}
function updateNavigation(position) {
function effectiveHeading(position) {
const movementHeading = position
&& Number.isFinite(position.heading)
&& Number(position.speed) > 0.5
? position.heading
: null;
return state.deviceHeading ?? movementHeading;
}
function poiMetrics(position) {
if (!position || !state.route?.pois?.length) return [];
const heading = effectiveHeading(position);
return state.route.pois.map(poi => {
const distance = ns.Distance.meters(position, poi);
const bearing = ns.Distance.bearing(position, poi);
return {
poi,
distance,
bearing,
rotation: heading == null
? bearing
: ns.Distance.normalizeDegrees(bearing - heading)
};
});
}
function updatePoiIndicators(position) {
const metrics = poiMetrics(position);
metrics.forEach(metric => {
const indicator = $(`#poi-list [data-poi-id="${metric.poi.id}"] .poi-proximity`);
const distance = Math.round(metric.distance);
indicator
.attr('aria-label', `${distance} Meter entfernt`)
.attr('title', `${distance} m entfernt`);
indicator.find('svg').css('transform', `rotate(${metric.rotation.toFixed(1)}deg)`);
indicator.find('small').text(`${distance} m`);
});
return metrics;
}
function nearestPendingPoi(metrics) {
return metrics
.filter(metric => !state.triggered.has(metric.poi.id))
.reduce((nearest, metric) => (
!nearest || metric.distance < nearest.distance ? metric : nearest
), null);
}
function updateRouteProgress(position) {
const points = state.route?.points || [];
if (state.routeState !== 'running' || !points.length) return;
if (!points.length) return null;
const nearestIndex = nearestRoutePointIndex(position);
if (nearestIndex == null) return;
if (nearestIndex == null) return null;
state.routeProgressIndex = state.routeProgressIndex == null
? nearestIndex
@@ -308,32 +367,72 @@
if (targetIndex === lastIndex && distance <= reachedRadius) {
state.routeProgressIndex = lastIndex;
return { reached: true, target, distance: 0 };
}
return { reached: false, target, distance };
}
function showPoiNavigation(position, metric) {
const heading = effectiveHeading(position);
const distance = Math.round(metric.distance);
$('#navigation-arrow').css('transform', `rotate(${metric.rotation.toFixed(1)}deg)`);
showNavigation(
heading == null
? `Norden oben · nächster POI: ${metric.poi.title}`
: `Nächster POI: ${metric.poi.title}`,
`${distance} m`
);
$('#navigation-footer').attr(
'aria-label',
`Nächster POI ${metric.poi.title} in ${distance} Metern`
);
}
function showRoutePointNavigation(position, progress) {
if (!progress) {
$('#navigation-arrow').css('transform', 'rotate(0deg)');
showNavigation('Ziel der Route erreicht', '0 m');
$('#navigation-footer').attr('aria-label', 'Ziel der Route erreicht');
showNavigation('Keine Navigationspunkte vorhanden', '–');
return;
}
const bearing = ns.Distance.bearing(position, target);
const movementHeading = Number.isFinite(position.heading) && Number(position.speed) > 0.5
? position.heading
: null;
const heading = state.deviceHeading ?? movementHeading;
if (progress.reached) {
$('#navigation-arrow').css('transform', 'rotate(0deg)');
showNavigation('Alle Stationen und das Routenziel erreicht', '0 m');
$('#navigation-footer').attr('aria-label', 'Alle Stationen und das Routenziel erreicht');
return;
}
const bearing = ns.Distance.bearing(position, progress.target);
const heading = effectiveHeading(position);
const rotation = heading == null
? bearing
: ns.Distance.normalizeDegrees(bearing - heading);
const distance = Math.round(progress.distance);
$('#navigation-arrow').css('transform', `rotate(${rotation.toFixed(1)}deg)`);
showNavigation(
heading == null ? 'Norden oben · nächster Routenpunkt' : 'Nächster Routenpunkt',
`${Math.round(distance)} m`
`${distance} m`
);
$('#navigation-footer').attr(
'aria-label',
`Nächster Routenpunkt in ${Math.round(distance)} Metern`
`Nächster Routenpunkt in ${distance} Metern`
);
}
function updateNavigation(position) {
if (state.routeState !== 'running') return;
const progress = updateRouteProgress(position);
const metrics = updatePoiIndicators(position);
const nextPoi = nearestPendingPoi(metrics);
if (nextPoi) showPoiNavigation(position, nextPoi);
else showRoutePointNavigation(position, progress);
}
function updateHeading(heading) {
if (state.deviceHeading == null) {
state.deviceHeading = heading;
@@ -347,26 +446,25 @@
function evaluatePosition(position) {
state.position = position;
if (!state.route || state.routeState !== 'running') return;
if (!state.route) return;
const metrics = updatePoiIndicators(position);
if (state.routeState !== 'running') return;
let nearest = nearestPendingPoi(metrics);
updateNavigation(position);
let nearest = null;
state.route.pois.forEach(poi => {
if (state.triggered.has(poi.id)) return;
const distance = ns.Distance.meters(position, poi);
if (!nearest || distance < nearest.distance) nearest = { poi, distance };
});
$('#tracking-status').text(
`GPS aktiv · Genauigkeit ${Math.round(position.accuracy)} m`
+ (nearest ? ` · nächste Station ${Math.round(nearest.distance)} m` : '')
+ (nearest ? ` · nächster POI ${Math.round(nearest.distance)} m` : ' · alle POIs erreicht')
);
if (nearest && nearest.distance <= nearest.poi.triggerRadiusM) {
state.triggered.add(nearest.poi.id);
if (navigator.vibrate) navigator.vibrate([120, 80, 120]);
showPoi(nearest.poi, true);
ns.Vibration.start(ns.Vibration.Patterns.ACTIVE_POI);
showPoi(nearest.poi);
nearest = nearestPendingPoi(metrics);
updateNavigation(position);
}
}
@@ -399,7 +497,7 @@
}
if (!orientationAvailable) {
$('#navigation-mode').text('Norden oben · nächster Routenpunkt');
$('#navigation-mode').text('Norden oben · Navigation');
}
}
@@ -426,7 +524,7 @@
activate('[data-poi-id]', function () {
const poi = state.route?.pois.find(item => item.id === Number($(this).data('poi-id')));
if (poi) showPoi(poi, false);
if (poi) showPoi(poi);
});
activate('[data-page-target]', function () {