mirror of
https://github.com/subsurface/subsurface.git
synced 2024-11-28 13:10:19 +00:00
6850e6d567
Changing the scale, it seems that the header of trips is not rescaled. The reason for this is simple. That string does not use our manipulated font but a different one. In fact, this is the only ocurrence on the divelist that did not scale. However, other screens hardly rescaled at all. All these will be fixed in seperate commits. Signed-off-by: Jan Mulder <jlmulder@xs4all.nl>
468 lines
14 KiB
QML
468 lines
14 KiB
QML
// SPDX-License-Identifier: GPL-2.0
|
|
import QtQuick 2.6
|
|
import QtQuick.Controls 2.2 as Controls
|
|
import QtQuick.Layouts 1.2
|
|
import QtQuick.Window 2.2
|
|
import QtQuick.Dialogs 1.2
|
|
import org.kde.kirigami 2.2 as Kirigami
|
|
import org.subsurfacedivelog.mobile 1.0
|
|
|
|
Kirigami.ScrollablePage {
|
|
id: page
|
|
objectName: "DiveList"
|
|
title: qsTr("Dive list")
|
|
verticalScrollBarPolicy: Qt.ScrollBarAlwaysOff
|
|
width: subsurfaceTheme.columnWidth
|
|
property int credentialStatus: prefs.credentialStatus
|
|
property int numDives: diveListView.count
|
|
property color textColor: subsurfaceTheme.textColor
|
|
property color secondaryTextColor: subsurfaceTheme.secondaryTextColor
|
|
property int horizontalPadding: Kirigami.Units.gridUnit / 2 - Kirigami.Units.smallSpacing + 1
|
|
property string activeTrip
|
|
|
|
supportsRefreshing: true
|
|
onRefreshingChanged: {
|
|
if (refreshing) {
|
|
if (prefs.credentialStatus === CloudStatus.CS_VERIFIED) {
|
|
console.log("User pulled down dive list - syncing with cloud storage")
|
|
detailsWindow.endEditMode()
|
|
manager.saveChangesCloud(true)
|
|
console.log("done syncing, turn off spinner")
|
|
refreshing = false
|
|
} else {
|
|
console.log("sync with cloud storage requested, but credentialStatus is " + prefs.credentialStatus)
|
|
console.log("no syncing, turn off spinner")
|
|
refreshing = false
|
|
}
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: diveDelegate
|
|
Kirigami.AbstractListItem {
|
|
// this looks weird, but it's how we can tell that this dive isn't in a trip
|
|
property bool diveOutsideTrip: dive.tripNrDives === 0
|
|
leftPadding: 0
|
|
topPadding: 0
|
|
id: innerListItem
|
|
enabled: true
|
|
supportsMouseEvents: true
|
|
checked: diveListView.currentIndex === model.index
|
|
width: parent.width
|
|
height: diveOutsideTrip ? diveListEntry.height + Kirigami.Units.smallSpacing : 0
|
|
visible: diveOutsideTrip
|
|
backgroundColor: checked ? subsurfaceTheme.primaryColor : subsurfaceTheme.backgroundColor
|
|
activeBackgroundColor: subsurfaceTheme.primaryColor
|
|
textColor: checked ? subsurfaceTheme.primaryTextColor : subsurfaceTheme.textColor
|
|
|
|
states: [
|
|
State {
|
|
name: "isHidden";
|
|
when: dive.tripMeta !== activeTrip && ! diveOutsideTrip
|
|
PropertyChanges {
|
|
target: innerListItem
|
|
height: 0
|
|
visible: false
|
|
}
|
|
},
|
|
State {
|
|
name: "isVisible";
|
|
when: dive.tripMeta === activeTrip || diveOutsideTrip
|
|
PropertyChanges {
|
|
target: innerListItem
|
|
height: diveListEntry.height + Kirigami.Units.smallSpacing
|
|
visible: true
|
|
}
|
|
}
|
|
]
|
|
transitions: [
|
|
Transition {
|
|
from: "isHidden"
|
|
to: "isVisible"
|
|
SequentialAnimation {
|
|
NumberAnimation {
|
|
property: "visible"
|
|
duration: 1
|
|
}
|
|
NumberAnimation {
|
|
property: "height"
|
|
duration: 200 + 20 * dive.tripNrDives
|
|
easing.type: Easing.InOutQuad
|
|
}
|
|
}
|
|
},
|
|
Transition {
|
|
from: "isVisible"
|
|
to: "isHidden"
|
|
SequentialAnimation {
|
|
NumberAnimation {
|
|
property: "height"
|
|
duration: 200 + 20 * dive.tripNrDives
|
|
easing.type: Easing.InOutQuad
|
|
}
|
|
NumberAnimation {
|
|
property: "visible"
|
|
duration: 1
|
|
}
|
|
}
|
|
}
|
|
]
|
|
|
|
// When clicked, the mode changes to details view
|
|
onClicked: {
|
|
if (detailsWindow.state === "view") {
|
|
diveListView.currentIndex = index
|
|
detailsWindow.showDiveIndex(index);
|
|
stackView.push(detailsWindow);
|
|
}
|
|
}
|
|
|
|
property bool deleteButtonVisible: false
|
|
|
|
onPressAndHold: {
|
|
deleteButtonVisible = true
|
|
timer.restart()
|
|
}
|
|
Item {
|
|
Rectangle {
|
|
id: leftBarDive
|
|
width: dive.tripMeta == "" ? 0 : Kirigami.Units.smallSpacing
|
|
height: diveListEntry.height * 0.8
|
|
color: subsurfaceTheme.lightPrimaryColor
|
|
anchors {
|
|
left: parent.left
|
|
top: parent.top
|
|
leftMargin: Kirigami.Units.smallSpacing
|
|
topMargin: Kirigami.Units.smallSpacing * 2
|
|
bottomMargin: Kirigami.Units.smallSpacing * 2
|
|
}
|
|
}
|
|
Item {
|
|
id: diveListEntry
|
|
width: parent.width - Kirigami.Units.gridUnit * (innerListItem.deleteButtonVisible ? 3 : 1)
|
|
height: Math.ceil(childrenRect.height + Kirigami.Units.smallSpacing)
|
|
anchors.left: leftBarDive.right
|
|
Controls.Label {
|
|
id: locationText
|
|
text: dive.location
|
|
font.weight: Font.Bold
|
|
font.pointSize: subsurfaceTheme.regularPointSize
|
|
elide: Text.ElideRight
|
|
maximumLineCount: 1 // needed for elide to work at all
|
|
color: textColor
|
|
anchors {
|
|
left: parent.left
|
|
leftMargin: horizontalPadding * 2
|
|
topMargin: Kirigami.Units.smallSpacing
|
|
top: parent.top
|
|
right: parent.right
|
|
}
|
|
}
|
|
Row {
|
|
anchors {
|
|
left: locationText.left
|
|
top: locationText.bottom
|
|
topMargin: Kirigami.Units.smallSpacing
|
|
bottom: numberText.bottom
|
|
}
|
|
|
|
Controls.Label {
|
|
id: dateLabel
|
|
text: dive.date + " " + dive.time
|
|
width: Math.max(locationText.width * 0.45, paintedWidth) // helps vertical alignment throughout listview
|
|
font.pointSize: subsurfaceTheme.smallPointSize
|
|
color: innerListItem.checked ? subsurfaceTheme.darkerPrimaryTextColor : secondaryTextColor
|
|
}
|
|
// let's try to show the depth / duration very compact
|
|
Controls.Label {
|
|
text: dive.depth + ' / ' + dive.duration
|
|
width: Math.max(Kirigami.Units.gridUnit * 3, paintedWidth) // helps vertical alignment throughout listview
|
|
font.pointSize: subsurfaceTheme.smallPointSize
|
|
color: innerListItem.checked ? subsurfaceTheme.darkerPrimaryTextColor : secondaryTextColor
|
|
}
|
|
}
|
|
Controls.Label {
|
|
id: numberText
|
|
text: "#" + dive.number
|
|
font.pointSize: subsurfaceTheme.smallPointSize
|
|
color: innerListItem.checked ? subsurfaceTheme.darkerPrimaryTextColor : secondaryTextColor
|
|
anchors {
|
|
right: parent.right
|
|
rightMargin: horizontalPadding
|
|
top: locationText.bottom
|
|
topMargin: Kirigami.Units.smallSpacing
|
|
}
|
|
}
|
|
}
|
|
Rectangle {
|
|
visible: deleteButtonVisible
|
|
height: diveListEntry.height - Kirigami.Units.smallSpacing
|
|
width: height - 3 * Kirigami.Units.smallSpacing
|
|
color: subsurfaceTheme.contrastAccentColor
|
|
antialiasing: true
|
|
radius: Kirigami.Units.smallSpacing
|
|
anchors {
|
|
left: diveListEntry.right
|
|
right: parent.right
|
|
}
|
|
Kirigami.Icon {
|
|
anchors {
|
|
horizontalCenter: parent.horizontalCenter
|
|
verticalCenter: parent.verticalCenter
|
|
}
|
|
source: ":/icons/trash-empty"
|
|
width: Kirigami.Units.iconSizes.smallMedium
|
|
height: width
|
|
}
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
enabled: parent.visible
|
|
onClicked: {
|
|
deleteButtonVisible = false
|
|
timer.stop()
|
|
manager.deleteDive(dive.id)
|
|
}
|
|
}
|
|
}
|
|
Item {
|
|
Timer {
|
|
id: timer
|
|
interval: 4000
|
|
onTriggered: {
|
|
deleteButtonVisible = false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: tripHeading
|
|
Item {
|
|
width: page.width
|
|
height: childrenRect.height
|
|
Rectangle {
|
|
id: headingBackground
|
|
height: section == "" ? 0 : sectionText.height + Kirigami.Units.gridUnit
|
|
anchors {
|
|
left: parent.left
|
|
right: parent.right
|
|
}
|
|
color: subsurfaceTheme.lightPrimaryColor
|
|
visible: section != ""
|
|
Rectangle {
|
|
id: dateBox
|
|
visible: section != ""
|
|
height: section == "" ? 0 : 2 * Kirigami.Units.gridUnit
|
|
width: section == "" ? 0 : 2.5 * Kirigami.Units.gridUnit
|
|
color: subsurfaceTheme.primaryColor
|
|
radius: Kirigami.Units.smallSpacing * 2
|
|
antialiasing: true
|
|
anchors {
|
|
verticalCenter: parent.verticalCenter
|
|
left: parent.left
|
|
leftMargin: Kirigami.Units.smallSpacing
|
|
}
|
|
Controls.Label {
|
|
text: { section.replace(/.*\+\+/, "").replace(/::.*/, "").replace("@", "\n'") }
|
|
color: subsurfaceTheme.primaryTextColor
|
|
font.pointSize: subsurfaceTheme.smallPointSize
|
|
lineHeightMode: Text.FixedHeight
|
|
lineHeight: Kirigami.Units.gridUnit *.9
|
|
horizontalAlignment: Text.AlignHCenter
|
|
anchors {
|
|
horizontalCenter: parent.horizontalCenter
|
|
verticalCenter: parent.verticalCenter
|
|
}
|
|
}
|
|
}
|
|
MouseArea {
|
|
anchors.fill: headingBackground
|
|
onClicked: {
|
|
if (activeTrip === section)
|
|
activeTrip = ""
|
|
else
|
|
activeTrip = section
|
|
}
|
|
}
|
|
Controls.Label {
|
|
id: sectionText
|
|
text: {
|
|
// if the tripMeta (which we get as "section") ends in ::-- we know
|
|
// that there's no trip -- otherwise strip the meta information before
|
|
// the :: and show the trip location
|
|
var shownText
|
|
var endsWithDoubleDash = /::--$/;
|
|
if (endsWithDoubleDash.test(section) || section === "--") {
|
|
shownText = ""
|
|
} else {
|
|
shownText = section.replace(/.*::/, "")
|
|
}
|
|
shownText
|
|
}
|
|
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
|
visible: text !== ""
|
|
font.weight: Font.Bold
|
|
font.pointSize: subsurfaceTheme.regularPointSize
|
|
anchors {
|
|
top: parent.top
|
|
left: dateBox.right
|
|
topMargin: Math.max(2, Kirigami.Units.gridUnit / 2)
|
|
leftMargin: horizontalPadding * 2
|
|
right: parent.right
|
|
}
|
|
color: subsurfaceTheme.lightPrimaryTextColor
|
|
}
|
|
MouseArea {
|
|
anchors.fill: sectionText
|
|
onClicked: {
|
|
if (activeTrip === section)
|
|
activeTrip = ""
|
|
else
|
|
activeTrip = section
|
|
}
|
|
}
|
|
}
|
|
Rectangle {
|
|
height: Math.max(2, Kirigami.Units.gridUnit / 12) // we want a thicker line
|
|
anchors {
|
|
bottom: headingBackground.top
|
|
left: parent.left
|
|
rightMargin: Kirigami.Units.gridUnit * -2
|
|
right: parent.right
|
|
}
|
|
color: subsurfaceTheme.lightPrimaryColor
|
|
}
|
|
}
|
|
}
|
|
|
|
StartPage {
|
|
id: startPage
|
|
anchors.fill: parent
|
|
opacity: credentialStatus === CloudStatus.CS_NOCLOUD ||
|
|
(credentialStatus === CloudStatus.CS_VERIFIED) ? 0 : 1
|
|
visible: opacity > 0
|
|
Behavior on opacity { NumberAnimation { duration: Kirigami.Units.shortDuration } }
|
|
function setupActions() {
|
|
if (visible) {
|
|
page.actions.main = null
|
|
page.actions.right = null
|
|
page.title = qsTr("Cloud credentials")
|
|
} else if (prefs.credentialStatus === CloudStatus.CS_VERIFIED ||
|
|
prefs.credentialStatus === CloudStatus.CS_NOCLOUD) {
|
|
page.actions.main = page.downloadFromDCAction
|
|
page.actions.right = page.addDiveAction
|
|
page.title = qsTr("Dive list")
|
|
if (diveListView.count === 0)
|
|
showPassiveNotification(qsTr("Please tap the '+' button to add a dive (or download dives from a supported dive computer)"), 3000)
|
|
} else {
|
|
page.actions.main = null
|
|
page.actions.right = null
|
|
page.title = qsTr("Dive list")
|
|
}
|
|
}
|
|
onVisibleChanged: {
|
|
setupActions();
|
|
}
|
|
|
|
Component.onCompleted: {
|
|
manager.finishSetup();
|
|
setupActions();
|
|
}
|
|
}
|
|
|
|
Text {
|
|
// make sure this gets pushed far enough down so that it's not obscured by the page title
|
|
// it would be nicer to use Controls.Label, but due to a QML bug that isn't possible with a
|
|
// weird "component versioning" error
|
|
// using this property means that we require Qt 5.6 / QtQuick2.6
|
|
topPadding: Kirigami.Units.iconSizes.large
|
|
leftPadding: Kirigami.Units.iconSizes.large
|
|
|
|
text: qsTr("No dives in dive list")
|
|
visible: diveListView.visible && diveListView.count === 0
|
|
}
|
|
|
|
ListView {
|
|
id: diveListView
|
|
anchors.fill: parent
|
|
opacity: 1.0 - startPage.opacity
|
|
visible: opacity > 0
|
|
model: diveModel
|
|
currentIndex: -1
|
|
delegate: diveDelegate
|
|
boundsBehavior: Flickable.DragOverBounds
|
|
maximumFlickVelocity: parent.height * 5
|
|
bottomMargin: Kirigami.Units.iconSizes.medium + Kirigami.Units.gridUnit
|
|
cacheBuffer: 40 // this will increase memory use, but should help with scrolling
|
|
section.property: "dive.tripMeta"
|
|
section.criteria: ViewSection.FullString
|
|
section.delegate: tripHeading
|
|
section.labelPositioning: ViewSection.CurrentLabelAtStart | ViewSection.InlineLabels
|
|
Connections {
|
|
target: detailsWindow
|
|
onCurrentIndexChanged: diveListView.currentIndex = detailsWindow.currentIndex
|
|
}
|
|
}
|
|
|
|
function showDownloadPage(vendor, product, connection) {
|
|
downloadFromDc.dcImportModel.clearTable()
|
|
stackView.push(downloadFromDc)
|
|
if (vendor !== undefined && product !== undefined && connection !== undefined) {
|
|
/* set up the correct values on the download page */
|
|
if (vendor !== -1)
|
|
downloadFromDc.vendor = vendor
|
|
if (product !== -1)
|
|
downloadFromDc.product = product
|
|
if (connection !== -1)
|
|
downloadFromDc.connection = connection
|
|
}
|
|
}
|
|
|
|
property QtObject downloadFromDCAction: Kirigami.Action {
|
|
icon {
|
|
name: ":/icons/downloadDC"
|
|
color: subsurfaceTheme.primaryColor
|
|
}
|
|
onTriggered: {
|
|
showDownloadPage()
|
|
}
|
|
}
|
|
|
|
property QtObject addDiveAction: Kirigami.Action {
|
|
icon {
|
|
name: ":/icons/list-add"
|
|
}
|
|
onTriggered: {
|
|
startAddDive()
|
|
}
|
|
}
|
|
|
|
onBackRequested: {
|
|
if (startPage.visible && diveListView.count > 0 &&
|
|
prefs.credentialStatus !== CloudStatus.CS_INCORRECT_USER_PASSWD) {
|
|
prefs.credentialStatus = oldStatus
|
|
event.accepted = true;
|
|
}
|
|
if (!startPage.visible) {
|
|
if (Qt.platform.os != "ios") {
|
|
manager.quit()
|
|
}
|
|
// let's make sure Kirigami doesn't quit on our behalf
|
|
event.accepted = true
|
|
}
|
|
}
|
|
|
|
function setCurrentDiveListIndex(idx, noScroll) {
|
|
diveListView.currentIndex = idx
|
|
// updating the index of the ListView triggers a non-linear scroll
|
|
// animation that can be very slow. the fix is to stop this animation
|
|
// by setting contentY to itself and then using positionViewAtIndex().
|
|
// the downside is that the view jumps to the index immediately.
|
|
if (noScroll) {
|
|
diveListView.contentY = diveListView.contentY
|
|
diveListView.positionViewAtIndex(idx, ListView.Center)
|
|
}
|
|
}
|
|
}
|