harbour-books/app/qml/BooksStorageView.qml

335 lines
12 KiB
QML
Raw Normal View History

2015-06-28 14:22:35 +03:00
/*
Copyright (C) 2015-2021 Jolla Ltd.
Copyright (C) 2015-2021 Slava Monich <slava.monich@jolla.com>
2015-06-28 14:22:35 +03:00
You may use this file under the terms of BSD license as follows:
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
3. Neither the names of the copyright holders nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
2015-06-28 14:22:35 +03:00
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.
*/
import QtQuick 2.0
import Sailfish.Silica 1.0
import harbour.books 1.0
Item {
2015-06-28 14:22:35 +03:00
id: storageView
property alias viewInteractive: storageList.interactive
property alias viewScale: storageList.scale
property alias pullDownMenu: menu
property alias isCurrentView: menu.visible
property bool pageActive
2015-06-28 14:22:35 +03:00
property bool editMode: false
signal openBook(var book)
property real _cellWidth
property real _cellHeight: Math.ceil(_cellWidth*8/5)
2015-06-28 14:22:35 +03:00
property var draggedItem
property var currentShelf
property var currentShelfView
property int currentShelfIndex: storageListWatcher.currentIndex
readonly property bool dragInProgress: draggedItem ? true : false
readonly property real maxContentY: currentShelfView ? Math.max(0, currentShelfView.contentHeight - currentShelfView.height) -
(currentShelfView.headerItem ? currentShelfView.headerItem.height : 0) : 0
2015-06-28 14:22:35 +03:00
readonly property real verticalScrollThreshold: _cellHeight/2
readonly property real horizontalScrollThreshold: _cellWidth/2
readonly property real _minGridCellWidth: 10*Theme.paddingMedium
2016-10-07 17:41:59 +03:00
property var _settingsComponent
2015-06-28 14:22:35 +03:00
// Books in the library shouldn't be too small or too big.
// At least 3 (or 5 in landscape) should fit in the horizontal direction.
// The width shouldn't be smaller than 10*Theme.paddingMedium or 0.88 inch
function calculateCellWidth2(viewWidth, minCount) {
2015-06-28 14:22:35 +03:00
var result = 0
if (viewWidth > 0) {
2015-06-28 14:22:35 +03:00
// At least 3 books in portrait, 5 in landscape
var n = minCount + 1
var cellSize = viewWidth/minCount
2015-06-28 14:22:35 +03:00
while (cellSize > _minGridCellWidth && (cellSize/PointsPerInch) > 0.88 && n < 11) {
cellSize = viewWidth/(++n)
2015-06-28 14:22:35 +03:00
}
result = Math.floor(viewWidth/(n-1))
2015-06-28 14:22:35 +03:00
}
return result
}
function calculateCellWidth() {
// At least 3 books in portrait, 5 in landscape
2015-11-30 18:57:27 +03:00
var result2 = calculateCellWidth2(Math.min(window.width, window.height), 3)
var result1 = calculateCellWidth2(Math.max(window.width, window.height), 5)
var result = Math.min(result1, result2)
return result
}
Component.onCompleted: _cellWidth = calculateCellWidth()
onCurrentShelfChanged: {
if (storageList.completed && currentShelf) {
Settings.currentFolder = currentShelf.path
}
}
2015-06-28 14:22:35 +03:00
PullDownMenu {
id: menu
2015-06-28 14:22:35 +03:00
MenuItem {
2016-10-07 17:41:59 +03:00
//: Pulley menu item
//% "Settings"
text: qsTrId("harbour-books-menu-settings")
2016-10-07 17:41:59 +03:00
visible: !editMode && BooksSettingsMenu
onClicked: {
if (!_settingsComponent) {
_settingsComponent = Qt.createComponent("../settings/BooksSettings.qml")
if (_settingsComponent.status !== Component.Ready) {
2016-10-07 17:41:59 +03:00
console.log(_settingsComponent.errorString())
}
}
2016-10-07 19:40:18 +03:00
pageStack.push(_settingsComponent, {
"title" : text,
"allowedOrientations": window.allowedOrientations,
"inApp": true
2016-10-07 19:40:18 +03:00
})
2016-10-07 17:41:59 +03:00
}
}
MenuItem {
//: Pulley menu item
2015-06-28 14:22:35 +03:00
//% "Scan downloads"
text: qsTrId("harbour-books-storage-menu-scan_downloads")
2015-06-28 14:22:35 +03:00
visible: !editMode
onClicked: pageStack.push(importComponent)
}
MenuItem {
2016-10-07 17:41:59 +03:00
//: Pulley menu item
2015-06-28 14:22:35 +03:00
//% "Delete all books"
text: qsTrId("harbour-books-storage-menu-delete_everything")
2015-06-28 14:22:35 +03:00
visible: editMode
enabled: currentShelf && (currentShelf.count > 0)
onClicked: storageModel.setDeleteAllRequest(storageListWatcher.currentIndex, true)
}
MenuLabel {
//: Number of books in the storage header
//% "%0 book(s)"
text: qsTrId("harbour-books-storage-book_count",bookCount).arg(bookCount)
visible: !bookCountVisible && bookCount > 0
readonly property int bookCount: storageList.currentItem ? storageList.currentItem.bookCount : 0
readonly property bool bookCountVisible: storageList.currentItem && storageList.currentItem.bookCountVisible
}
2015-06-28 14:22:35 +03:00
}
onEditModeChanged: {
storageModel.cancelDeleteAllRequests()
dragScrollAnimation.stop()
}
Component {
id: importComponent
2015-06-28 14:22:35 +03:00
BooksImport {
destination: currentShelf ? currentShelf.path : ""
onAccepted: {
var count = selectedCount
for (var i=0; i<count; i++) {
currentShelf.importBook(selectedBook(i))
}
}
}
}
Connections {
target: Qt.application
onActiveChanged: if (!Qt.application.active) editMode = false
2015-06-28 14:22:35 +03:00
}
BookStorage {
id: storageModel
2015-06-28 14:22:35 +03:00
// Show the contents of SD-card and let use know that he can switch
// between the internal memory and the removable storage by swiping
// the list horizontally
onNewStorage: {
if (pageActive && storageView.visible) {
console.log("showing SD card contents")
storageList.scrollToPage(index)
}
}
2015-06-28 14:22:35 +03:00
}
ListWatcher {
id: storageListWatcher
2015-06-28 14:22:35 +03:00
listView: storageList
onSizeChanged: _cellWidth = calculateCellWidth()
2015-06-28 14:22:35 +03:00
}
SilicaListView {
id: storageList
anchors.centerIn: parent
width: parent.width
height: parent.height
2015-06-28 14:22:35 +03:00
model: storageModel
flickDeceleration: maximumFlickVelocity
orientation: ListView.Horizontal
snapMode: ListView.SnapOneItem
preferredHighlightBegin: 0
preferredHighlightEnd: width + spacing
highlightRangeMode: ListView.StrictlyEnforceRange
2015-06-28 14:22:35 +03:00
spacing: Theme.paddingMedium
interactive: !dragInProgress && !dragScrollAnimation.running
property bool completed
2015-06-28 14:22:35 +03:00
readonly property real maxContentX: Math.max(0, contentWidth - width)
onMaxContentXChanged: {
// SD-card can be removed while scroll animation is still active
if (dragScrollAnimation.running && dragScrollAnimation.to > maxContentX) {
dragScrollAnimation.to = maxContentX
dragScrollAnimation.restart()
}
}
Component.onCompleted: {
var index = model.deviceIndex(Settings.currentStorage)
// positionViewAtIndex doesn't work here, update contentX directly
if (index >= 0) {
contentX = (width + spacing) * index
} else {
// Most likely, removable storage is gone
console.log(Settings.currentFolder, "is gone")
Settings.currentFolder = currentShelf ? currentShelf.path : ""
}
completed = true
}
2015-06-28 14:22:35 +03:00
delegate: BooksShelfView {
width: storageList.width
height: storageList.height
cellWidth: storageView._cellWidth
cellHeight: storageView._cellHeight
singleStorage: storageModel.count < 2
editMode: storageView.editMode
deleteAllRequest: model.deleteAllRequest
device: model.device
removableStorage: model.removable
shelfIndex: model.index
view.clip: storageList.scale == 1
2015-06-28 14:22:35 +03:00
onStartEditing: storageView.editMode = true
onStopEditing: storageView.editMode = false
onScrollLeft: storageList.scrollOnePageLeft()
onScrollRight: storageList.scrollOnePageRight()
onCancelDeleteAll: storageModel.cancelDeleteAllRequests()
onDropItem: storageView.dropItem()
property bool current: model.index === currentShelfIndex
Component.onCompleted: updateCurrentShelf()
onCurrentChanged: updateCurrentShelf()
function updateCurrentShelf() {
if (current) {
storageView.currentShelf = shelf
storageView.currentShelfView = view
} else {
// no need for dummy item anymore
shelf.hasDummyItem = false
}
}
onOpenBook: storageView.openBook(book)
}
function scrollOnePageLeft() {
if (contentX > 0) {
dragScrollAnimation.from = contentX
dragScrollAnimation.to = Math.max(0, contentX - width - spacing)
dragScrollAnimation.start()
}
}
function scrollOnePageRight() {
if (contentX < maxContentX) {
dragScrollAnimation.from = contentX
dragScrollAnimation.to = Math.min(maxContentX, contentX + width + spacing)
dragScrollAnimation.start()
}
}
function scrollToPage(index) {
if (currentIndex !== index) {
dragScrollAnimation.from = contentX
dragScrollAnimation.to = contentX + (width + spacing) * (index - currentIndex)
dragScrollAnimation.start()
}
2015-06-28 14:22:35 +03:00
}
}
function dropItem() {
if (draggedItem && dragItem.shelfIndex !== currentShelfIndex && currentShelf) {
var targetIndex = currentShelf.dummyItemIndex
if (targetIndex >= 0 && currentShelf.drop(draggedItem)) {
console.log("drop accepted")
// Update coordinates of the drag item to make it move toward the drop target
var cellsPerRow = Math.floor(currentShelfView.width/_cellWidth)
var delta = currentShelfView.mapToItem(storageView,0,0)
var x = _cellWidth * (targetIndex % cellsPerRow) - currentShelfView.contentX
var y = _cellHeight * Math.floor(targetIndex / cellsPerRow) - currentShelfView.contentY
dragItem.x = x + delta.x
dragItem.y = y + delta.y
// This hides the target item until the grag item makes it to the destination:
dragItem.dropShelfIndex = currentShelfIndex
dragItem.dropItemIndex = targetIndex
}
}
}
BooksShelfItem {
id: dragItem
2015-06-28 14:22:35 +03:00
visible: false
width: storageView._cellWidth
height: storageView._cellHeight
pressed: false
editMode: false
scaledDown: false
scaleAnimationEnabled: false
synchronous: true
book: draggedItem ? draggedItem.book : null
name: draggedItem ? draggedItem.name : ""
property int shelfIndex: -1
property int dropShelfIndex: -1
property int dropItemIndex: -1
}
NumberAnimation {
id: dragScrollAnimation
2015-06-28 14:22:35 +03:00
target: storageList
property: "contentX"
duration: 500
easing.type: Easing.InOutQuad
}
}