Files
harbour-calibreweb/qml/pages/ReaderPage.qml
T

300 lines
9.5 KiB
QML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import QtQuick 2.0
import Sailfish.Silica 1.0
import Sailfish.WebView 1.0
import harbour.calibreweb 1.0
Page {
id: page
property string epubPath: ""
property int bookId: 0
property string bookFormat: "epub"
property int currentChapter: 0
property double chapterFraction: 0.0
property bool ready: false
property bool restoring: false
property int saveCounter: 0
property int syncCounter: 0
property string errorMessage: ""
// reflow: scala il font (il testo si ri-avvolge), non lo zoom grafico
property double fontScale: 1.0
allowedOrientations: Orientation.All
EpubBook {
id: book
}
SilicaFlickable {
anchors.fill: parent
contentHeight: parent.height
// il PullDownMenu va DENTRO una vista Silica, non a livello di Page
PullDownMenu {
MenuItem {
text: "Indice capitoli"
onClicked: showChapterList()
}
MenuItem {
text: "Aggiorna posizione sul server"
onClicked: syncNow()
}
MenuItem {
text: "Margini del testo"
onClicked: {
var dlg = pageStack.push(Qt.resolvedUrl("MarginDialog.qml"),
{ marginValue: appSettings.readerMargin })
dlg.accepted.connect(function() {
if (page.ready && book.chapterCount() > 0)
loadChapter()
})
}
}
MenuItem {
text: "Impostazioni"
onClicked: pageStack.push(Qt.resolvedUrl("SettingsPage.qml"))
}
}
// controlli del lettore: un solo trascinamento in alto li apre
PushUpMenu {
id: readerMenu
Label {
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width - 2 * Theme.horizontalPageMargin
text: (book.chapterCount > 0)
? (page.currentChapter + 1) + "/" + book.chapterCount
+ " · " + book.chapterTitle(page.currentChapter)
+ " · " + Math.round(page.overallProgress() * 100) + "%"
: "…"
truncationMode: TruncationMode.Elide
color: Theme.primaryColor
horizontalAlignment: Text.AlignHCenter
font.pixelSize: Theme.fontSizeSmall
}
Row {
anchors.horizontalCenter: parent.horizontalCenter
spacing: Theme.paddingSmall
IconButton {
icon.source: "image://theme/icon-m-left"
enabled: page.currentChapter > 0
onClicked: gotoChapter(page.currentChapter - 1)
}
Button {
text: "A"
width: Theme.itemSizeSmall
height: Theme.itemSizeMedium
onClicked: page.changeFontScale(-0.15)
}
Button {
text: "A+"
width: Theme.itemSizeSmall
height: Theme.itemSizeMedium
onClicked: page.changeFontScale(0.15)
}
IconButton {
icon.source: "image://theme/icon-m-right"
enabled: page.currentChapter < book.chapterCount - 1
onClicked: gotoChapter(page.currentChapter + 1)
}
}
ProgressBar {
width: parent.width
value: page.overallProgress()
minimumValue: 0
maximumValue: 1
}
}
WebView {
id: web
anchors.fill: parent
active: true
onLoadedChanged: {
if (!loaded)
return
loadWatchdog.stop()
page.ready = true
// ripristina la posizione in frazione sull'altezza ricalcolata
if (page.chapterFraction > 0.01) {
web.runJavaScript(
"window.scrollTo(0," + page.chapterFraction
+ "*Math.max(1,document.documentElement.scrollHeight-window.innerHeight));true",
function() { })
}
page.restoring = false
}
}
}
BusyIndicator {
anchors.centerIn: parent
running: !page.ready
size: BusyIndicatorSize.Large
}
// watchdog: se il capitolo non si carica, niente spinner infinito
Timer {
id: loadWatchdog
interval: 15000
onTriggered: {
if (!web.loaded && !page.ready) {
page.errorMessage = "Il lettore non risponde (WebView). Il file EPUB potrebbe essere danneggiato."
page.ready = true
}
}
}
// ritardo di apertura: evita la race tra onCompleted e l'init del WebView
Timer {
id: openTimer
interval: 100
onTriggered: openChapter()
}
Label {
anchors.centerIn: parent
text: page.errorMessage
color: Theme.errorColor
visible: page.errorMessage.length > 0
}
// ---- controlli spostati nel PushUpMenu (trascina in alto) ----
// ---- timer di tracciamento ----
Timer {
id: trackTimer
interval: 2000
running: page.ready
repeat: true
onTriggered: readScrollPosition()
}
function overallProgress() {
if (book.chapterCount <= 0)
return 0
return (page.currentChapter + page.chapterFraction) / book.chapterCount
}
function readScrollPosition() {
if (!web.loaded)
return
web.runJavaScript(
"window.scrollY / Math.max(1, document.documentElement.scrollHeight - window.innerHeight)",
function(f) {
var v = Math.max(0, Math.min(1, f))
if (Math.abs(v - page.chapterFraction) > 0.005) {
page.chapterFraction = v
maybeSave(true)
}
})
}
function maybeSave(force) {
if (bookId <= 0)
return
page.saveCounter++
// salva locale ogni ~10 s, sync server ogni ~30 s
if (force || page.saveCounter % 5 === 0) {
appSettings.setBookmarkKey(page.bookId, page.bookFormat,
book.makeBookmarkKey(page.currentChapter, page.chapterFraction))
}
if (force || page.syncCounter++ % 15 === 0)
syncNow()
}
function syncNow() {
if (bookId <= 0)
return
apiClient.postBookmark(page.bookId, page.bookFormat,
book.makeBookmarkKey(page.currentChapter, page.chapterFraction))
}
function gotoChapter(index) {
if (index < 0 || index >= book.chapterCount)
return
if (index !== page.currentChapter) {
maybeSave(true)
page.chapterFraction = 0
}
page.currentChapter = index
loadChapter()
}
function changeFontScale(delta) {
page.fontScale = Math.max(0.7, Math.min(2.5, page.fontScale + delta))
// ricarica il capitolo con la nuova dimensione: il reflow avviene al load
loadChapter()
}
function fontPx() {
return Math.round(16 * page.fontScale * 100) / 100
}
// carica il capitolo corrente via loadHtml: lo stile di reflow (font-size
// e margini) è iniettato nell'HTML dal parser, quindi niente JS post-load
function loadChapter() {
var html = book.chapterHtml(page.currentChapter, page.fontPx(),
appSettings.readerMargin)
if (html.length === 0) {
page.errorMessage = "Capitolo non leggibile"
page.ready = true
return
}
page.ready = false
page.restoring = true
loadWatchdog.restart()
web.loadHtml(html, book.chapterBaseUrl(page.currentChapter))
}
function showChapterList() {
var dialog = pageStack.push(Qt.resolvedUrl("ChapterListDialog.qml"),
{ model: book.chapterList() })
dialog.accepted.connect(function() {
gotoChapter(dialog.selectedIndex)
})
}
Component.onCompleted: {
page.ready = false
if (!book.open(page.epubPath)) {
page.errorMessage = "Impossibile aprire il libro EPUB"
page.ready = true
return
}
openTimer.start()
}
function openChapter() {
// ripristina il segno locale
var key = appSettings.bookmarkKey(page.bookId, page.bookFormat)
var ch = 0
var fr = 0.0
if (key.length > 0) {
var r = book.parseBookmarkKey(key)
if (r && r.hasOwnProperty("chapter")) {
ch = Math.max(0, Math.min(r.chapter, book.chapterCount - 1))
fr = r.fraction
}
}
page.currentChapter = ch
page.chapterFraction = fr
loadChapter()
}
onStatusChanged: {
if (status === PageStatus.Inactive) {
maybeSave(true)
} else if (status === PageStatus.Active) {
// riapplica font/margini se cambiati nelle impostazioni:
// ricarica il capitolo e ripristina la posizione
if (page.ready && book.chapterCount() > 0)
loadChapter()
}
}
}