mobile/divelist: rewrite the QML dive list

This isn't perfect yet, but it looks fairly reasonable.

This commit was mainly written by Dirk, but includes a few fixes from Berthold
which where squashed into this commit as they really should have been part of
the initial version.

Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
This commit is contained in:
Dirk Hohndel 2019-11-11 22:03:40 +01:00
parent 64ae6d54a7
commit b382445e59
2 changed files with 226 additions and 360 deletions

View file

@ -12,19 +12,8 @@ Kirigami.ScrollablePage {
objectName: "DiveList"
title: qsTr("Dive list")
verticalScrollBarPolicy: Qt.ScrollBarAlwaysOff
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 QtObject diveListModel: diveTripModel
opacity: 0
Behavior on opacity {
NumberAnimation {
duration: 200
easing.type: Easing.OutQuad
}
}
property QtObject diveListModel: mobileListModel
supportsRefreshing: true
onRefreshingChanged: {
@ -34,377 +23,253 @@ Kirigami.ScrollablePage {
manager.saveChangesCloud(true)
refreshing = false
} else {
manager.appendTextToLog("sync with cloud storage requested, but credential status is " + PrefCloudStorage.cloud_verification_status)
manager.appendTextToLog("sync with cloud storage requested, but credentialStatus is " + Backend.cloud_verification_status)
manager.appendTextToLog("no syncing, turn off spinner")
refreshing = false
}
}
}
onVisibleChanged: {
if (visible) {
page.actions.main = page.downloadFromDCAction
page.actions.right = page.addDiveAction
page.actions.left = page.filterToggleAction
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)
}
}
Component {
id: diveDelegate
id: diveOrTripDelegate
Kirigami.AbstractListItem {
// this allows us to access properties of the currentItem from outside
property variant myData: model
leftPadding: 0
topPadding: 0
id: innerListItem
enabled: true
id: diveOrTripDelegateItem
padding: 0
supportsMouseEvents: true
checked: model.selected
width: parent.width
height: (collapsed & 1) ? diveListEntry.height + Kirigami.Units.smallSpacing : 0
visible: collapsed & 1
backgroundColor: checked ? subsurfaceTheme.primaryColor : subsurfaceTheme.backgroundColor
activeBackgroundColor: subsurfaceTheme.primaryColor
textColor: checked ? subsurfaceTheme.primaryTextColor : subsurfaceTheme.textColor
checked: !isTrip && current
anchors {
left: parent.left
right: parent.right
}
height: (isTrip ? 9 : 11) * Kirigami.Units.smallSpacing // delegateInnerItem.height
states: [
State {
name: "isHidden";
when: (collapsed & 1) == 0
PropertyChanges {
target: innerListItem
height: 0
visible: false
}
},
State {
name: "isVisible";
when: (collapsed & 1) == 1
PropertyChanges {
target: innerListItem
height: diveListEntry.height + Kirigami.Units.smallSpacing
visible: true
}
}
]
// When clicked, the mode changes to details view
// When clicked, a trip expands / unexpands, a dive is opened in DiveDetails
onClicked: {
if (detailsWindow.state === "view") {
//diveListView.currentIndex = index
detailsWindow.showDiveIndex(id);
// switch to detailsWindow (or push it if it's not in the stack)
var i = rootItem.pageIndex(detailsWindow)
if (i === -1)
pageStack.push(detailsWindow)
else
pageStack.currentIndex = i
if (isTrip) {
manager.appendTextToLog("clicked on trip " + tripTitle)
// toggle expand (backend to deal with unexpand other trip)
manager.toggle(model.row);
} else {
manager.appendTextToLog("clicked on dive")
if (detailsWindow.state === "view") {
detailsWindow.showDiveIndex(id); // we need access to dive->id
// switch to detailsWindow (or push it if it's not in the stack)
var i = rootItem.pageIndex(detailsWindow)
if (i === -1)
pageStack.push(detailsWindow)
else
pageStack.currentIndex = i
}
}
}
property bool deleteButtonVisible: false
property bool copyButtonVisible: false
property bool pasteButtonVisible: false
onPressAndHold: {
deleteButtonVisible = true
copyButtonVisible = true
pasteButtonVisible = true
timer.restart()
}
// first we look at the trip
Item {
id: delegateInnerItem
width: page.width
height: childrenRect.height
Rectangle {
id: leftBarDive
width: tripId == "" ? 0 : Kirigami.Units.smallSpacing
height: diveListEntry.height * 0.8
id: headingBackground
height: visible ? 8 * Kirigami.Units.smallSpacing : 0
anchors {
topMargin: Kirigami.Units.smallSpacing / 2
left: parent.left
right: parent.right
}
color: subsurfaceTheme.lightPrimaryColor
visible: isTrip
Rectangle {
id: dateBox
height: 1.8 * Kirigami.Units.gridUnit
width: 2.2 * 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 {
visible: headingBackground.visible
text: visible ? tripShortDate : ""
color: subsurfaceTheme.primaryTextColor
font.pointSize: subsurfaceTheme.smallPointSize * 0.8
lineHeightMode: Text.FixedHeight
lineHeight: Kirigami.Units.gridUnit *.8
horizontalAlignment: Text.AlignHCenter
height: contentHeight
anchors {
horizontalCenter: parent.horizontalCenter
verticalCenter: parent.verticalCenter
}
}
}
Controls.Label {
text: visible ? tripTitle : ""
elide: Text.ElideRight
visible: headingBackground.visible
font.weight: Font.Medium
font.pointSize: subsurfaceTheme.regularPointSize
anchors {
verticalCenter: parent.verticalCenter
left: dateBox.right
leftMargin: horizontalPadding * 2
right: parent.right
}
color: subsurfaceTheme.lightPrimaryTextColor
}
}
Rectangle {
id: headingBottomLine
height: visible ? Kirigami.Units.smallSpacing : 0
visible: headingBackground.visible
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 * 3 : 1)
height: Math.ceil(childrenRect.height + Kirigami.Units.smallSpacing)
anchors.left: leftBarDive.right
Controls.Label {
id: locationText
text: (undefined !== location && "" != location) ? location : qsTr("<unnamed dive site>")
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: (undefined !== dateTime) ? dateTime : ""
width: Math.max(locationText.width * 0.45, paintedWidth) // helps vertical alignment throughout listview
font.pointSize: subsurfaceTheme.smallPointSize
color: innerListItem.checked ? subsurfaceTheme.darkerPrimaryTextColor : secondaryTextColor
}
// spacer, just in case
Controls.Label {
text: " "
width: Kirigami.Units.largeSpacing
}
// let's try to show the depth / duration very compact
Controls.Label {
text: (undefined !== depthDuration) ? depthDuration : ""
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: "#" + 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 {
id: copyButton
visible: copyButtonVisible
height: diveListEntry.height - 2 * Kirigami.Units.smallSpacing
width: height
color: subsurfaceTheme.lightDrawerColor
antialiasing: true
radius: Kirigami.Units.smallSpacing
anchors {
left: diveListEntry.right
verticalCenter: diveListEntry.verticalCenter
verticalCenterOffset: Kirigami.Units.smallSpacing / 2
rightMargin: horizontalPadding * 2
leftMargin: horizontalPadding * 2
}
Kirigami.Icon {
anchors {
horizontalCenter: parent.horizontalCenter
verticalCenter: parent.verticalCenter
}
source: ":/icons/edit-copy"
width: parent.height
height: width
}
MouseArea {
anchors.fill: parent
enabled: parent.visible
onClicked: {
deleteButtonVisible = false
copyButtonVisible = false
pasteButtonVisible = false
timer.stop()
manager.copyDiveData(id)
}
onPressAndHold: {
globalDrawer.close()
manager.copyDiveData(id)
pageStack.push(settingsCopyWindow)
}
}
}
Rectangle {
id: pasteButton
visible: pasteButtonVisible
height: diveListEntry.height - 2 * Kirigami.Units.smallSpacing
width: height
color: subsurfaceTheme.lightDrawerColor
antialiasing: true
radius: Kirigami.Units.smallSpacing
anchors {
left: copyButton.right
verticalCenter: diveListEntry.verticalCenter
verticalCenterOffset: Kirigami.Units.smallSpacing / 2
rightMargin: horizontalPadding * 2
leftMargin: horizontalPadding * 2
}
Kirigami.Icon {
anchors {
horizontalCenter: parent.horizontalCenter
verticalCenter: parent.verticalCenter
}
source: ":/icons/edit-paste"
width: parent.height
height: width
}
MouseArea {
anchors.fill: parent
enabled: parent.visible
onClicked: {
deleteButtonVisible = false
copyButtonVisible = false
pasteButtonVisible = false
timer.stop()
manager.pasteDiveData(id)
}
}
}
Rectangle {
id: deleteButton
visible: deleteButtonVisible
height: diveListEntry.height - 2 * Kirigami.Units.smallSpacing
width: height
color: subsurfaceTheme.contrastAccentColor
antialiasing: true
radius: Kirigami.Units.smallSpacing
anchors {
left: pasteButton.right
right: parent.right
verticalCenter: diveListEntry.verticalCenter
verticalCenterOffset: Kirigami.Units.smallSpacing / 2
rightMargin: horizontalPadding * 2
leftMargin: horizontalPadding * 2
top: headingBackground.bottom
}
Kirigami.Icon {
anchors {
horizontalCenter: parent.horizontalCenter
verticalCenter: parent.verticalCenter
}
source: ":/icons/trash-empty"
width: parent.height
height: width
color: "#B2B2B2"
}
Rectangle {
id: diveBackground
height: visible ? diveListEntry.height + Kirigami.Units.smallSpacing : 0
anchors {
left: parent.left
right: parent.right
}
MouseArea {
color: subsurfaceTheme.backgroundColor
visible: !isTrip
Item {
anchors.fill: parent
enabled: parent.visible
onClicked: {
deleteButtonVisible = false
copyButtonVisible = false
pasteButtonVisible = false
timer.stop()
manager.deleteDive(id)
Rectangle {
id: leftBarDive
width: Kirigami.Units.smallSpacing
height: isTopLevel ? 0 : 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
height: visible ? 10 * Kirigami.Units.smallSpacing : 0
anchors {
right: parent.right
left: leftBarDive.right
verticalCenter: parent.verticalCenter
}
Controls.Label {
id: locationText
text: (undefined !== location && "" != location) ? location : qsTr("<unnamed dive site>")
font.weight: Font.Medium
font.pointSize: subsurfaceTheme.smallPointSize
elide: Text.ElideRight
maximumLineCount: 1 // needed for elide to work at all
color: subsurfaceTheme.textColor
anchors {
left: parent.left
leftMargin: horizontalPadding * 2
topMargin: Kirigami.Units.smallSpacing / 2
top: parent.top
right: parent.right
}
}
Row {
anchors {
left: locationText.left
top: locationText.bottom
topMargin: Kirigami.Units.smallSpacing / 2
}
Controls.Label {
id: dateLabel
text: (undefined !== dateTime) ? dateTime : ""
width: Math.max(locationText.width * 0.45, paintedWidth) // helps vertical alignment throughout listview
font.pointSize: subsurfaceTheme.smallPointSize
color: diveOrTripDelegateItem.checked ? subsurfaceTheme.darkerPrimaryTextColor : subsurfaceTheme.secondaryTextColor
}
// spacer, just in case
Controls.Label {
text: " "
width: Kirigami.Units.largeSpacing
}
// let's try to show the depth / duration very compact
Controls.Label {
text: (undefined !== depthDuration) ? depthDuration : ""
width: Math.max(Kirigami.Units.gridUnit * 3, paintedWidth) // helps vertical alignment throughout listview
font.pointSize: subsurfaceTheme.smallPointSize
color: diveOrTripDelegateItem.checked ? subsurfaceTheme.darkerPrimaryTextColor : subsurfaceTheme.secondaryTextColor
}
}
Controls.Label {
id: numberText
text: "#" + number
font.pointSize: subsurfaceTheme.smallPointSize
color: diveOrTripDelegateItem.checked ? subsurfaceTheme.darkerPrimaryTextColor : subsurfaceTheme.secondaryTextColor
anchors {
right: parent.right
rightMargin: Kirigami.Units.smallSpacing
top: locationText.bottom
topMargin: Kirigami.Units.smallSpacing / 2
}
}
}
}
}
Timer {
id: timer
interval: 4000
onTriggered: {
deleteButtonVisible = false
copyButtonVisible = false
pasteButtonVisible = 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 : parent.height - Kirigami.Units.smallSpacing
width: section == "" ? 0 : 2.5 * Kirigami.Units.gridUnit * PrefDisplay.mobile_scale
color: subsurfaceTheme.primaryColor
radius: Kirigami.Units.smallSpacing * 2
antialiasing: true
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
leftMargin: Kirigami.Units.smallSpacing
}
Controls.Label {
text: {
diveListModel ? diveListModel.tripShortDate(section) : "no data model"
}
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 (diveListModel) {
if (diveListModel.activeTrip() === section)
diveListModel.setActiveTrip("")
else
diveListModel.setActiveTrip(section)
}
}
}
Controls.Label {
id: sectionText
text: {
diveListModel ? diveListModel.tripTitle(section) : "no data model"
}
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
}
}
Rectangle {
height: section == "" ? 0 : 1
width: parent.width
anchors.top: headingBackground.bottom
color: "#B2B2B2"
StartPage {
id: startPage
anchors.fill: parent
opacity: (Backend.cloud_verification_status === Enums.CS_NOCLOUD ||
Backend.cloud_verification_status === Enums.CS_VERIFIED) ? 0 : 1
visible: opacity > 0
Behavior on opacity { NumberAnimation { duration: Kirigami.Units.shortDuration } }
function setupActions() {
if (Backend.cloud_verification_status === Enums.CS_VERIFIED ||
Backend.cloud_verification_status === Enums.CS_NOCLOUD) {
page.actions.main = page.downloadFromDCAction
page.actions.right = page.addDiveAction
page.actions.left = page.filterToggleAction
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.actions.left = null
page.title = qsTr("Cloud credentials")
}
}
onVisibleChanged: {
setupActions();
}
Component.onCompleted: {
manager.finishSetup();
setupActions();
}
}
Controls.Label {
anchors.fill: parent
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text: diveListModel ? qsTr("No dives in dive list") : qsTr("Please wait, filtering dive list")
text: diveListModel ? qsTr("No dives in dive list") : qsTr("Please wait, updating the dive list")
visible: diveListView.visible && diveListView.count === 0
}
Component {
id: filterHeader
Rectangle {
@ -434,11 +299,8 @@ Kirigami.ScrollablePage {
]
transitions: [
Transition {
NumberAnimation { property: "height"; duration: 400; easing.type: Easing.InOutQuad }
}
Transition { NumberAnimation { property: "height"; duration: 400; easing.type: Easing.InOutQuad }}
]
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: Kirigami.Units.gridUnit / 2
@ -489,22 +351,19 @@ Kirigami.ScrollablePage {
ListView {
id: diveListView
anchors.fill: parent
opacity: 1.0 - startPage.opacity
visible: opacity > 0
model: diveListModel
currentIndex: -1
delegate: diveDelegate
delegate: diveOrTripDelegate
header: filterHeader
headerPositioning: ListView.OverlayHeader
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: "tripId"
section.criteria: ViewSection.FullString
section.delegate: tripHeading
section.labelPositioning: ViewSection.CurrentLabelAtStart | ViewSection.InlineLabels
Connections {
target: detailsWindow
onCurrentIndexChanged: diveListView.currentIndex = detailsWindow.currentIndex
Component.onCompleted: {
manager.appendTextToLog("finished setting up the diveListView")
}
}
@ -541,11 +400,18 @@ Kirigami.ScrollablePage {
}
onBackRequested: {
if (Qt.platform.os != "ios")
manager.quit()
// let's make sure Kirigami doesn't quit on our behalf
event.accepted = true
if (startPage.visible && diveListView.count > 0 &&
Backend.cloud_verification_status !== Enums.CS_INCORRECT_USER_PASSWD) {
Backend.cloud_verification_status = 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) {

View file

@ -55,7 +55,7 @@ Kirigami.ApplicationWindow {
}
function showBusy(msg) {
if (msg !== undefined)
if (msg !== undefined && msg !== "")
showPassiveNotification(msg, 15000) // show for 15 seconds
busy.running = true
}
@ -282,7 +282,7 @@ Kirigami.ApplicationWindow {
pageStack.pop()
}
diveList.diveListModel = diveModel
pageStack.push(diveList)
showDiveList()
hideBusy()
}
}