Add iOS pull-to-refresh and PNG app icons

This commit is contained in:
2026-04-12 20:39:56 +02:00
parent cd7526bd80
commit 4e9fe2de6a
10 changed files with 160 additions and 9 deletions
+120
View File
@@ -992,6 +992,125 @@
return window.matchMedia("(display-mode: standalone)").matches || window.navigator.standalone === true;
}
function isAppleTouchDevice() {
return /iPhone|iPad|iPod/i.test(window.navigator.userAgent)
|| (window.navigator.platform === "MacIntel" && window.navigator.maxTouchPoints > 1);
}
function initPullToRefresh() {
if (!isStandaloneMode() || !isAppleTouchDevice()) {
return;
}
const indicator = document.querySelector("[data-pull-refresh-indicator]");
const body = document.body;
const threshold = 96;
let isTracking = false;
let isReady = false;
let startY = 0;
const setIndicator = message => {
if (indicator) {
indicator.textContent = message;
}
};
const resetState = () => {
body.classList.remove("is-pull-refreshing", "is-pull-refresh-ready");
isTracking = false;
isReady = false;
startY = 0;
setIndicator("Zum Aktualisieren ziehen");
};
const scrollTop = () => Math.max(
window.scrollY || 0,
document.documentElement.scrollTop || 0,
document.body.scrollTop || 0
);
const canStart = target => {
if (scrollTop() > 0) {
return false;
}
if (!(target instanceof Element)) {
return true;
}
return !target.closest("input, textarea, select, button");
};
window.addEventListener("touchstart", event => {
if (event.touches.length !== 1 || !canStart(event.target)) {
resetState();
return;
}
isTracking = true;
startY = event.touches[0].clientY;
}, { passive: true });
window.addEventListener("touchmove", event => {
if (!isTracking || event.touches.length !== 1) {
return;
}
if (scrollTop() > 0) {
resetState();
return;
}
const delta = event.touches[0].clientY - startY;
if (delta <= 0) {
body.classList.remove("is-pull-refreshing", "is-pull-refresh-ready");
isReady = false;
setIndicator("Zum Aktualisieren ziehen");
return;
}
if (delta > 18) {
body.classList.add("is-pull-refreshing");
event.preventDefault();
}
if (delta >= threshold) {
if (!isReady) {
body.classList.add("is-pull-refresh-ready");
setIndicator("Loslassen zum Aktualisieren");
isReady = true;
}
return;
}
if (isReady) {
body.classList.remove("is-pull-refresh-ready");
isReady = false;
}
setIndicator("Zum Aktualisieren ziehen");
}, { passive: false });
window.addEventListener("touchend", () => {
if (!isTracking) {
return;
}
if (isReady) {
body.classList.remove("is-pull-refreshing", "is-pull-refresh-ready");
body.classList.add("is-pull-refresh-reloading");
setIndicator("Wird aktualisiert ...");
window.location.reload();
return;
}
resetState();
}, { passive: true });
window.addEventListener("touchcancel", resetState, { passive: true });
}
function initPushControls() {
const panel = document.querySelector("[data-push-panel]");
if (!panel) {
@@ -1147,5 +1266,6 @@
initDashboardCharts();
initSportTypeManager();
initPwaShell();
initPullToRefresh();
initPushControls();
})();