From 4ed1eef421fa3af5aa25d231cc3ad36e8cdf4419 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Sat, 7 Nov 2026 12:03:15 +0100 Subject: sonix: the Playlists tab --- desktop/modules/sonix/SonixPage.qml | 2 +- desktop/modules/sonix/SonixPlaylists.qml | 151 +++++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 desktop/modules/sonix/SonixPlaylists.qml (limited to 'desktop/modules/sonix') diff --git a/desktop/modules/sonix/SonixPage.qml b/desktop/modules/sonix/SonixPage.qml index 1ee89a6..4e1c12c 100644 --- a/desktop/modules/sonix/SonixPage.qml +++ b/desktop/modules/sonix/SonixPage.qml @@ -51,6 +51,6 @@ Column { } Component { id: deviceTab; SonixDevice { onGo: name => page.go(name) } } - Component { id: playlistsTab; Item {} } + Component { id: playlistsTab; SonixPlaylists { mod: page.mod } } Component { id: foldersTab; Item {} } } diff --git a/desktop/modules/sonix/SonixPlaylists.qml b/desktop/modules/sonix/SonixPlaylists.qml new file mode 100644 index 0000000..7a3cb1e --- /dev/null +++ b/desktop/modules/sonix/SonixPlaylists.qml @@ -0,0 +1,151 @@ +// Copyright (C) 2026 Danilo M. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2 as +// published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + + +import QtQuick +import "../.." +import "../appearance" +import "../sound" +import "Sonix.js" as S + +// Playlists: list, play, open, remove tracks, and new names that become +// playlists when Folders adds their first track. No rename, delete or +// reorder: SonixLink has none (README.md). +Column { + id: pl + required property var mod + property var lists: [] + property string open: "" + property var tracks: [] + property string error: "" + property var rev: undefined + spacing: 8 + + function glyph(cp) { return String.fromCodePoint(cp); } + function report(ok, d, err) { pl.error = ok ? "" : err; } + + function load() { + Sonix.get("/api/playlists", (ok, d, err) => { + pl.report(ok, d, err); + if (!ok) return; + pl.lists = d.playlists || []; + const names = pl.lists.map(l => l.name); + pl.mod.draftPlaylists = pl.mod.draftPlaylists.filter(n => names.indexOf(n) < 0); + }); + if (pl.open !== "") pl.loadTracks(); + } + function loadTracks() { + Sonix.get("/api/playlist?" + S.query({ name: pl.open }), (ok, d, err) => { + pl.report(ok, d, err); + if (ok) pl.tracks = d.tracks || []; + }); + } + + Component.onCompleted: load() + // Edits made on the player show up: the state carries a revision. + Connections { + target: Sonix + function onPlayerChanged() { + const r = Sonix.player.playlists_revision; + if (r !== undefined && r !== pl.rev) { pl.rev = r; pl.load(); } + } + } + + // --- the list --------------------------------------------------------- + + Row { + visible: pl.open === "" + spacing: 8 + Field { id: newName; width: 220; placeholder: "New playlist name" } + Button { + text: "New" + onClicked: { + const n = newName.value.trim(); + if (n === "" || pl.lists.some(l => l.name === n) || pl.mod.draftPlaylists.indexOf(n) >= 0) return; + pl.mod.draftPlaylists = pl.mod.draftPlaylists.concat([n]); + newName.value = ""; + } + } + } + + Repeater { + model: pl.open === "" ? pl.lists : [] + delegate: SonixRow { + required property var modelData + width: pl.width + text: modelData.name + sub: modelData.count + " tracks" + onClicked: { pl.open = modelData.name; pl.tracks = []; pl.loadTracks(); } + TransportButton { + glyph: pl.glyph(0xF040A) // md-play + onClicked: Sonix.command({ do: "play_all", list: "playlist", value: modelData.name }, null, pl.report) + } + } + } + + Repeater { + model: pl.open === "" ? pl.mod.draftPlaylists : [] + delegate: SonixRow { + required property var modelData + width: pl.width + text: modelData + sub: "empty: add its first track from Folders" + } + } + + // --- one playlist ----------------------------------------------------- + + Row { + visible: pl.open !== "" + spacing: 8 + Button { text: "Playlists"; icon: "\uF053"; onClicked: { pl.open = ""; pl.tracks = []; } } + Button { + text: "Play all" + onClicked: Sonix.command({ do: "play_all", list: "playlist", value: pl.open }, null, pl.report) + } + } + + Text { + visible: pl.open !== "" + text: pl.open + font { family: Theme.fontFamily; pixelSize: Theme.fontSize; bold: true } + color: Theme.text + } + + Repeater { + model: pl.open !== "" ? pl.tracks : [] + delegate: SonixRow { + required property var modelData + width: pl.width + text: modelData.title || modelData.path.split("/").pop() + sub: modelData.artist || "" + TransportButton { + glyph: pl.glyph(0xF040A) // md-play + onClicked: Sonix.command({ do: "play_path", path: modelData.path, list: "playlist", value: pl.open }, + null, pl.report) + } + TransportButton { + glyph: pl.glyph(0xF0156) // md-close + onClicked: Sonix.command({ do: "playlist_remove", value: pl.open }, modelData.path, + (ok, d, err) => { pl.report(ok, d, err); if (ok) pl.loadTracks(); }) + } + } + } + + Text { + visible: pl.error !== "" + width: pl.width + wrapMode: Text.Wrap + text: pl.error + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } + color: Theme.red + } +} -- cgit v1.2.3