Podcast-Oberfläche aus diora-android 3d1281c (Listen + Abonnieren-Vorschau), sw v63
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
69b22cf2e3
commit
d3e432e165
2 changed files with 153 additions and 5 deletions
|
|
@ -2,7 +2,7 @@
|
|||
* diora service worker — caches the app shell for offline use.
|
||||
*/
|
||||
|
||||
const CACHE = 'diora-v62';
|
||||
const CACHE = 'diora-v63';
|
||||
const SHELL = [
|
||||
'/static/css/app.css',
|
||||
'/static/js/app.js',
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copied from diora-android 99528f2 2026-10-02: web-native/static/native-podcast.js
|
||||
// Copied from diora-android 3d1281c 2026-10-02: web-native/static/native-podcast.js
|
||||
// by tools/sync_podcast_ui.py. Do not edit here: change it in diora-android
|
||||
// and copy again. The browser host is static/js/podcasts-web.js.
|
||||
|
||||
|
|
@ -47,6 +47,8 @@
|
|||
let view = 'queue';
|
||||
let feedView = null; // {feed, episodes} while one podcast is open
|
||||
let searchResults = null;
|
||||
let preview = null; // {id, url, data | choices | error} while a feed is looked at before subscribing
|
||||
let previewSeq = 0;
|
||||
let refreshing = false;
|
||||
let selected = null; // Set of episode ids while choosing several (queue or inbox)
|
||||
let locked = false; // queue locked: no dragging
|
||||
|
|
@ -256,12 +258,13 @@
|
|||
removePlayedItem(),
|
||||
]),
|
||||
group('▶', 'Abspielen', ['Sprungweiten', 'skip_silence' in prefs && 'Stille', '„gehört“',
|
||||
'headset_next' in prefs && 'Kopfhörer'].filter(Boolean).join(', '), () => [
|
||||
'headset_next' in prefs && 'Kopfhörer', 'notify_buttons' in prefs && 'Benachrichtigung'].filter(Boolean).join(', '), () => [
|
||||
skipItem('skip_back', 'Zurückspringen', [5, 10, 15, 30]),
|
||||
skipItem('skip_forward', 'Vorspringen', [10, 15, 30, 45, 60]),
|
||||
silenceItem(),
|
||||
playedItem(),
|
||||
headsetItem(),
|
||||
notifyItem(),
|
||||
]),
|
||||
DL && {icon: '↓', label: 'Downloads', hint: dlSummary(), action: openDownloads},
|
||||
DL && group('⚙︎', 'Download-Einstellungen', 'automatisch, gleichzeitig, Limit, aufräumen', () => [
|
||||
|
|
@ -310,6 +313,29 @@
|
|||
])};
|
||||
}
|
||||
|
||||
// The notification's two extra buttons (app only: NotifyButtons.kt), like AntennaPod's.
|
||||
const NOTIFY = [
|
||||
{key: 'next', label: 'Folge überspringen', hint: 'zählt als gehört, die nächste spielt'},
|
||||
{key: 'chapter', label: 'Nächstes Kapitel', hint: 'wenn die Folge Kapitel hat'},
|
||||
{key: 'speed', label: 'Tempo', hint: '1× · 1,2× · 1,5× · 1,75× · 2×'},
|
||||
{key: 'sleep', label: 'Sleep-Timer', hint: '15 Minuten an / aus'},
|
||||
];
|
||||
function notifyItem() {
|
||||
if (!N.podcastSetNotifyButtons || !Array.isArray(devicePrefs().notify_buttons)) return null;
|
||||
const now = devicePrefs().notify_buttons;
|
||||
const names = keys => keys.map(k => (NOTIFY.find(n => n.key === k) || {}).label).filter(Boolean).join(', ') || 'keine';
|
||||
const sheet = chosen => openSheet('Zwei Knöpfe in der Benachrichtigung', NOTIFY.map(n => ({
|
||||
icon: chosen.includes(n.key) ? '☑' : '☐', label: n.label, hint: n.hint,
|
||||
// A third one replaces the one chosen first.
|
||||
action: () => {
|
||||
const next = chosen.includes(n.key) ? chosen.filter(k => k !== n.key) : chosen.concat(n.key).slice(-2);
|
||||
N.podcastSetNotifyButtons(next.join(','));
|
||||
toast('Benachrichtigung: ' + names(next));
|
||||
},
|
||||
})));
|
||||
return {icon: '⊞', label: 'Knöpfe in der Benachrichtigung', hint: names(now), action: () => sheet(now)};
|
||||
}
|
||||
|
||||
function silenceItem() {
|
||||
if (!N.podcastSetPref || !('skip_silence' in devicePrefs())) return null;
|
||||
const on = !!devicePrefs().skip_silence;
|
||||
|
|
@ -659,7 +685,9 @@
|
|||
button('⋮', 'Einstellungen', settingsMenu)].filter(Boolean));
|
||||
root.append(bar);
|
||||
|
||||
if (feedView) {
|
||||
if (preview) {
|
||||
renderPreview();
|
||||
} else if (feedView) {
|
||||
renderFeed();
|
||||
} else {
|
||||
const tabs = el('div', 'nui-pod-tabs');
|
||||
|
|
@ -1700,7 +1728,7 @@
|
|||
|
||||
function search(q) {
|
||||
if (!q) return;
|
||||
if (/^https?:\/\//i.test(q)) { subscribe(q, q); return; }
|
||||
if (/^https?:\/\//i.test(q)) { if (N.podcastPreview) openPreview(q); else subscribe(q, q); return; }
|
||||
searchDraft = null;
|
||||
const test = matcher(q);
|
||||
const s = searchResults = {
|
||||
|
|
@ -1759,11 +1787,115 @@
|
|||
if (!r.ok || !data.ok) throw new Error(data.error || r.status);
|
||||
toast(`„${data.title || title}“ abonniert`);
|
||||
refresh(false);
|
||||
return true;
|
||||
} catch (e) {
|
||||
toast(navigator.onLine ? 'Dieser Feed lässt sich nicht abonnieren.' : 'Abonnieren braucht eine Verbindung.');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// A feed from another app (shared link, pcast:// …) or a pasted address:
|
||||
// the app fetches it (N.podcastPreview, FeedPreview.kt) and the page shows
|
||||
// title, cover, description and the newest episodes before subscribing.
|
||||
function openPreview(url) {
|
||||
if (!N.podcastPreview) { subscribe(url, url); return; }
|
||||
closePlayer();
|
||||
feedView = null;
|
||||
selected = null;
|
||||
preview = {id: ++previewSeq, url};
|
||||
if (typeof showTab === 'function') showTab('podcasts');
|
||||
render();
|
||||
scrollTop(0);
|
||||
N.podcastPreview(preview.id, url);
|
||||
}
|
||||
|
||||
function closePreview() {
|
||||
if (!preview) return false;
|
||||
preview = null;
|
||||
render();
|
||||
return true;
|
||||
}
|
||||
|
||||
function renderPreview() {
|
||||
const p = preview;
|
||||
const d = p.data || {};
|
||||
const head = el('div', 'nui-pod-feedhead');
|
||||
head.append(button('‹', 'Zurück', closePreview), art(d.artwork, 'big'));
|
||||
const info = el('div', 'nui-pod-main');
|
||||
info.append(el('div', 'nui-pod-title', plain(d.title) || 'Podcast'),
|
||||
el('div', 'nui-pod-meta', plain(d.author) || p.url));
|
||||
head.append(info);
|
||||
root.append(head);
|
||||
if (p.choices) {
|
||||
root.append(el('div', 'nui-pod-empty', 'Diese Seite verlinkt mehrere Feeds. Welcher ist es?'));
|
||||
const list = el('div', 'nui-pod-list');
|
||||
for (const c of p.choices) {
|
||||
const row = el('div', 'nui-pod-row');
|
||||
const main = el('div', 'nui-pod-main');
|
||||
main.append(el('div', 'nui-pod-title', plain(c.title) || c.url), el('div', 'nui-pod-meta', c.url));
|
||||
row.append(main);
|
||||
row.addEventListener('click', () => openPreview(c.url));
|
||||
list.append(row);
|
||||
}
|
||||
root.append(list);
|
||||
return;
|
||||
}
|
||||
if (p.error) {
|
||||
// The server may still manage (it fetches the feed itself).
|
||||
const box = el('div', 'nui-pod-offer', p.error);
|
||||
const actions = el('div', 'nui-pod-offer-row');
|
||||
const anyway = el('button', 'btn', 'Trotzdem abonnieren');
|
||||
anyway.type = 'button';
|
||||
anyway.addEventListener('click', () => subscribeFromPreview(p.url, p.url, anyway));
|
||||
actions.append(anyway);
|
||||
box.append(actions);
|
||||
root.append(box);
|
||||
return;
|
||||
}
|
||||
if (!p.data) { empty('Feed wird geladen …'); return; }
|
||||
const known = feeds().find(f => f.rss_url === d.url || f.rss_url === p.url);
|
||||
const actions = el('div', 'nui-pod-offer-row');
|
||||
const go = el('button', 'btn', known ? 'Abonniert – öffnen' : 'Abonnieren');
|
||||
go.type = 'button';
|
||||
go.addEventListener('click', () => {
|
||||
if (known) { preview = null; openFeed(known); return; }
|
||||
subscribeFromPreview(d.url, d.title, go);
|
||||
});
|
||||
actions.append(go);
|
||||
root.append(actions);
|
||||
if (d.description) {
|
||||
const desc = el('div', 'nui-pod-offer', plain(d.description));
|
||||
desc.style.whiteSpace = 'pre-line';
|
||||
root.append(desc);
|
||||
}
|
||||
const eps = d.episodes || [];
|
||||
if (eps.length) {
|
||||
const list = el('div', 'nui-pod-list');
|
||||
for (const e of eps) {
|
||||
const row = el('div', 'nui-pod-row');
|
||||
row.append(art(e.artwork || d.artwork));
|
||||
const main = el('div', 'nui-pod-main');
|
||||
main.append(el('div', 'nui-pod-title', plain(e.title)),
|
||||
el('div', 'nui-pod-meta', [e.duration && minutes(e.duration), day(e.date)].filter(Boolean).join(' · ')));
|
||||
row.append(main);
|
||||
list.append(row);
|
||||
}
|
||||
root.append(list);
|
||||
}
|
||||
}
|
||||
|
||||
async function subscribeFromPreview(url, title, btn) {
|
||||
btn.disabled = true;
|
||||
const ok = await subscribe(url, title);
|
||||
btn.disabled = false;
|
||||
if (!ok) return;
|
||||
preview = null;
|
||||
view = 'feeds';
|
||||
try { localStorage.setItem('nui_pod_view', view); } catch (e) {}
|
||||
render();
|
||||
scrollTop(0);
|
||||
}
|
||||
|
||||
// ---- drag to reorder (touch) -----------------------------------------------------------
|
||||
|
||||
function dragSort(handle, row, list) {
|
||||
|
|
@ -2950,6 +3082,20 @@
|
|||
|
||||
// ---- hooks from the app ---------------------------------------------------------------
|
||||
|
||||
// A podcast address from another app is waiting (MainActivity): show its preview.
|
||||
window.__dioraSubscribe = function () {
|
||||
const url = N.podcastTakeSubscribe ? N.podcastTakeSubscribe() : '';
|
||||
if (url) openPreview(url);
|
||||
};
|
||||
|
||||
window.__dioraPreview = function (id, d) {
|
||||
if (!preview || preview.id !== id) return;
|
||||
if (d && d.ok) preview = {id, url: d.url, data: d};
|
||||
else if (d && d.choices && d.choices.length) preview = {id, url: preview.url, choices: d.choices};
|
||||
else preview = {id, url: preview.url, error: (d && d.error) || 'Dieser Feed ließ sich nicht laden.'};
|
||||
render();
|
||||
};
|
||||
|
||||
window.__dioraPodcasts = function (json) {
|
||||
state = typeof json === 'string' ? parse(json, state) : json;
|
||||
render();
|
||||
|
|
@ -2996,6 +3142,7 @@
|
|||
if (typeof _currentTopTab !== 'undefined' && _currentTopTab === 'podcasts' && stopSelecting()) return true;
|
||||
if (back && back()) return true;
|
||||
if (typeof _currentTopTab !== 'undefined' && _currentTopTab === 'podcasts') {
|
||||
if (closePreview()) return true;
|
||||
if (feedView) { feedView = null; render(); return true; }
|
||||
showTab('books');
|
||||
return true;
|
||||
|
|
@ -3005,5 +3152,6 @@
|
|||
|
||||
// Pull the latest state once per start; the server has done the fetching.
|
||||
N.podcastRefresh(false);
|
||||
window.__dioraSubscribe();
|
||||
});
|
||||
})();
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue