import QtQuick 2.0 import Sailfish.Silica 1.0 Page { id: page property var bookData: ({}) property string coverSource: "" property bool downloading: false property string downloadError: "" property bool readingNow: false property string readingUrl: "" SilicaFlickable { anchors.fill: parent contentHeight: column.height + Theme.paddingLarge Column { id: column width: parent.width PageHeader { title: bookData.title } Image { id: cover anchors.horizontalCenter: parent.horizontalCenter width: Math.min(parent.width * 0.45, 260) height: width * 1.5 fillMode: Image.PreserveAspectFit source: page.coverSource visible: source.length > 0 } Button { anchors.horizontalCenter: parent.horizontalCenter text: "Leggi (EPUB)" visible: page.hasEpubFormat() enabled: !page.downloading onClicked: startReading() } Label { x: Theme.horizontalPageMargin width: parent.width - 2 * Theme.horizontalPageMargin text: bookData.authors color: Theme.secondaryColor wrapMode: Text.Wrap visible: text.length > 0 } Label { x: Theme.horizontalPageMargin width: parent.width - 2 * Theme.horizontalPageMargin text: bookData.summary wrapMode: Text.Wrap font.pixelSize: Theme.fontSizeSmall visible: text.length > 0 } SectionHeader { text: "Formati" } Repeater { model: bookData.formats delegate: ListItem { id: formatItem contentHeight: Theme.itemSizeMedium enabled: !page.downloading Label { anchors.left: parent.left anchors.leftMargin: Theme.horizontalPageMargin anchors.verticalCenter: parent.verticalCenter text: modelData.format + (modelData.size > 0 ? " (" + formatSize(modelData.size) + ")" : "") color: formatItem.highlighted ? Theme.highlightColor : Theme.primaryColor } onClicked: startDownload(modelData) } } Label { x: Theme.horizontalPageMargin width: parent.width - 2 * Theme.horizontalPageMargin text: page.downloadError color: Theme.errorColor wrapMode: Text.Wrap visible: text.length > 0 } Item { width: 1; height: Theme.paddingLarge } } } Column { anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom visible: page.downloading height: Theme.itemSizeMedium ProgressBar { id: progressBar anchors.left: parent.left anchors.right: parent.right value: 0 minimumValue: 0 maximumValue: 1 } Label { anchors.horizontalCenter: parent.horizontalCenter text: "Download in corso…" color: Theme.secondaryColor font.pixelSize: Theme.fontSizeSmall } } // banner di conferma download (Notices non è istanziabile in questa Silica) property string downloadNotice: "" Label { x: Theme.horizontalPageMargin width: parent.width - 2 * Theme.horizontalPageMargin text: page.downloadNotice color: Theme.highlightColor wrapMode: Text.Wrap visible: text.length > 0 } Timer { id: hideNoticeTimer interval: 4000 onTriggered: page.downloadNotice = "" } function formatSize(bytes) { if (bytes <= 0) return "" if (bytes < 1048576) return Math.round(bytes / 1024) + " KB" return (bytes / 1048576).toFixed(1) + " MB" } function resolveUrl(u) { if (u.indexOf("http://") === 0 || u.indexOf("https://") === 0) return u var base = appSettings.baseUrl // il server dietro sottopath (es. /calibre) emette link che lo includono già: // evita di raddoppiarlo (solo operazioni su stringhe, niente regex) var schemeEnd = base.indexOf("://") var slash = base.indexOf("/", schemeEnd + 3) var basePath = slash >= 0 ? base.substring(slash) : "" if (basePath.length > 1 && u.indexOf(basePath) === 0) return base + u.substring(basePath.length) return base + (u.charAt(0) === "/" ? u : "/" + u) } function sanitizeFileName(s) { var out = "" for (var i = 0; i < s.length; i++) { var c = s.charAt(i) if (c === '/' || c === ':' || c === '*' || c === '?' || c === '"' || c === '<' || c === '>' || c === '|' || c.charCodeAt(0) === 92) out += '_' else out += c } return out.trim() } function hasEpubFormat() { var fmts = bookData.formats if (!fmts) return false for (var i = 0; i < fmts.length; i++) if (String(fmts[i].format).toLowerCase() === "epub") return true return false } function firstEpubFormat() { var fmts = bookData.formats if (!fmts) return null for (var i = 0; i < fmts.length; i++) if (String(fmts[i].format).toLowerCase() === "epub") return fmts[i] return null } // estrae l'id numerico calibre da un url di download: /opds/download/// function extractBookId(u) { var parts = u.split("/") for (var i = 0; i < parts.length - 1; i++) if (parts[i] === "download" && parts[i + 1].length > 0 && !isNaN(parseInt(parts[i + 1]))) return parseInt(parts[i + 1]) return 0 } function startReading() { var f = page.firstEpubFormat() if (!f) return var id = page.extractBookId(f.url) if (id <= 0) return page.readingNow = true page.readingUrl = f.url page.downloading = true page.downloadError = "" downloader.download(resolveUrl(f.url), appSettings.bookFilePath(id, "epub")) } function startDownload(format) { if (page.downloading) return var ext = format.format.toLowerCase() var dest = appSettings.downloadDir + "/" + sanitizeFileName(bookData.title) + "." + ext page.downloading = true page.downloadError = "" progressBar.value = 0 progressBar.maximumValue = format.size > 0 ? format.size : 1 downloader.download(resolveUrl(format.url), dest) } Component.onCompleted: { if (bookData.coverUrl && bookData.coverUrl.length > 0) apiClient.fetchImage(bookData.coverUrl, "detailcover") } Connections { target: apiClient // nota: parametro rinominato (imageId) — "id" è parola chiave QML e rompe il confronto onImageReady: function(imageId, localPath) { if (imageId === "detailcover") page.coverSource = localPath } } Connections { target: downloader onProgress: { if (!page.downloading) return progressBar.value = received if (total > 0) progressBar.maximumValue = total } onFinished: { page.downloading = false if (ok) { if (page.readingNow) { page.readingNow = false var id = page.extractBookId(page.readingUrl) pageStack.push(Qt.resolvedUrl("ReaderPage.qml"), { epubPath: path, bookId: id, bookFormat: "epub" }) return } page.downloadError = "" page.downloadNotice = "Salvato in " + path hideNoticeTimer.restart() } else { page.downloadNotice = "" page.readingNow = false page.downloadError = "Errore: " + error } } } }