Move subsurface-core to core and qt-mobile to mobile-widgets
Having subsurface-core as a directory name really messes with autocomplete and is obviously redundant. Simmilarly, qt-mobile caused an autocomplete conflict and also was inconsistent with the desktop-widget name for the directory containing the "other" UI. And while cleaning up the resulting change in the path name for include files, I decided to clean up those even more to make them consistent overall. This could have been handled in more commits, but since this requires a make clean before the build, it seemed more sensible to do it all in one. Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
59
mobile-widgets/qml/About.qml
Normal file
|
@ -0,0 +1,59 @@
|
|||
import QtQuick 2.3
|
||||
import QtQuick.Controls 1.2
|
||||
import QtQuick.Layouts 1.1
|
||||
import org.kde.kirigami 1.0 as Kirigami
|
||||
import org.subsurfacedivelog.mobile 1.0
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
id: aboutPage
|
||||
property int pageWidth: subsurfaceTheme.columnWidth - Kirigami.Units.smallSpacing
|
||||
title: "About Subsurface-mobile"
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
width: aboutPage.width
|
||||
Layout.margins: Kirigami.Units.gridUnit / 2
|
||||
|
||||
|
||||
Kirigami.Heading {
|
||||
text: "About Subsurface-mobile"
|
||||
Layout.topMargin: Kirigami.Units.gridUnit
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.maximumWidth: pageWidth
|
||||
wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere
|
||||
}
|
||||
Image {
|
||||
id: image
|
||||
source: "qrc:/qml/subsurface-mobile-icon.png"
|
||||
width: pageWidth / 2
|
||||
height: width
|
||||
fillMode: Image.Stretch
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
horizontalAlignment: Image.AlignHCenter
|
||||
}
|
||||
|
||||
Kirigami.Heading {
|
||||
text: "A mobile version of the free Subsurface divelog software.\n" +
|
||||
"View your dive logs while on the go."
|
||||
level: 4
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing * 3
|
||||
Layout.maximumWidth: pageWidth
|
||||
wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere
|
||||
anchors.horizontalCenter: parent.Center
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
Kirigami.Heading {
|
||||
text: "Version: " + manager.getVersion() + "\n\n© Subsurface developer team\n2011-2016"
|
||||
level: 5
|
||||
font.pointSize: subsurfaceTheme.smallPointSize + 1
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
Layout.maximumWidth: pageWidth
|
||||
wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere
|
||||
anchors.horizontalCenter: parent.Center
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
84
mobile-widgets/qml/CloudCredentials.qml
Normal file
|
@ -0,0 +1,84 @@
|
|||
import QtQuick 2.3
|
||||
import QtQuick.Controls 1.2
|
||||
import QtQuick.Window 2.2
|
||||
import QtQuick.Dialogs 1.2
|
||||
import QtQuick.Layouts 1.1
|
||||
import org.kde.kirigami 1.0 as Kirigami
|
||||
import org.subsurfacedivelog.mobile 1.0
|
||||
|
||||
Item {
|
||||
id: loginWindow
|
||||
height: outerLayout.height + 2 * Kirigami.Units.gridUnit
|
||||
|
||||
property string username: login.text;
|
||||
property string password: password.text;
|
||||
|
||||
function saveCredentials() {
|
||||
manager.cloudUserName = login.text
|
||||
manager.cloudPassword = password.text
|
||||
manager.saveCloudCredentials()
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: outerLayout
|
||||
width: subsurfaceTheme.columnWidth - 2 * Kirigami.Units.gridUnit
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible && manager.accessingCloud < 0) {
|
||||
manager.appendTextToLog("Credential scrn: show kbd was: " + (Qt.inputMethod.isVisible ? "visible" : "invisible"))
|
||||
Qt.inputMethod.show()
|
||||
login.forceActiveFocus()
|
||||
} else {
|
||||
manager.appendTextToLog("Credential scrn: hide kbd was: " + (Qt.inputMethod.isVisible ? "visible" : "invisible"))
|
||||
Qt.inputMethod.hide()
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Heading {
|
||||
text: "Cloud credentials"
|
||||
level: headingLevel
|
||||
Layout.bottomMargin: Kirigami.Units.largeSpacing / 2
|
||||
}
|
||||
|
||||
Kirigami.Label {
|
||||
text: "Email"
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: login
|
||||
text: manager.cloudUserName
|
||||
Layout.fillWidth: true
|
||||
inputMethodHints: Qt.ImhEmailCharactersOnly |
|
||||
Qt.ImhNoAutoUppercase
|
||||
}
|
||||
|
||||
Kirigami.Label {
|
||||
text: "Password"
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: password
|
||||
text: manager.cloudPassword
|
||||
echoMode: TextInput.Password
|
||||
inputMethodHints: Qt.ImhSensitiveData |
|
||||
Qt.ImhHiddenText |
|
||||
Qt.ImhNoAutoUppercase
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
GridLayout {
|
||||
columns: 2
|
||||
|
||||
CheckBox {
|
||||
checked: false
|
||||
id: showPassword
|
||||
onCheckedChanged: {
|
||||
password.echoMode = checked ? TextInput.Normal : TextInput.Password
|
||||
}
|
||||
}
|
||||
Kirigami.Label {
|
||||
text: "Show password"
|
||||
}
|
||||
}
|
||||
Item { width: Kirigami.Units.gridUnit; height: width }
|
||||
}
|
||||
}
|
216
mobile-widgets/qml/DiveDetails.qml
Normal file
|
@ -0,0 +1,216 @@
|
|||
import QtQuick 2.4
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import QtQuick.Dialogs 1.2
|
||||
import QtQuick.Layouts 1.2
|
||||
import org.subsurfacedivelog.mobile 1.0
|
||||
import org.kde.kirigami 1.0 as Kirigami
|
||||
|
||||
Kirigami.Page {
|
||||
id: diveDetailsPage
|
||||
property alias currentIndex: diveDetailsListView.currentIndex
|
||||
property alias dive_id: detailsEdit.dive_id
|
||||
property alias number: detailsEdit.number
|
||||
property alias date: detailsEdit.dateText
|
||||
property alias airtemp: detailsEdit.airtempText
|
||||
property alias watertemp: detailsEdit.watertempText
|
||||
property alias buddy: detailsEdit.buddyText
|
||||
property alias divemaster: detailsEdit.divemasterText
|
||||
property alias depth: detailsEdit.depthText
|
||||
property alias duration: detailsEdit.durationText
|
||||
property alias location: detailsEdit.locationText
|
||||
property alias notes: detailsEdit.notesText
|
||||
property alias suit: detailsEdit.suitText
|
||||
property alias weight: detailsEdit.weightText
|
||||
property alias startpressure: detailsEdit.startpressureText
|
||||
property alias endpressure: detailsEdit.endpressureText
|
||||
property alias gasmix: detailsEdit.gasmixText
|
||||
|
||||
topPadding: applicationWindow().header.Layout.preferredHeight
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
bottomPadding: 0
|
||||
|
||||
title: diveDetailsListView.currentItem.modelData.dive.location
|
||||
state: "view"
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "view"
|
||||
PropertyChanges { target: diveDetailsPage; contextualActions: Qt.platform.os == "ios" ? [ deleteAction, backAction ] : [ deleteAction ] }
|
||||
PropertyChanges { target: detailsEditScroll; opened: false }
|
||||
},
|
||||
State {
|
||||
name: "edit"
|
||||
PropertyChanges { target: diveDetailsPage; contextualActions: Qt.platform.os == "ios" ? [ cancelAction ] : null }
|
||||
PropertyChanges { target: detailsEditScroll; opened: true }
|
||||
},
|
||||
State {
|
||||
name: "add"
|
||||
PropertyChanges { target: diveDetailsPage; contextualActions: Qt.platform.os == "ios" ? [ cancelAction ] : null }
|
||||
PropertyChanges { target: detailsEditScroll; opened: true }
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
property QtObject deleteAction: Action {
|
||||
text: "Delete dive"
|
||||
iconName: "trash-empty"
|
||||
onTriggered: {
|
||||
contextDrawer.close()
|
||||
var deletedId = diveDetailsListView.currentItem.modelData.dive.id
|
||||
manager.deleteDive(deletedId)
|
||||
stackView.pop()
|
||||
showPassiveNotification("Dive deleted", 3000, "Undo",
|
||||
function() {
|
||||
manager.undoDelete(deletedId)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
property QtObject cancelAction: Kirigami.Action {
|
||||
text: state === "edit" ? "Cancel edit" : "Cancel dive add"
|
||||
iconName: "dialog-cancel"
|
||||
onTriggered: {
|
||||
contextDrawer.close()
|
||||
if (state === "add")
|
||||
returnTopPage()
|
||||
else
|
||||
endEditMode()
|
||||
}
|
||||
}
|
||||
|
||||
property QtObject backAction: Action {
|
||||
text: "Back to dive list"
|
||||
iconName: "go-previous"
|
||||
onTriggered: {
|
||||
contextDrawer.close()
|
||||
returnTopPage()
|
||||
}
|
||||
}
|
||||
|
||||
mainAction: Action {
|
||||
iconName: state !== "view" ? "document-save" : "document-edit"
|
||||
onTriggered: {
|
||||
if (state === "edit" || state === "add") {
|
||||
detailsEdit.saveData()
|
||||
} else {
|
||||
startEditMode()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onBackRequested: {
|
||||
if (state === "edit") {
|
||||
endEditMode()
|
||||
event.accepted = true;
|
||||
} else if (state === "add") {
|
||||
endEditMode()
|
||||
stackView.pop()
|
||||
event.accepted = true;
|
||||
}
|
||||
// if we were in view mode, don't accept the event and pop the page
|
||||
}
|
||||
|
||||
function showDiveIndex(index) {
|
||||
currentIndex = index;
|
||||
diveDetailsListView.positionViewAtIndex(index, ListView.Beginning);
|
||||
}
|
||||
|
||||
function endEditMode() {
|
||||
// if we were adding a dive, we need to remove it
|
||||
if (state === "add")
|
||||
manager.addDiveAborted(dive_id)
|
||||
// just cancel the edit/add state
|
||||
state = "view";
|
||||
Qt.inputMethod.hide();
|
||||
}
|
||||
|
||||
function startEditMode() {
|
||||
// set things up for editing - so make sure that the detailsEdit has
|
||||
// all the right data (using the property aliases set up above)
|
||||
dive_id = diveDetailsListView.currentItem.modelData.dive.id
|
||||
number = diveDetailsListView.currentItem.modelData.dive.number
|
||||
date = diveDetailsListView.currentItem.modelData.dive.date + " " + diveDetailsListView.currentItem.modelData.dive.time
|
||||
location = diveDetailsListView.currentItem.modelData.dive.location
|
||||
duration = diveDetailsListView.currentItem.modelData.dive.duration
|
||||
depth = diveDetailsListView.currentItem.modelData.dive.depth
|
||||
airtemp = diveDetailsListView.currentItem.modelData.dive.airTemp
|
||||
watertemp = diveDetailsListView.currentItem.modelData.dive.waterTemp
|
||||
suit = diveDetailsListView.currentItem.modelData.dive.suit
|
||||
buddy = diveDetailsListView.currentItem.modelData.dive.buddy
|
||||
divemaster = diveDetailsListView.currentItem.modelData.dive.divemaster
|
||||
notes = diveDetailsListView.currentItem.modelData.dive.notes
|
||||
if (diveDetailsListView.currentItem.modelData.dive.singleWeight) {
|
||||
// we have only one weight, go ahead, have fun and edit it
|
||||
weight = diveDetailsListView.currentItem.modelData.dive.sumWeight
|
||||
} else {
|
||||
// careful when translating, this text is "magic" in DiveDetailsEdit.qml
|
||||
weight = "cannot edit multiple weight systems"
|
||||
}
|
||||
if (diveDetailsListView.currentItem.modelData.dive.getCylinder != "Multiple" ) {
|
||||
startpressure = diveDetailsListView.currentItem.modelData.dive.startPressure
|
||||
endpressure = diveDetailsListView.currentItem.modelData.dive.endPressure
|
||||
gasmix = diveDetailsListView.currentItem.modelData.dive.firstGas
|
||||
} else {
|
||||
// careful when translating, this text is "magic" in DiveDetailsEdit.qml
|
||||
startpressure = "cannot edit multiple cylinders"
|
||||
endpressure = "cannot edit multiple cylinders"
|
||||
gasmix = "cannot edit multiple gases"
|
||||
}
|
||||
|
||||
diveDetailsPage.state = "edit"
|
||||
}
|
||||
|
||||
onWidthChanged: diveDetailsListView.positionViewAtIndex(diveDetailsListView.currentIndex, ListView.Beginning);
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
ScrollView {
|
||||
id: diveDetailList
|
||||
anchors.fill: parent
|
||||
ListView {
|
||||
id: diveDetailsListView
|
||||
anchors.fill: parent
|
||||
model: diveModel
|
||||
currentIndex: -1
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
maximumFlickVelocity: parent.width * 5
|
||||
orientation: ListView.Horizontal
|
||||
focus: true
|
||||
clip: true
|
||||
snapMode: ListView.SnapOneItem
|
||||
onMovementEnded: {
|
||||
currentIndex = indexAt(contentX+1, 1);
|
||||
}
|
||||
delegate: ScrollView {
|
||||
id: internalScrollView
|
||||
width: diveDetailsListView.width
|
||||
height: diveDetailsListView.height
|
||||
property var modelData: model
|
||||
Flickable {
|
||||
//contentWidth: parent.width
|
||||
contentHeight: diveDetails.height
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
DiveDetailsView {
|
||||
id: diveDetails
|
||||
width: internalScrollView.width
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Kirigami.OverlaySheet {
|
||||
id: detailsEditScroll
|
||||
anchors.fill: parent
|
||||
onOpenedChanged: {
|
||||
if (!opened) {
|
||||
endEditMode()
|
||||
}
|
||||
}
|
||||
DiveDetailsEdit {
|
||||
id: detailsEdit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
236
mobile-widgets/qml/DiveDetailsEdit.qml
Normal file
|
@ -0,0 +1,236 @@
|
|||
import QtQuick 2.3
|
||||
import QtQuick.Controls 1.2
|
||||
import QtQuick.Controls.Styles 1.2
|
||||
import QtQuick.Dialogs 1.2
|
||||
import QtQuick.Layouts 1.1
|
||||
import org.subsurfacedivelog.mobile 1.0
|
||||
import org.kde.kirigami 1.0 as Kirigami
|
||||
|
||||
Item {
|
||||
id: detailsEdit
|
||||
property int dive_id
|
||||
property int number
|
||||
property alias dateText: txtDate.text
|
||||
property alias locationText: txtLocation.text
|
||||
property string gpsText
|
||||
property alias airtempText: txtAirTemp.text
|
||||
property alias watertempText: txtWaterTemp.text
|
||||
property alias suitText: txtSuit.text
|
||||
property alias buddyText: txtBuddy.text
|
||||
property alias divemasterText: txtDiveMaster.text
|
||||
property alias notesText: txtNotes.text
|
||||
property alias durationText: txtDuration.text
|
||||
property alias depthText: txtDepth.text
|
||||
property alias weightText: txtWeight.text
|
||||
property alias startpressureText: txtStartPressure.text
|
||||
property alias endpressureText: txtEndPressure.text
|
||||
property alias gasmixText: txtGasMix.text
|
||||
|
||||
function saveData() {
|
||||
// apply the changes to the dive_table
|
||||
manager.commitChanges(dive_id, detailsEdit.dateText, detailsEdit.locationText, detailsEdit.gpsText, detailsEdit.durationText,
|
||||
detailsEdit.depthText, detailsEdit.airtempText, detailsEdit.watertempText, detailsEdit.suitText,
|
||||
detailsEdit.buddyText, detailsEdit.divemasterText, detailsEdit.weightText, detailsEdit.notesText,
|
||||
detailsEdit.startpressureText, detailsEdit.endpressureText, detailsEdit.gasmixText)
|
||||
// trigger the profile to be redrawn
|
||||
QMLProfile.diveId = dive_id
|
||||
|
||||
// apply the changes to the dive detail view - since the edit could have changed the order
|
||||
// first make sure that we are looking at the correct dive - our model allows us to look
|
||||
// up the index based on the unique dive_id
|
||||
var newIdx = diveModel.getIdxForId(dive_id)
|
||||
diveDetailsListView.currentIndex = newIdx
|
||||
diveDetailsListView.currentItem.modelData.date = detailsEdit.dateText
|
||||
diveDetailsListView.currentItem.modelData.location = detailsEdit.locationText
|
||||
diveDetailsListView.currentItem.modelData.duration = detailsEdit.durationText
|
||||
diveDetailsListView.currentItem.modelData.depth = detailsEdit.depthText
|
||||
diveDetailsListView.currentItem.modelData.airtemp = detailsEdit.airtempText
|
||||
diveDetailsListView.currentItem.modelData.watertemp = detailsEdit.watertempText
|
||||
diveDetailsListView.currentItem.modelData.suit = detailsEdit.suitText
|
||||
diveDetailsListView.currentItem.modelData.buddy = detailsEdit.buddyText
|
||||
diveDetailsListView.currentItem.modelData.divemaster = detailsEdit.divemasterText
|
||||
diveDetailsListView.currentItem.modelData.notes = detailsEdit.notesText
|
||||
diveDetailsPage.state = "view"
|
||||
Qt.inputMethod.hide()
|
||||
// now make sure we directly show the saved dive (this may be a new dive, or it may have moved)
|
||||
showDiveIndex(newIdx)
|
||||
}
|
||||
|
||||
height: editArea.height
|
||||
ColumnLayout {
|
||||
id: editArea
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
width: subsurfaceTheme.columnWidth - 2 * Kirigami.Units.gridUnit
|
||||
|
||||
GridLayout {
|
||||
id: editorDetails
|
||||
width: parent.width
|
||||
columns: 2
|
||||
|
||||
Kirigami.Heading {
|
||||
Layout.columnSpan: 2
|
||||
text: "Dive " + number
|
||||
}
|
||||
Kirigami.Label {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: "Date:"
|
||||
}
|
||||
TextField {
|
||||
id: txtDate;
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Kirigami.Label {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: "Location:"
|
||||
}
|
||||
TextField {
|
||||
id: txtLocation;
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// we should add a checkbox here that allows the user
|
||||
// to add the current location as the dive location
|
||||
// (think of someone adding a dive while on the boat or
|
||||
// at the dive site)
|
||||
Kirigami.Label {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: "Use current\nGPS location:"
|
||||
}
|
||||
CheckBox {
|
||||
id: checkboxGPS
|
||||
onCheckedChanged: {
|
||||
if (checked)
|
||||
gpsText = manager.getCurrentPosition()
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Label {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: "Depth:"
|
||||
}
|
||||
TextField {
|
||||
id: txtDepth
|
||||
Layout.fillWidth: true
|
||||
validator: RegExpValidator { regExp: /[^-]*/ }
|
||||
}
|
||||
Kirigami.Label {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: "Duration:"
|
||||
}
|
||||
TextField {
|
||||
id: txtDuration
|
||||
Layout.fillWidth: true
|
||||
validator: RegExpValidator { regExp: /[^-]*/ }
|
||||
}
|
||||
|
||||
Kirigami.Label {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: "Air Temp:"
|
||||
}
|
||||
TextField {
|
||||
id: txtAirTemp
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Kirigami.Label {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: "Water Temp:"
|
||||
}
|
||||
TextField {
|
||||
id: txtWaterTemp
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Kirigami.Label {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: "Suit:"
|
||||
}
|
||||
TextField {
|
||||
id: txtSuit
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Kirigami.Label {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: "Buddy:"
|
||||
}
|
||||
TextField {
|
||||
id: txtBuddy
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Kirigami.Label {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: "Dive Master:"
|
||||
}
|
||||
TextField {
|
||||
id: txtDiveMaster
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Kirigami.Label {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: "Weight:"
|
||||
}
|
||||
TextField {
|
||||
id: txtWeight
|
||||
readOnly: (text == "cannot edit multiple weight systems" ? true : false)
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Kirigami.Label {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: "Gas mix:"
|
||||
}
|
||||
TextField {
|
||||
id: txtGasMix
|
||||
readOnly: (text == "cannot edit multiple gases" ? true : false)
|
||||
Layout.fillWidth: true
|
||||
validator: RegExpValidator { regExp: /(EAN100|EAN\d\d|AIR|100|\d{1,2}|\d{1,2}\/\d{1,2})/ }
|
||||
}
|
||||
|
||||
Kirigami.Label {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: "Start Pressure:"
|
||||
}
|
||||
TextField {
|
||||
id: txtStartPressure
|
||||
readOnly: (text == "cannot edit multiple cylinders" ? true : false)
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Kirigami.Label {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: "End Pressure:"
|
||||
}
|
||||
TextField {
|
||||
id: txtEndPressure
|
||||
readOnly: (text == "cannot edit multiple cylinders" ? true : false)
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
|
||||
Kirigami.Label {
|
||||
Layout.columnSpan: 2
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
text: "Notes:"
|
||||
}
|
||||
TextArea {
|
||||
Layout.columnSpan: 2
|
||||
width: parent.width
|
||||
id: txtNotes
|
||||
textFormat: TextEdit.RichText
|
||||
focus: true
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.minimumHeight: Kirigami.Units.gridUnit * 6
|
||||
selectByMouse: true
|
||||
wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere
|
||||
}
|
||||
}
|
||||
Item {
|
||||
height: Kirigami.Units.gridUnit * 3
|
||||
width: height // just to make sure the spacer doesn't produce scrollbars, but also isn't null
|
||||
}
|
||||
}
|
||||
}
|
303
mobile-widgets/qml/DiveDetailsView.qml
Normal file
|
@ -0,0 +1,303 @@
|
|||
import QtQuick 2.3
|
||||
/*
|
||||
import QtWebView 1.0
|
||||
*/
|
||||
import QtQuick.Controls 1.2
|
||||
import QtQuick.Controls.Styles 1.2
|
||||
import QtQuick.Dialogs 1.2
|
||||
import QtQuick.Layouts 1.1
|
||||
import org.subsurfacedivelog.mobile 1.0
|
||||
import org.kde.kirigami 1.0 as Kirigami
|
||||
|
||||
Item {
|
||||
id: detailsView
|
||||
property real gridWidth: subsurfaceTheme.columnWidth - 2 * Kirigami.Units.gridUnit
|
||||
property real col1Width: gridWidth * 0.23
|
||||
property real col2Width: gridWidth * 0.37
|
||||
property real col3Width: gridWidth * 0.20
|
||||
property real col4Width: gridWidth * 0.20
|
||||
|
||||
width: SubsurfaceTheme.columnWidth
|
||||
height: mainLayout.implicitHeight + bottomLayout.implicitHeight + Kirigami.Units.iconSizes.large
|
||||
Rectangle {
|
||||
z: 99
|
||||
color: Kirigami.Theme.textColor
|
||||
opacity: 0.3
|
||||
width: Kirigami.Units.smallSpacing/4
|
||||
anchors {
|
||||
right: parent.right
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
}
|
||||
}
|
||||
GridLayout {
|
||||
id: mainLayout
|
||||
anchors {
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
margins: Math.round(Kirigami.Units.gridUnit / 2)
|
||||
}
|
||||
columns: 4
|
||||
rowSpacing: Kirigami.Units.smallSpacing * 2
|
||||
columnSpacing: Kirigami.Units.smallSpacing
|
||||
|
||||
Kirigami.Heading {
|
||||
id: detailsViewHeading
|
||||
Layout.fillWidth: true
|
||||
text: dive.location
|
||||
font.underline: dive.gps !== ""
|
||||
Layout.columnSpan: 4
|
||||
wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
if (dive.gps !== "")
|
||||
showMap(dive.gps)
|
||||
}
|
||||
}
|
||||
}
|
||||
Kirigami.Label {
|
||||
id: dateLabel
|
||||
text: "Date: "
|
||||
opacity: 0.6
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
Kirigami.Label {
|
||||
text: dive.date + " " + dive.time
|
||||
wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere
|
||||
Layout.columnSpan: 2
|
||||
}
|
||||
Kirigami.Label {
|
||||
id: numberText
|
||||
text: "#" + dive.number
|
||||
color: Kirigami.Theme.textColor
|
||||
wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere
|
||||
}
|
||||
|
||||
Kirigami.Label {
|
||||
id: depthLabel
|
||||
text: "Depth: "
|
||||
opacity: 0.6
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
Kirigami.Label {
|
||||
text: dive.depth
|
||||
Layout.fillWidth: true
|
||||
wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere
|
||||
}
|
||||
Kirigami.Label {
|
||||
text: "Duration: "
|
||||
opacity: 0.6
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
Kirigami.Label {
|
||||
text: dive.duration
|
||||
wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere
|
||||
}
|
||||
|
||||
QMLProfile {
|
||||
id: qmlProfile
|
||||
visible: !dive.noDive
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Layout.minimumHeight
|
||||
Layout.minimumHeight: width * 0.75
|
||||
Layout.columnSpan: 4
|
||||
clip: false
|
||||
Rectangle {
|
||||
color: "transparent"
|
||||
opacity: 0.6
|
||||
border.width: 1
|
||||
border.color: Kirigami.Theme.textColor;
|
||||
anchors.fill: parent
|
||||
}
|
||||
}
|
||||
Kirigami.Label {
|
||||
id: noProfile
|
||||
visible: dive.noDive
|
||||
Layout.fillWidth: true
|
||||
Layout.columnSpan: 4
|
||||
Layout.margins: Kirigami.Units.gridUnit
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: "No profile to show"
|
||||
}
|
||||
}
|
||||
GridLayout {
|
||||
id: bottomLayout
|
||||
anchors {
|
||||
top: mainLayout.bottom
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
margins: Math.round(Kirigami.Units.gridUnit / 2)
|
||||
}
|
||||
columns: 4
|
||||
rowSpacing: Kirigami.Units.smallSpacing * 2
|
||||
columnSpacing: Kirigami.Units.smallSpacing
|
||||
|
||||
Kirigami.Heading {
|
||||
Layout.fillWidth: true
|
||||
level: 3
|
||||
text: "Dive Details"
|
||||
Layout.columnSpan: 4
|
||||
}
|
||||
|
||||
// first row - here we set up the column widths - total is 90% of width
|
||||
Kirigami.Label {
|
||||
text: "Suit:"
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
opacity: 0.6
|
||||
Layout.maximumWidth: detailsView.col1Width
|
||||
Layout.preferredWidth: detailsView.col1Width
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
Kirigami.Label {
|
||||
id: txtSuit
|
||||
text: dive.suit
|
||||
wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere
|
||||
Layout.maximumWidth: detailsView.col2Width
|
||||
Layout.preferredWidth: detailsView.col2Width
|
||||
}
|
||||
|
||||
Kirigami.Label {
|
||||
text: "Air Temp:"
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
opacity: 0.6
|
||||
Layout.maximumWidth: detailsView.col3Width
|
||||
Layout.preferredWidth: detailsView.col3Width
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
Kirigami.Label {
|
||||
id: txtAirTemp
|
||||
text: dive.airTemp
|
||||
wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere
|
||||
Layout.maximumWidth: detailsView.col4Width
|
||||
Layout.preferredWidth: detailsView.col4Width
|
||||
}
|
||||
|
||||
Kirigami.Label {
|
||||
text: "Cylinder:"
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
opacity: 0.6
|
||||
Layout.maximumWidth: detailsView.col1Width
|
||||
Layout.preferredWidth: detailsView.col1Width
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
Kirigami.Label {
|
||||
id: txtCylinder
|
||||
text: dive.getCylinder
|
||||
wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere
|
||||
Layout.maximumWidth: detailsView.col2Width
|
||||
Layout.preferredWidth: detailsView.col2Width
|
||||
}
|
||||
|
||||
Kirigami.Label {
|
||||
text: "Water Temp:"
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
opacity: 0.6
|
||||
Layout.maximumWidth: detailsView.col3Width
|
||||
Layout.preferredWidth: detailsView.col3Width
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
Kirigami.Label {
|
||||
id: txtWaterTemp
|
||||
text: dive.waterTemp
|
||||
wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere
|
||||
Layout.maximumWidth: detailsView.col4Width
|
||||
Layout.preferredWidth: detailsView.col4Width
|
||||
}
|
||||
|
||||
Kirigami.Label {
|
||||
text: "Dive Master:"
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
opacity: 0.6
|
||||
Layout.maximumWidth: detailsView.col1Width
|
||||
Layout.preferredWidth: detailsView.col1Width
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
Kirigami.Label {
|
||||
id: txtDiveMaster
|
||||
text: dive.divemaster
|
||||
wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere
|
||||
Layout.maximumWidth: detailsView.col2Width
|
||||
Layout.preferredWidth: detailsView.col2Width
|
||||
}
|
||||
|
||||
Kirigami.Label {
|
||||
text: "Weight:"
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
opacity: 0.6
|
||||
Layout.maximumWidth: detailsView.col3Width
|
||||
Layout.preferredWidth: detailsView.col3Width
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
Kirigami.Label {
|
||||
id: txtWeight
|
||||
text: dive.sumWeight
|
||||
wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere
|
||||
Layout.maximumWidth: detailsView.col4Width
|
||||
Layout.preferredWidth: detailsView.col4Width
|
||||
}
|
||||
|
||||
Kirigami.Label {
|
||||
text: "Buddy:"
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
opacity: 0.6
|
||||
Layout.maximumWidth: detailsView.col1Width
|
||||
Layout.preferredWidth: detailsView.col1Width
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
Kirigami.Label {
|
||||
id: txtBuddy
|
||||
text: dive.buddy
|
||||
wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere
|
||||
Layout.maximumWidth: detailsView.col2Width
|
||||
Layout.preferredWidth: detailsView.col2Width
|
||||
}
|
||||
|
||||
Kirigami.Label {
|
||||
text: "SAC:"
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
opacity: 0.6
|
||||
Layout.maximumWidth: detailsView.col3Width
|
||||
Layout.preferredWidth: detailsView.col3Width
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
Kirigami.Label {
|
||||
id: txtSAC
|
||||
text: dive.sac
|
||||
wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere
|
||||
Layout.maximumWidth: detailsView.col4Width
|
||||
Layout.preferredWidth: detailsView.col4Width
|
||||
}
|
||||
|
||||
Kirigami.Heading {
|
||||
Layout.fillWidth: true
|
||||
level: 3
|
||||
text: "Notes"
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
Layout.columnSpan: 4
|
||||
}
|
||||
|
||||
Kirigami.Label {
|
||||
id: txtNotes
|
||||
text: dive.notes
|
||||
focus: true
|
||||
Layout.columnSpan: 4
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
//selectByMouse: true
|
||||
wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere
|
||||
}
|
||||
Item {
|
||||
Layout.columnSpan: 4
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: Kirigami.Units.gridUnit * 3
|
||||
}
|
||||
Component.onCompleted: {
|
||||
qmlProfile.setMargin(Kirigami.Units.smallSpacing)
|
||||
qmlProfile.diveId = model.dive.id;
|
||||
qmlProfile.update();
|
||||
}
|
||||
}
|
||||
}
|
302
mobile-widgets/qml/DiveList.qml
Normal file
|
@ -0,0 +1,302 @@
|
|||
import QtQuick 2.4
|
||||
import QtQuick.Controls 1.2
|
||||
import QtQuick.Layouts 1.2
|
||||
import QtQuick.Window 2.2
|
||||
import QtQuick.Dialogs 1.2
|
||||
import org.kde.kirigami 1.0 as Kirigami
|
||||
import org.subsurfacedivelog.mobile 1.0
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
id: page
|
||||
objectName: "DiveList"
|
||||
title: "Subsurface-mobile"
|
||||
background: Rectangle {
|
||||
color: Kirigami.Theme.viewBackgroundColor
|
||||
}
|
||||
|
||||
property int credentialStatus: manager.credentialStatus
|
||||
property int numDives: diveListView.count
|
||||
property color textColor: subsurfaceTheme.diveListTextColor
|
||||
|
||||
function scrollToTop() {
|
||||
diveListView.positionViewAtBeginning()
|
||||
}
|
||||
|
||||
Component {
|
||||
id: diveDelegate
|
||||
Kirigami.AbstractListItem {
|
||||
enabled: true
|
||||
supportsMouseEvents: true
|
||||
checked: diveListView.currentIndex === model.index
|
||||
width: parent.width
|
||||
|
||||
property real detailsOpacity : 0
|
||||
property int horizontalPadding: Kirigami.Units.gridUnit / 2 - Kirigami.Units.smallSpacing + 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()
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width - Kirigami.Units.gridUnit
|
||||
height: childrenRect.height - Kirigami.Units.smallSpacing
|
||||
spacing: horizontalPadding
|
||||
add: Transition {
|
||||
NumberAnimation { property: "opacity"; from: 0; to: 1.0; duration: 400 }
|
||||
NumberAnimation { property: "scale"; from: 0; to: 1.0; duration: 400 }
|
||||
}
|
||||
Item {
|
||||
id: diveListEntry
|
||||
width: parent.width - Kirigami.Units.gridUnit
|
||||
height: childrenRect.height - Kirigami.Units.smallSpacing
|
||||
|
||||
Kirigami.Label {
|
||||
id: locationText
|
||||
text: dive.location
|
||||
font.weight: Font.Light
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1 // needed for elide to work at all
|
||||
color: textColor
|
||||
anchors {
|
||||
left: parent.left
|
||||
leftMargin: horizontalPadding
|
||||
top: parent.top
|
||||
right: dateLabel.left
|
||||
}
|
||||
}
|
||||
Kirigami.Label {
|
||||
id: dateLabel
|
||||
text: dive.date + " " + dive.time
|
||||
font.pointSize: subsurfaceTheme.smallPointSize
|
||||
color: textColor
|
||||
anchors {
|
||||
right: parent.right
|
||||
top: parent.top
|
||||
}
|
||||
}
|
||||
Row {
|
||||
anchors {
|
||||
left: parent.left
|
||||
leftMargin: horizontalPadding
|
||||
right: parent.right
|
||||
rightMargin: horizontalPadding
|
||||
topMargin: - Kirigami.Units.smallSpacing * 2
|
||||
bottom: numberText.bottom
|
||||
}
|
||||
Kirigami.Label {
|
||||
text: 'Depth: '
|
||||
font.pointSize: subsurfaceTheme.smallPointSize
|
||||
color: textColor
|
||||
}
|
||||
Kirigami.Label {
|
||||
text: dive.depth
|
||||
width: Math.max(Kirigami.Units.gridUnit * 3, paintedWidth) // helps vertical alignment throughout listview
|
||||
font.pointSize: subsurfaceTheme.smallPointSize
|
||||
color: textColor
|
||||
}
|
||||
Kirigami.Label {
|
||||
text: 'Duration: '
|
||||
font.pointSize: subsurfaceTheme.smallPointSize
|
||||
color: textColor
|
||||
}
|
||||
Kirigami.Label {
|
||||
text: dive.duration
|
||||
font.pointSize: subsurfaceTheme.smallPointSize
|
||||
color: textColor
|
||||
}
|
||||
}
|
||||
Kirigami.Label {
|
||||
id: numberText
|
||||
text: "#" + dive.number
|
||||
font.pointSize: subsurfaceTheme.smallPointSize
|
||||
color: textColor
|
||||
anchors {
|
||||
right: parent.right
|
||||
top: locationText.bottom
|
||||
topMargin: - Kirigami.Units.smallSpacing * 2
|
||||
}
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
visible: deleteButtonVisible
|
||||
height: diveListEntry.height - Kirigami.Units.smallSpacing
|
||||
width: height - 3 * Kirigami.Units.smallSpacing
|
||||
color: "#FF3030"
|
||||
antialiasing: true
|
||||
radius: Kirigami.Units.smallSpacing
|
||||
Kirigami.Icon {
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
source: "trash-empty"
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: parent.visible
|
||||
onClicked: {
|
||||
parent.visible = false
|
||||
timer.stop()
|
||||
manager.deleteDive(dive.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
Item {
|
||||
Timer {
|
||||
id: timer
|
||||
interval: 4000
|
||||
onTriggered: {
|
||||
deleteButtonVisible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: tripHeading
|
||||
Item {
|
||||
width: page.width - Kirigami.Units.gridUnit
|
||||
height: childrenRect.height + Kirigami.Units.smallSpacing * 2 + Math.max(2, Kirigami.Units.gridUnit / 2)
|
||||
|
||||
Kirigami.Heading {
|
||||
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
|
||||
}
|
||||
anchors {
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
topMargin: Math.max(2, Kirigami.Units.gridUnit / 2)
|
||||
leftMargin: Kirigami.Units.gridUnit / 2
|
||||
right: parent.right
|
||||
}
|
||||
color: textColor
|
||||
level: 2
|
||||
}
|
||||
Rectangle {
|
||||
height: Math.max(2, Kirigami.Units.gridUnit / 12) // we want a thicker line
|
||||
anchors {
|
||||
top: sectionText.bottom
|
||||
left: parent.left
|
||||
leftMargin: Kirigami.Units.gridUnit * -2
|
||||
rightMargin: Kirigami.Units.gridUnit * -2
|
||||
right: parent.right
|
||||
}
|
||||
color: subsurfaceTheme.accentColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
id: startPageWrapper
|
||||
anchors.fill: parent
|
||||
opacity: (diveListView.count > 0 && (credentialStatus == QMLManager.VALID || credentialStatus == QMLManager.VALID_EMAIL)) ? 0 : 1
|
||||
visible: opacity > 0
|
||||
Behavior on opacity { NumberAnimation { duration: Kirigami.Units.shortDuration } }
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
page.mainAction = page.saveAction
|
||||
} else {
|
||||
page.mainAction = page.addDiveAction
|
||||
}
|
||||
}
|
||||
|
||||
StartPage {
|
||||
id: startPage
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: diveListView
|
||||
anchors.fill: parent
|
||||
opacity: 0.8 - startPageWrapper.opacity
|
||||
visible: opacity > 0
|
||||
model: diveModel
|
||||
currentIndex: -1
|
||||
delegate: diveDelegate
|
||||
//boundsBehavior: Flickable.StopAtBounds
|
||||
maximumFlickVelocity: parent.height * 5
|
||||
bottomMargin: Kirigami.Units.iconSizes.medium + Kirigami.Units.gridUnit
|
||||
cacheBuffer: 0 // seems to avoid empty rendered profiles
|
||||
section.property: "dive.tripMeta"
|
||||
section.criteria: ViewSection.FullString
|
||||
section.delegate: tripHeading
|
||||
header: Kirigami.Heading {
|
||||
x: Kirigami.Units.gridUnit / 2
|
||||
height: paintedHeight + Kirigami.Units.gridUnit / 2
|
||||
verticalAlignment: Text.AlignBottom
|
||||
text: "Dive Log"
|
||||
}
|
||||
Connections {
|
||||
target: detailsWindow
|
||||
onCurrentIndexChanged: diveListView.currentIndex = detailsWindow.currentIndex
|
||||
}
|
||||
Connections {
|
||||
target: stackView
|
||||
onDepthChanged: {
|
||||
if (stackView.depth === 1) {
|
||||
diveListView.currentIndex = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
Connections {
|
||||
target: header
|
||||
onTitleBarClicked: {
|
||||
// if we can see the dive list and it's not at the top already, go to the top,
|
||||
// otherwise have the title bar handle the click (for bread-crumb navigation)
|
||||
if (stackView.currentItem.objectName === "DiveList" && diveListView.contentY > Kirigami.Units.gridUnit) {
|
||||
diveListView.positionViewAtBeginning()
|
||||
event.accepted = true
|
||||
} else {
|
||||
event.accepted = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
property QtObject addDiveAction: Action {
|
||||
iconName: "list-add"
|
||||
onTriggered: {
|
||||
startAddDive()
|
||||
}
|
||||
}
|
||||
|
||||
property QtObject saveAction: Action {
|
||||
iconName: "document-save"
|
||||
onTriggered: {
|
||||
startPage.saveCredentials();
|
||||
}
|
||||
}
|
||||
|
||||
onBackRequested: {
|
||||
if (startPageWrapper.visible && diveListView.count > 0 && manager.credentialStatus != QMLManager.INVALID) {
|
||||
manager.credentialStatus = oldStatus
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
125
mobile-widgets/qml/DownloadFromDiveComputer.qml
Normal file
|
@ -0,0 +1,125 @@
|
|||
import QtQuick 2.3
|
||||
import QtQuick.Controls 1.2
|
||||
import QtQuick.Controls.Styles 1.2
|
||||
import QtQuick.Window 2.2
|
||||
import QtQuick.Dialogs 1.2
|
||||
import QtQuick.Layouts 1.1
|
||||
import org.subsurfacedivelog.mobile 1.0
|
||||
import org.kde.kirigami 1.0 as Kirigami
|
||||
|
||||
Kirigami.Page {
|
||||
id: diveComputerDownloadWindow
|
||||
anchors.top:parent.top
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
Layout.fillWidth: true;
|
||||
title: "Dive Computer"
|
||||
|
||||
/* this can be done by hitting the back key
|
||||
contextualActions: [
|
||||
Action {
|
||||
text: "Close Preferences"
|
||||
iconName: "dialog-cancel"
|
||||
onTriggered: {
|
||||
stackView.pop()
|
||||
contextDrawer.close()
|
||||
}
|
||||
}
|
||||
]
|
||||
*/
|
||||
ColumnLayout {
|
||||
anchors.top: parent.top
|
||||
height: parent.height
|
||||
width: parent.width
|
||||
Layout.fillWidth: true
|
||||
RowLayout {
|
||||
anchors.top:parent.top
|
||||
Layout.fillWidth: true
|
||||
Text { text: " Vendor name : " }
|
||||
ComboBox { Layout.fillWidth: true }
|
||||
}
|
||||
RowLayout {
|
||||
Text { text: " Dive Computer:" }
|
||||
ComboBox { Layout.fillWidth: true }
|
||||
}
|
||||
RowLayout {
|
||||
Text { text: " Progress:" }
|
||||
Layout.fillWidth: true
|
||||
ProgressBar { Layout.fillWidth: true }
|
||||
}
|
||||
RowLayout {
|
||||
SubsurfaceButton {
|
||||
text: "Download"
|
||||
onClicked: {
|
||||
text: "Retry"
|
||||
stackView.pop();
|
||||
}
|
||||
}
|
||||
SubsurfaceButton {
|
||||
id:quitbutton
|
||||
text: "Quit"
|
||||
onClicked: {
|
||||
stackView.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
Text {
|
||||
text: " Downloaded dives"
|
||||
}
|
||||
}
|
||||
TableView {
|
||||
width: parent.width
|
||||
Layout.fillWidth: true // The tableview should fill
|
||||
Layout.fillHeight: true // all remaining vertical space
|
||||
height: parent.height // on this screen
|
||||
TableViewColumn {
|
||||
width: parent.width / 2
|
||||
role: "datetime"
|
||||
title: "Date / Time"
|
||||
}
|
||||
TableViewColumn {
|
||||
width: parent.width / 4
|
||||
role: "duration"
|
||||
title: "Duration"
|
||||
}
|
||||
TableViewColumn {
|
||||
width: parent.width / 4
|
||||
role: "depth"
|
||||
title: "Depth"
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
SubsurfaceButton {
|
||||
text: "Accept"
|
||||
onClicked: {
|
||||
stackView.pop();
|
||||
}
|
||||
}
|
||||
SubsurfaceButton {
|
||||
text: "Quit"
|
||||
onClicked: {
|
||||
stackView.pop();
|
||||
}
|
||||
}
|
||||
Text {
|
||||
text: "" // Spacer between 2 button groups
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
SubsurfaceButton {
|
||||
text: "Select All"
|
||||
}
|
||||
SubsurfaceButton {
|
||||
id: unselectbutton
|
||||
text: "Unselect All"
|
||||
}
|
||||
}
|
||||
RowLayout { // spacer to make space for silly button
|
||||
Layout.minimumHeight: 1.2 * unselectbutton.height
|
||||
Text {
|
||||
text:""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
128
mobile-widgets/qml/GpsList.qml
Normal file
|
@ -0,0 +1,128 @@
|
|||
import QtQuick 2.3
|
||||
import QtQuick.Controls 1.2
|
||||
import QtQuick.Controls.Styles 1.2
|
||||
import QtQuick.Window 2.2
|
||||
import QtQuick.Dialogs 1.2
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Window 2.2
|
||||
import org.subsurfacedivelog.mobile 1.0
|
||||
import org.kde.kirigami 1.0 as Kirigami
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
id: gpsListWindow
|
||||
width: parent.width - Kirigami.Units.gridUnit
|
||||
anchors.margins: Kirigami.Units.gridUnit / 2
|
||||
objectName: "gpsList"
|
||||
title: "GPS Fixes"
|
||||
|
||||
/* this can be done by hitting the back key
|
||||
contextualActions: [
|
||||
Action {
|
||||
text: "Close GPS list"
|
||||
iconName: "dialog-cancel"
|
||||
onTriggered: {
|
||||
stackView.pop()
|
||||
contextDrawer.close()
|
||||
}
|
||||
}
|
||||
]
|
||||
*/
|
||||
Component {
|
||||
id: gpsDelegate
|
||||
Kirigami.SwipeListItem {
|
||||
id: gpsFix
|
||||
enabled: true
|
||||
width: parent.width
|
||||
property int horizontalPadding: Kirigami.Units.gridUnit / 2 - Kirigami.Units.smallSpacing + 1
|
||||
|
||||
Kirigami.BasicListItem {
|
||||
supportsMouseEvents: true
|
||||
width: parent.width - Kirigami.Units.gridUnit
|
||||
height: childrenRect.height - Kirigami.Units.smallSpacing
|
||||
GridLayout {
|
||||
columns: 4
|
||||
id: timeAndName
|
||||
anchors {
|
||||
left: parent.left
|
||||
leftMargin: horizontalPadding
|
||||
right: parent.right
|
||||
rightMargin: horizontalPadding
|
||||
}
|
||||
Kirigami.Label {
|
||||
text: 'Date: '
|
||||
opacity: 0.6
|
||||
font.pointSize: subsurfaceTheme.smallPointSize
|
||||
}
|
||||
Kirigami.Label {
|
||||
text: date
|
||||
Layout.preferredWidth: Math.max(parent.width / 5, paintedWidth)
|
||||
font.pointSize: subsurfaceTheme.smallPointSize
|
||||
}
|
||||
Kirigami.Label {
|
||||
text: 'Name: '
|
||||
opacity: 0.6
|
||||
font.pointSize: subsurfaceTheme.smallPointSize
|
||||
}
|
||||
Kirigami.Label {
|
||||
text: name
|
||||
Layout.preferredWidth: Math.max(parent.width / 5, paintedWidth)
|
||||
font.pointSize: subsurfaceTheme.smallPointSize
|
||||
}
|
||||
Kirigami.Label {
|
||||
text: 'Latitude: '
|
||||
opacity: 0.6
|
||||
font.pointSize: subsurfaceTheme.smallPointSize
|
||||
}
|
||||
Kirigami.Label {
|
||||
text: latitude
|
||||
font.pointSize: subsurfaceTheme.smallPointSize
|
||||
}
|
||||
Kirigami.Label {
|
||||
text: 'Longitude: '
|
||||
opacity: 0.6
|
||||
font.pointSize: subsurfaceTheme.smallPointSize
|
||||
}
|
||||
Kirigami.Label {
|
||||
text: longitude
|
||||
font.pointSize: subsurfaceTheme.smallPointSize
|
||||
}
|
||||
}
|
||||
}
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
iconName: "trash-empty"
|
||||
onTriggered: {
|
||||
print("delete this!")
|
||||
manager.deleteGpsFix(when)
|
||||
}
|
||||
},
|
||||
Kirigami.Action {
|
||||
iconName: "gps"
|
||||
onTriggered: {
|
||||
showMap(latitude + " " + longitude)
|
||||
}
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: gpsListView
|
||||
anchors.fill: parent
|
||||
model: gpsModel
|
||||
currentIndex: -1
|
||||
delegate: gpsDelegate
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
maximumFlickVelocity: parent.height * 5
|
||||
cacheBuffer: Math.max(5000, parent.height * 5)
|
||||
focus: true
|
||||
clip: true
|
||||
header: Kirigami.Heading {
|
||||
x: Kirigami.Units.gridUnit / 2
|
||||
height: paintedHeight + Kirigami.Units.gridUnit / 2
|
||||
verticalAlignment: Text.AlignBottom
|
||||
text: "List of stored GPS fixes"
|
||||
}
|
||||
}
|
||||
}
|
40
mobile-widgets/qml/Log.qml
Normal file
|
@ -0,0 +1,40 @@
|
|||
import QtQuick 2.3
|
||||
import QtQuick.Controls 1.2
|
||||
import QtQuick.Controls.Styles 1.2
|
||||
import QtQuick.Window 2.2
|
||||
import QtQuick.Dialogs 1.2
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Window 2.2
|
||||
import org.subsurfacedivelog.mobile 1.0
|
||||
import org.kde.kirigami 1.0 as Kirigami
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
id: logWindow
|
||||
width: parent.width - Kirigami.Units.gridUnit
|
||||
anchors.margins: Kirigami.Units.gridUnit / 2
|
||||
objectName: "Log"
|
||||
title: "Application Log"
|
||||
|
||||
property int pageWidth: subsurfaceTheme.columnWidth - Kirigami.Units.smallSpacing
|
||||
|
||||
ColumnLayout {
|
||||
width: pageWidth
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
Kirigami.Heading {
|
||||
text: "Application Log"
|
||||
}
|
||||
Kirigami.Label {
|
||||
id: logContent
|
||||
width: parent.width
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.maximumWidth: parent.width
|
||||
wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere
|
||||
text: manager.logText
|
||||
}
|
||||
Rectangle {
|
||||
color: "transparent"
|
||||
height: Kirigami.Units.gridUnit * 2
|
||||
width: pageWidth
|
||||
}
|
||||
}
|
||||
}
|
74
mobile-widgets/qml/Preferences.qml
Normal file
|
@ -0,0 +1,74 @@
|
|||
import QtQuick 2.3
|
||||
import QtQuick.Controls 1.2
|
||||
import QtQuick.Window 2.2
|
||||
import QtQuick.Dialogs 1.2
|
||||
import QtQuick.Layouts 1.1
|
||||
import org.kde.kirigami 1.0 as Kirigami
|
||||
import org.subsurfacedivelog.mobile 1.0
|
||||
|
||||
Kirigami.Page {
|
||||
|
||||
title: "Preferences"
|
||||
mainAction: Action {
|
||||
text: "Save"
|
||||
iconName: "document-save"
|
||||
onTriggered: {
|
||||
manager.distanceThreshold = distanceThreshold.text
|
||||
manager.timeThreshold = timeThreshold.text
|
||||
manager.savePreferences()
|
||||
stackView.pop()
|
||||
}
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
|
||||
signal accept
|
||||
|
||||
columns: 2
|
||||
width: parent.width - Kirigami.Units.gridUnit
|
||||
anchors {
|
||||
fill: parent
|
||||
margins: Kirigami.Units.gridUnit / 2
|
||||
}
|
||||
|
||||
Kirigami.Heading {
|
||||
text: "Preferences"
|
||||
Layout.bottomMargin: Kirigami.Units.largeSpacing / 2
|
||||
Layout.columnSpan: 2
|
||||
}
|
||||
|
||||
Kirigami.Heading {
|
||||
text: "Subsurface GPS data webservice"
|
||||
level: 3
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
Layout.bottomMargin: Kirigami.Units.largeSpacing / 2
|
||||
Layout.columnSpan: 2
|
||||
}
|
||||
|
||||
Kirigami.Label {
|
||||
text: "Distance threshold (meters)"
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: distanceThreshold
|
||||
text: manager.distanceThreshold
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Kirigami.Label {
|
||||
text: "Time threshold (minutes)"
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: timeThreshold
|
||||
text: manager.timeThreshold
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
}
|
42
mobile-widgets/qml/StartPage.qml
Normal file
|
@ -0,0 +1,42 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.2
|
||||
import QtQuick.Controls.Styles 1.2
|
||||
import QtQuick.Layouts 1.1
|
||||
import org.kde.kirigami 1.0 as Kirigami
|
||||
import org.subsurfacedivelog.mobile 1.0
|
||||
|
||||
ColumnLayout {
|
||||
id: startpage
|
||||
width: subsurfaceTheme.columnWidth
|
||||
|
||||
function saveCredentials() { cloudCredentials.saveCredentials() }
|
||||
|
||||
Kirigami.Heading {
|
||||
Layout.margins: Kirigami.Units.gridUnit
|
||||
text: "Subsurface-mobile"
|
||||
}
|
||||
Kirigami.Label {
|
||||
id: explanationText
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Kirigami.Units.gridUnit
|
||||
Layout.topMargin: 0
|
||||
text: "In order to use Subsurface-mobile you need to have a Subsurface cloud storage account " +
|
||||
"(which can be created with the Subsurface desktop application)."
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
Kirigami.Label {
|
||||
id: messageArea
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Kirigami.Units.gridUnit
|
||||
Layout.topMargin: 0
|
||||
text: manager.startPageText
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
CloudCredentials {
|
||||
id: cloudCredentials
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Kirigami.Units.gridUnit
|
||||
Layout.topMargin: 0
|
||||
property int headingLevel: 3
|
||||
}
|
||||
}
|
26
mobile-widgets/qml/SubsurfaceButton.qml
Normal file
|
@ -0,0 +1,26 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.2
|
||||
import QtQuick.Controls.Styles 1.2
|
||||
import org.kde.kirigami 1.0 as Kirigami
|
||||
|
||||
Button {
|
||||
style: ButtonStyle {
|
||||
padding {
|
||||
top: Kirigami.Units.smallSpacing * 2
|
||||
left: Kirigami.Units.smallSpacing * 4
|
||||
right: Kirigami.Units.smallSpacing * 4
|
||||
bottom: Kirigami.Units.smallSpacing * 2
|
||||
}
|
||||
background: Rectangle {
|
||||
border.width: 1
|
||||
radius: height / 3
|
||||
color: control.pressed ? subsurfaceTheme.shadedColor : subsurfaceTheme.accentColor
|
||||
}
|
||||
label: Text{
|
||||
text: control.text
|
||||
color: subsurfaceTheme.accentTextColor
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
37
mobile-widgets/qml/TextButton.qml
Normal file
|
@ -0,0 +1,37 @@
|
|||
import QtQuick 2.3
|
||||
|
||||
Rectangle {
|
||||
id: container
|
||||
|
||||
property alias text: label.text
|
||||
|
||||
signal clicked
|
||||
|
||||
width: label.width + 20; height: label.height + 6
|
||||
smooth: true
|
||||
radius: 10
|
||||
|
||||
gradient: Gradient {
|
||||
GradientStop { id: gradientStop; position: 0.0; color: palette.light }
|
||||
GradientStop { position: 1.0; color: palette.button }
|
||||
}
|
||||
|
||||
SystemPalette { id: palette }
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
onClicked: { container.clicked() }
|
||||
}
|
||||
|
||||
Text {
|
||||
id: label
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
states: State {
|
||||
name: "pressed"
|
||||
when: mouseArea.pressed
|
||||
PropertyChanges { target: gradientStop; color: palette.dark }
|
||||
}
|
||||
}
|
115
mobile-widgets/qml/ThemeTest.qml
Normal file
|
@ -0,0 +1,115 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Window 2.2
|
||||
import org.kde.kirigami 1.0 as Kirigami
|
||||
|
||||
Kirigami.Page {
|
||||
|
||||
title: "Theme Information"
|
||||
/* this can be done by hitting the back key
|
||||
contextualActions: [
|
||||
Action {
|
||||
text: "Close Theme info"
|
||||
iconName: "dialog-cancel"
|
||||
onTriggered: {
|
||||
stackView.pop()
|
||||
contextDrawer.close()
|
||||
}
|
||||
}
|
||||
]
|
||||
*/
|
||||
GridLayout {
|
||||
id: themetest
|
||||
columns: 2
|
||||
anchors.margins: Kirigami.Units.gridUnit / 2
|
||||
|
||||
Kirigami.Heading {
|
||||
Layout.columnSpan: 2
|
||||
text: "Theme Information"
|
||||
}
|
||||
|
||||
Kirigami.Heading {
|
||||
text: "Screen"
|
||||
Layout.columnSpan: 2
|
||||
level: 3
|
||||
}
|
||||
FontMetrics {
|
||||
id: fm
|
||||
}
|
||||
|
||||
Kirigami.Label {
|
||||
text: "Geometry (pixels):"
|
||||
}
|
||||
Kirigami.Label {
|
||||
text: rootItem.width + "x" + rootItem.height
|
||||
}
|
||||
|
||||
Kirigami.Label {
|
||||
text: "Geometry (gridUnits):"
|
||||
}
|
||||
Kirigami.Label {
|
||||
text: Math.round(rootItem.width / Kirigami.Units.gridUnit) + "x" + Math.round(rootItem.height / Kirigami.Units.gridUnit)
|
||||
}
|
||||
|
||||
Kirigami.Label {
|
||||
text: "Units.gridUnit:"
|
||||
}
|
||||
Kirigami.Label {
|
||||
text: Kirigami.Units.gridUnit
|
||||
}
|
||||
|
||||
Kirigami.Label {
|
||||
text: "Units.devicePixelRatio:"
|
||||
}
|
||||
Kirigami.Label {
|
||||
text: Screen.devicePixelRatio
|
||||
}
|
||||
|
||||
Kirigami.Heading {
|
||||
text: "Font Metrics"
|
||||
level: 3
|
||||
Layout.columnSpan: 2
|
||||
}
|
||||
|
||||
Kirigami.Label {
|
||||
text: "FontMetrics pointSize:"
|
||||
}
|
||||
Kirigami.Label {
|
||||
text: fm.font.pointSize
|
||||
}
|
||||
|
||||
Kirigami.Label {
|
||||
text: "FontMetrics pixelSize:"
|
||||
}
|
||||
Kirigami.Label {
|
||||
text: fm.height
|
||||
}
|
||||
|
||||
Kirigami.Label {
|
||||
text: "FontMetrics devicePixelRatio:"
|
||||
}
|
||||
Kirigami.Label {
|
||||
text: fm.height / fm.font.pointSize
|
||||
}
|
||||
|
||||
Kirigami.Label {
|
||||
text: "Text item pixelSize:"
|
||||
}
|
||||
Text {
|
||||
text: font.pixelSize
|
||||
}
|
||||
|
||||
Kirigami.Label {
|
||||
text: "Text item pointSize:"
|
||||
}
|
||||
Text {
|
||||
text: font.pointSize
|
||||
}
|
||||
|
||||
Kirigami.Label {
|
||||
Layout.columnSpan: 2
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
}
|
59
mobile-widgets/qml/TopBar.qml
Normal file
|
@ -0,0 +1,59 @@
|
|||
import QtQuick 2.3
|
||||
import QtQuick.Controls 1.2
|
||||
import QtQuick.Controls.Styles 1.2
|
||||
import QtQuick.Window 2.2
|
||||
import QtQuick.Dialogs 1.2
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Window 2.2
|
||||
import org.kde.kirigami 1.0 as Kirigami
|
||||
import org.subsurfacedivelog.mobile 1.0
|
||||
|
||||
Rectangle {
|
||||
id: topPart
|
||||
|
||||
color: subsurfaceTheme.accentColor
|
||||
Layout.minimumHeight: Math.round(Kirigami.Units.gridUnit * 1.5)
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 0
|
||||
RowLayout {
|
||||
anchors.verticalCenter: topPart.verticalCenter
|
||||
Item {
|
||||
Layout.preferredHeight: subsurfaceLogo.height
|
||||
Layout.leftMargin: Kirigami.Units.gridUnit / 4
|
||||
Image {
|
||||
id: subsurfaceLogo
|
||||
source: "qrc:/qml/subsurface-mobile-icon.png"
|
||||
anchors {
|
||||
verticalCenter: parent.Center
|
||||
left: parent.left
|
||||
}
|
||||
width: Math.round(Kirigami.Units.gridUnit)
|
||||
height: width
|
||||
}
|
||||
Kirigami.Label {
|
||||
text: qsTr("Subsurface-mobile")
|
||||
font.pointSize: Math.round(Kirigami.Theme.defaultFont.pointSize)
|
||||
height: subsurfaceLogo.height
|
||||
anchors {
|
||||
left: subsurfaceLogo.right
|
||||
leftMargin: Math.round(Kirigami.Units.gridUnit / 2)
|
||||
}
|
||||
font.weight: Font.Light
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
Layout.fillWidth: false
|
||||
color: subsurfaceTheme.accentTextColor
|
||||
}
|
||||
}
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: topPart
|
||||
onClicked: {
|
||||
if (stackView.depth == 1 && showingDiveList) {
|
||||
scrollToTop()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
BIN
mobile-widgets/qml/dive.jpg
Normal file
After Width: | Height: | Size: 230 KiB |
BIN
mobile-widgets/qml/icons/context-menu.png
Normal file
After Width: | Height: | Size: 641 B |
1
mobile-widgets/qml/icons/context-menu.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><path d="M24 16c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 4c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm0 12c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4z"/></svg>
|
After Width: | Height: | Size: 275 B |
BIN
mobile-widgets/qml/icons/main-menu.png
Normal file
After Width: | Height: | Size: 112 B |
1
mobile-widgets/qml/icons/main-menu.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><path d="M6 36h36v-4H6v4zm0-10h36v-4H6v4zm0-14v4h36v-4H6z"/></svg>
|
After Width: | Height: | Size: 150 B |
BIN
mobile-widgets/qml/icons/menu-back.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
mobile-widgets/qml/icons/menu-edit.png
Normal file
After Width: | Height: | Size: 7.2 KiB |
360
mobile-widgets/qml/main.qml
Normal file
|
@ -0,0 +1,360 @@
|
|||
import QtQuick 2.4
|
||||
import QtQuick.Controls 1.2
|
||||
import QtQuick.Controls.Styles 1.2
|
||||
import QtQuick.Window 2.2
|
||||
import QtQuick.Dialogs 1.2
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Window 2.2
|
||||
import org.subsurfacedivelog.mobile 1.0
|
||||
import org.kde.kirigami 1.0 as Kirigami
|
||||
|
||||
Kirigami.ApplicationWindow {
|
||||
id: rootItem
|
||||
title: qsTr("Subsurface-mobile")
|
||||
|
||||
header.minimumHeight: 0
|
||||
header.preferredHeight: Kirigami.Units.gridUnit
|
||||
header.maximumHeight: Kirigami.Units.gridUnit * 2
|
||||
property bool fullscreen: true
|
||||
property int oldStatus: -1
|
||||
property alias accessingCloud: manager.accessingCloud
|
||||
property QtObject notification: null
|
||||
property bool showingDiveList: false
|
||||
property alias syncToCloud: manager.syncToCloud
|
||||
onAccessingCloudChanged: {
|
||||
if (accessingCloud >= 0) {
|
||||
// we now keep updating this to show progress, so timing out after 30 seconds is more useful
|
||||
// but should still be very conservative
|
||||
showPassiveNotification("Accessing Subsurface Cloud Storage " + accessingCloud +"%", 30000);
|
||||
} else {
|
||||
hidePassiveNotification();
|
||||
}
|
||||
}
|
||||
|
||||
FontMetrics {
|
||||
id: fontMetrics
|
||||
}
|
||||
|
||||
visible: false
|
||||
opacity: 0
|
||||
|
||||
function returnTopPage() {
|
||||
for (var i=stackView.depth; i>1; i--) {
|
||||
stackView.pop()
|
||||
}
|
||||
detailsWindow.endEditMode()
|
||||
}
|
||||
|
||||
function scrollToTop() {
|
||||
diveList.scrollToTop()
|
||||
}
|
||||
|
||||
function showMap(location) {
|
||||
var urlPrefix = "https://www.google.com/maps/place/"
|
||||
var locationPair = location + "/@" + location
|
||||
var urlSuffix = ",5000m/data=!3m1!1e3!4m2!3m1!1s0x0:0x0"
|
||||
Qt.openUrlExternally(urlPrefix + locationPair + urlSuffix)
|
||||
|
||||
}
|
||||
|
||||
function startAddDive() {
|
||||
detailsWindow.state = "add"
|
||||
detailsWindow.dive_id = manager.addDive();
|
||||
detailsWindow.number = manager.getNumber(detailsWindow.dive_id)
|
||||
detailsWindow.date = manager.getDate(detailsWindow.dive_id)
|
||||
detailsWindow.airtemp = ""
|
||||
detailsWindow.watertemp = ""
|
||||
detailsWindow.buddy = ""
|
||||
detailsWindow.depth = ""
|
||||
detailsWindow.divemaster = ""
|
||||
detailsWindow.notes = ""
|
||||
detailsWindow.location = ""
|
||||
detailsWindow.duration = ""
|
||||
detailsWindow.suit = ""
|
||||
detailsWindow.weight = ""
|
||||
detailsWindow.gasmix = ""
|
||||
detailsWindow.startpressure = ""
|
||||
detailsWindow.endpressure = ""
|
||||
stackView.push(detailsWindow)
|
||||
}
|
||||
|
||||
globalDrawer: Kirigami.GlobalDrawer {
|
||||
title: "Subsurface"
|
||||
titleIcon: "qrc:/qml/subsurface-mobile-icon.png"
|
||||
|
||||
bannerImageSource: "dive.jpg"
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
text: "Dive list"
|
||||
onTriggered: {
|
||||
manager.appendTextToLog("requested dive list with credential status " + manager.credentialStatus)
|
||||
if (manager.credentialStatus == QMLManager.UNKNOWN) {
|
||||
// the user has asked to change credentials - if the credentials before that
|
||||
// were valid, go back to dive list
|
||||
if (oldStatus == QMLManager.VALID || oldStatus == QMLManager.VALID_EMAIL) {
|
||||
manager.credentialStatus = oldStatus
|
||||
}
|
||||
}
|
||||
returnTopPage()
|
||||
globalDrawer.close()
|
||||
}
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: "Cloud credentials"
|
||||
onTriggered: {
|
||||
returnTopPage()
|
||||
oldStatus = manager.credentialStatus
|
||||
if (diveList.numDives > 0) {
|
||||
manager.startPageText = "Enter different credentials or return to dive list"
|
||||
} else {
|
||||
manager.startPageText = "Enter valid cloud storage credentials"
|
||||
}
|
||||
|
||||
manager.credentialStatus = QMLManager.UNKNOWN
|
||||
}
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: "Manage dives"
|
||||
enabled: manager.credentialStatus === QMLManager.VALID || manager.credentialStatus === QMLManager.VALID_EMAIL
|
||||
/*
|
||||
* disable for the beta to avoid confusion
|
||||
Action {
|
||||
text: "Download from computer"
|
||||
onTriggered: {
|
||||
detailsWindow.endEditMode()
|
||||
stackView.push(downloadDivesWindow)
|
||||
}
|
||||
}
|
||||
*/
|
||||
Kirigami.Action {
|
||||
text: "Add dive manually"
|
||||
onTriggered: {
|
||||
returnTopPage() // otherwise odd things happen with the page stack
|
||||
startAddDive()
|
||||
}
|
||||
}
|
||||
Kirigami.Action {
|
||||
text: "Manual sync with cloud"
|
||||
onTriggered: {
|
||||
globalDrawer.close()
|
||||
detailsWindow.endEditMode()
|
||||
manager.saveChanges();
|
||||
}
|
||||
}
|
||||
Kirigami.Action {
|
||||
text: syncToCloud ? "Disable auto cloud sync" : "Enable auto cloud sync"
|
||||
onTriggered: {
|
||||
syncToCloud = !syncToCloud
|
||||
if (!syncToCloud) {
|
||||
var alertText = "Turning off automatic sync to cloud causes all data to only be stored locally.\n"
|
||||
alertText += "This can be very useful in situations with limited or no network access.\n"
|
||||
alertText += "Please chose 'Manual sync with cloud' if you have network connectivity\n"
|
||||
alertText += "and want to sync your data to cloud storage."
|
||||
showPassiveNotification(alertText, 10000)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Kirigami.Action {
|
||||
text: "GPS"
|
||||
enabled: manager.credentialStatus === QMLManager.VALID || manager.credentialStatus === QMLManager.VALID_EMAIL
|
||||
Kirigami.Action {
|
||||
text: "GPS-tag dives"
|
||||
onTriggered: {
|
||||
manager.applyGpsData();
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Action {
|
||||
text: "Upload GPS data"
|
||||
onTriggered: {
|
||||
manager.sendGpsData();
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Action {
|
||||
text: "Download GPS data"
|
||||
onTriggered: {
|
||||
manager.downloadGpsData();
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Action {
|
||||
text: "Show GPS fixes"
|
||||
onTriggered: {
|
||||
returnTopPage()
|
||||
manager.populateGpsData();
|
||||
stackView.push(gpsWindow)
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Action {
|
||||
text: "Clear GPS cache"
|
||||
onTriggered: {
|
||||
manager.clearGpsData();
|
||||
}
|
||||
}
|
||||
Kirigami.Action {
|
||||
text: "Preferences"
|
||||
onTriggered: {
|
||||
stackView.push(prefsWindow)
|
||||
detailsWindow.endEditMode()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Kirigami.Action {
|
||||
text: "Developer"
|
||||
Kirigami.Action {
|
||||
text: "App log"
|
||||
onTriggered: {
|
||||
stackView.push(logWindow)
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Action {
|
||||
text: "Theme information"
|
||||
onTriggered: {
|
||||
stackView.push(themetest)
|
||||
}
|
||||
}
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: "User manual"
|
||||
onTriggered: {
|
||||
Qt.openUrlExternally("https://subsurface-divelog.org/documentation/subsurface-mobile-user-manual/")
|
||||
}
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: "About"
|
||||
onTriggered: {
|
||||
stackView.push(aboutWindow)
|
||||
detailsWindow.endEditMode()
|
||||
}
|
||||
}
|
||||
] // end actions
|
||||
|
||||
MouseArea {
|
||||
height: childrenRect.height
|
||||
width: Kirigami.Units.gridUnit * 10
|
||||
CheckBox {
|
||||
//text: "Run location service"
|
||||
id: locationCheckbox
|
||||
anchors {
|
||||
left: parent.left
|
||||
top: parent.top
|
||||
}
|
||||
checked: manager.locationServiceEnabled
|
||||
onCheckedChanged: {
|
||||
manager.locationServiceEnabled = checked;
|
||||
}
|
||||
}
|
||||
Kirigami.Label {
|
||||
x: Kirigami.Units.gridUnit * 1.5
|
||||
anchors {
|
||||
left: locationCheckbox.right
|
||||
//leftMargin: units.smallSpacing
|
||||
verticalCenter: locationCheckbox.verticalCenter
|
||||
}
|
||||
text: "Run location service"
|
||||
}
|
||||
onClicked: {
|
||||
print("Click.")
|
||||
locationCheckbox.checked = !locationCheckbox.checked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contextDrawer: Kirigami.ContextDrawer {
|
||||
id: contextDrawer
|
||||
actions: rootItem.pageStack.currentPage ? rootItem.pageStack.currentPage.contextualActions : null
|
||||
title: "Actions"
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: subsurfaceTheme
|
||||
property int titlePointSize: Math.round(fontMetrics.font.pointSize * 1.5)
|
||||
property int smallPointSize: Math.round(fontMetrics.font.pointSize * 0.8)
|
||||
property color accentColor: "#2d5b9a"
|
||||
property color shadedColor: "#132744"
|
||||
property color accentTextColor: "#ececec"
|
||||
property color diveListTextColor: "#000000" // the Kirigami theme text color is too light
|
||||
property int columnWidth: Math.round(rootItem.width/(Kirigami.Units.gridUnit*30)) > 0 ? Math.round(rootItem.width / Math.round(rootItem.width/(Kirigami.Units.gridUnit*30))) : rootItem.width
|
||||
}
|
||||
/*
|
||||
toolBar: TopBar {
|
||||
width: parent.width
|
||||
height: Layout.minimumHeight
|
||||
}
|
||||
*/
|
||||
|
||||
property Item stackView: pageStack
|
||||
pageStack.initialPage: DiveList {
|
||||
anchors.fill: detailsPage
|
||||
id: diveList
|
||||
opacity: 0
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: 200
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QMLManager {
|
||||
id: manager
|
||||
}
|
||||
|
||||
Preferences {
|
||||
id: prefsWindow
|
||||
visible: false
|
||||
}
|
||||
|
||||
About {
|
||||
id: aboutWindow
|
||||
visible: false
|
||||
}
|
||||
|
||||
DiveDetails {
|
||||
id: detailsWindow
|
||||
visible: false
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
}
|
||||
|
||||
DownloadFromDiveComputer {
|
||||
id: downloadDivesWindow
|
||||
visible: false
|
||||
}
|
||||
|
||||
Log {
|
||||
id: logWindow
|
||||
visible: false
|
||||
}
|
||||
|
||||
GpsList {
|
||||
id: gpsWindow
|
||||
visible: false
|
||||
}
|
||||
|
||||
ThemeTest {
|
||||
id: themetest
|
||||
visible: false
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
Kirigami.Theme.highlightColor = subsurfaceTheme.accentColor
|
||||
manager.finishSetup();
|
||||
rootItem.visible = true
|
||||
diveList.opacity = 1
|
||||
rootItem.opacity = 1
|
||||
}
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: 200
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
}
|
||||
}
|
66
mobile-widgets/qml/mobile-resources.qrc
Normal file
|
@ -0,0 +1,66 @@
|
|||
<RCC>
|
||||
<qresource prefix="/qml">
|
||||
<file>main.qml</file>
|
||||
<file>TextButton.qml</file>
|
||||
<file>Preferences.qml</file>
|
||||
<file>About.qml</file>
|
||||
<file>CloudCredentials.qml</file>
|
||||
<file>DiveList.qml</file>
|
||||
<file>DiveDetails.qml</file>
|
||||
<file>DiveDetailsEdit.qml</file>
|
||||
<file>DiveDetailsView.qml</file>
|
||||
<file>DownloadFromDiveComputer.qml</file>
|
||||
<file>GpsList.qml</file>
|
||||
<file>Log.qml</file>
|
||||
<file>TopBar.qml</file>
|
||||
<file>ThemeTest.qml</file>
|
||||
<file>StartPage.qml</file>
|
||||
<file>dive.jpg</file>
|
||||
<file>SubsurfaceButton.qml</file>
|
||||
<file alias="subsurface-mobile-icon.png">../../icons/subsurface-mobile-icon.png</file>
|
||||
<file alias="main-menu.png">icons/main-menu.png</file>
|
||||
<file alias="context-menu.png">icons/context-menu.png</file>
|
||||
<file alias="menu-edit.png">icons/menu-edit.png</file>
|
||||
<file alias="menu-back.png">icons/menu-back.png</file>
|
||||
</qresource>
|
||||
<qresource prefix="/imports">
|
||||
<file alias="org/kde/kirigami/qmldir">kirigami/qmldir</file>
|
||||
<file alias="org/kde/kirigami/Action.qml">kirigami/Action.qml</file>
|
||||
<file alias="org/kde/kirigami/ApplicationWindow.qml">kirigami/ApplicationWindow.qml</file>
|
||||
<file alias="org/kde/kirigami/BasicListItem.qml">kirigami/BasicListItem.qml</file>
|
||||
<file alias="org/kde/kirigami/GlobalDrawer.qml">kirigami/GlobalDrawer.qml</file>
|
||||
<file alias="org/kde/kirigami/ContextDrawer.qml">kirigami/ContextDrawer.qml</file>
|
||||
<file alias="org/kde/kirigami/Page.qml">kirigami/Page.qml</file>
|
||||
<file alias="org/kde/kirigami/ScrollablePage.qml">kirigami/ScrollablePage.qml</file>
|
||||
<file alias="org/kde/kirigami/Icon.qml">kirigami/Icon.qml</file>
|
||||
<file alias="org/kde/kirigami/Heading.qml">kirigami/Heading.qml</file>
|
||||
<file alias="org/kde/kirigami/OverlaySheet.qml">kirigami/OverlaySheet.qml</file>
|
||||
<file alias="org/kde/kirigami/ApplicationHeader.qml">kirigami/ApplicationHeader.qml</file>
|
||||
<file alias="org/kde/kirigami/private/PageRow.qml">kirigami/private/PageRow.qml</file>
|
||||
<file alias="org/kde/kirigami/Label.qml">kirigami/Label.qml</file>
|
||||
<file alias="org/kde/kirigami/AbstractListItem.qml">kirigami/AbstractListItem.qml</file>
|
||||
<file alias="org/kde/kirigami/SwipeListItem.qml">kirigami/SwipeListItem.qml</file>
|
||||
<file alias="org/kde/kirigami/OverlayDrawer.qml">kirigami/OverlayDrawer.qml</file>
|
||||
<file alias="org/kde/kirigami/Theme.qml">kirigami/Theme.qml</file>
|
||||
<file alias="org/kde/kirigami/Units.qml">kirigami/Units.qml</file>
|
||||
<file alias="org/kde/kirigami/private/RefreshableScrollView.qml">kirigami/private/RefreshableScrollView.qml</file>
|
||||
<file alias="org/kde/kirigami/private/ActionButton.qml">kirigami/private/ActionButton.qml</file>
|
||||
<file alias="org/kde/kirigami/private/BackButton.qml">kirigami/private/BackButton.qml</file>
|
||||
<file alias="org/kde/kirigami/private/MenuIcon.qml">kirigami/private/MenuIcon.qml</file>
|
||||
<file alias="org/kde/kirigami/private/ContextIcon.qml">kirigami/private/ContextIcon.qml</file>
|
||||
<file alias="org/kde/kirigami/private/AbstractDrawer.qml">kirigami/private/AbstractDrawer.qml</file>
|
||||
<file alias="org/kde/kirigami/private/PageStack.js">kirigami/private/PageStack.js</file>
|
||||
<file alias="org/kde/kirigami/private/PassiveNotification.qml">kirigami/private/PassiveNotification.qml</file>
|
||||
<file alias="org/kde/kirigami/icons/go-next.svg">kirigami/icons/go-next.svg</file>
|
||||
<file alias="org/kde/kirigami/icons/go-previous.svg">kirigami/icons/go-previous.svg</file>
|
||||
<file alias="org/kde/kirigami/icons/distribute-horizontal-x.svg">kirigami/icons/distribute-horizontal-x.svg</file>
|
||||
<file alias="org/kde/kirigami/icons/document-edit.svg">kirigami/icons/document-edit.svg</file>
|
||||
<file alias="org/kde/kirigami/icons/document-save.svg">kirigami/icons/document-save.svg</file>
|
||||
<file alias="org/kde/kirigami/icons/view-readermode.svg">kirigami/icons/view-readermode.svg</file>
|
||||
<file alias="org/kde/kirigami/icons/dialog-cancel.svg">kirigami/icons/dialog-cancel.svg</file>
|
||||
<file alias="org/kde/kirigami/icons/application-menu.svg">kirigami/icons/application-menu.svg</file>
|
||||
<file alias="org/kde/kirigami/icons/gps.svg">kirigami/icons/gps.svg</file>
|
||||
<file alias="org/kde/kirigami/icons/trash-empty.svg">kirigami/icons/trash-empty.svg</file>
|
||||
<file alias="org/kde/kirigami/icons/list-add.svg">kirigami/icons/list-add.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
57
mobile-widgets/qml/theme/Theme.qml
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright 2015 Marco Martin <mart@kde.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Library General Public License as
|
||||
* published by the Free Software Foundation; either version 2, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Library General Public License for more details
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
import QtQuick 2.0
|
||||
|
||||
//pragma Singleton
|
||||
|
||||
/*!
|
||||
\qmltype Theme
|
||||
\inqmlmodule Material 0.1
|
||||
|
||||
\brief Provides access to standard colors that follow the Material Design specification.
|
||||
|
||||
See \l {http://www.google.com/design/spec/style/color.html#color-ui-color-application} for
|
||||
details about choosing a color scheme for your application.
|
||||
*/
|
||||
QtObject {
|
||||
id: theme
|
||||
|
||||
property color textColor: Qt.rgba(0,0,0, 0.54)
|
||||
|
||||
property color highlightColor: "#2196F3"
|
||||
property color backgroundColor: "#f3f3f3"
|
||||
property color linkColor: "#2196F3"
|
||||
property color visitedLinkColor: "#2196F3"
|
||||
|
||||
property color buttonTextColor: Qt.rgba(0,0,0, 0.54)
|
||||
property color buttonBackgroundColor: "#f3f3f3"
|
||||
property color buttonHoverColor: "#2196F3"
|
||||
property color buttonFocusColor: "#2196F3"
|
||||
|
||||
property color viewTextColor: Qt.rgba(0,0,0, 0.54)
|
||||
property color viewBackgroundColor: "#f3f3f3"
|
||||
property color viewHoverColor: "#2196F3"
|
||||
property color viewFocusColor: "#2196F3"
|
||||
|
||||
property color complementaryTextColor: "#f3f3f3"
|
||||
property color complementaryBackgroundColor: Qt.rgba(0,0,0, 0.54)
|
||||
property color complementaryHoverColor: "#2196F3"
|
||||
property color complementaryFocusColor: "#2196F3"
|
||||
}
|
99
mobile-widgets/qml/theme/Units.qml
Normal file
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* Copyright 2015 Marco Martin <mart@kde.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Library General Public License as
|
||||
* published by the Free Software Foundation; either version 2, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Library General Public License for more details
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Window 2.2
|
||||
|
||||
//pragma Singleton
|
||||
|
||||
|
||||
QtObject {
|
||||
id: units
|
||||
|
||||
/**
|
||||
* The fundamental unit of space that should be used for sizes, expressed in pixels.
|
||||
* Given the screen has an accurate DPI settings, it corresponds to a width of
|
||||
* the capital letter M
|
||||
*/
|
||||
property int gridUnit: fontMetrics.height
|
||||
|
||||
/**
|
||||
* units.iconSizes provides access to platform-dependent icon sizing
|
||||
*
|
||||
* The icon sizes provided are normalized for different DPI, so icons
|
||||
* will scale depending on the DPI.
|
||||
*
|
||||
* Icon sizes from KIconLoader, adjusted to devicePixelRatio:
|
||||
* * small
|
||||
* * smallMedium
|
||||
* * medium
|
||||
* * large
|
||||
* * huge
|
||||
* * enormous
|
||||
*
|
||||
* Not devicePixelRation-adjusted::
|
||||
* * desktop
|
||||
*/
|
||||
property QtObject iconSizes: QtObject {
|
||||
property int small: 16 * devicePixelRatio
|
||||
property int smallMedium: 22 * devicePixelRatio
|
||||
property int medium: 32 * devicePixelRatio
|
||||
property int large: 48 * devicePixelRatio
|
||||
property int huge: 64 * devicePixelRatio
|
||||
property int enormous: 128 * devicePixelRatio
|
||||
}
|
||||
|
||||
/**
|
||||
* units.smallSpacing is the amount of spacing that should be used around smaller UI elements,
|
||||
* for example as spacing in Columns. Internally, this size depends on the size of
|
||||
* the default font as rendered on the screen, so it takes user-configured font size and DPI
|
||||
* into account.
|
||||
*/
|
||||
property int smallSpacing: gridUnit/4
|
||||
|
||||
/**
|
||||
* units.largeSpacing is the amount of spacing that should be used inside bigger UI elements,
|
||||
* for example between an icon and the corresponding text. Internally, this size depends on
|
||||
* the size of the default font as rendered on the screen, so it takes user-configured font
|
||||
* size and DPI into account.
|
||||
*/
|
||||
property int largeSpacing: gridUnit
|
||||
|
||||
/**
|
||||
* The ratio between physical and device-independent pixels. This value does not depend on the \
|
||||
* size of the configured font. If you want to take font sizes into account when scaling elements,
|
||||
* use theme.mSize(theme.defaultFont), units.smallSpacing and units.largeSpacing.
|
||||
* The devicePixelRatio follows the definition of "device independent pixel" by Microsoft.
|
||||
*/
|
||||
property real devicePixelRatio: Screen.devicePixelRatio
|
||||
|
||||
/**
|
||||
* units.longDuration should be used for longer, screen-covering animations, for opening and
|
||||
* closing of dialogs and other "not too small" animations
|
||||
*/
|
||||
property int longDuration: 250
|
||||
|
||||
/**
|
||||
* units.shortDuration should be used for short animations, such as accentuating a UI event,
|
||||
* hover events, etc..
|
||||
*/
|
||||
property int shortDuration: 150
|
||||
|
||||
property QtObject fontMetrics: FontMetrics {}
|
||||
}
|
2
mobile-widgets/qml/theme/qmldir
Normal file
|
@ -0,0 +1,2 @@
|
|||
#singleton Units Units.qml
|
||||
#//singleton Theme Theme.qml
|
1078
mobile-widgets/qmlmanager.cpp
Normal file
162
mobile-widgets/qmlmanager.h
Normal file
|
@ -0,0 +1,162 @@
|
|||
#ifndef QMLMANAGER_H
|
||||
#define QMLMANAGER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QScreen>
|
||||
#include <QElapsedTimer>
|
||||
|
||||
#include "core/gpslocation.h"
|
||||
|
||||
class QMLManager : public QObject {
|
||||
Q_OBJECT
|
||||
Q_ENUMS(credentialStatus_t)
|
||||
Q_PROPERTY(QString cloudUserName READ cloudUserName WRITE setCloudUserName NOTIFY cloudUserNameChanged)
|
||||
Q_PROPERTY(QString cloudPassword READ cloudPassword WRITE setCloudPassword NOTIFY cloudPasswordChanged)
|
||||
Q_PROPERTY(QString logText READ logText WRITE setLogText NOTIFY logTextChanged)
|
||||
Q_PROPERTY(bool locationServiceEnabled READ locationServiceEnabled WRITE setLocationServiceEnabled NOTIFY locationServiceEnabledChanged)
|
||||
Q_PROPERTY(int distanceThreshold READ distanceThreshold WRITE setDistanceThreshold NOTIFY distanceThresholdChanged)
|
||||
Q_PROPERTY(int timeThreshold READ timeThreshold WRITE setTimeThreshold NOTIFY timeThresholdChanged)
|
||||
Q_PROPERTY(bool loadFromCloud READ loadFromCloud WRITE setLoadFromCloud NOTIFY loadFromCloudChanged)
|
||||
Q_PROPERTY(QString startPageText READ startPageText WRITE setStartPageText NOTIFY startPageTextChanged)
|
||||
Q_PROPERTY(bool verboseEnabled READ verboseEnabled WRITE setVerboseEnabled NOTIFY verboseEnabledChanged)
|
||||
Q_PROPERTY(credentialStatus_t credentialStatus READ credentialStatus WRITE setCredentialStatus NOTIFY credentialStatusChanged)
|
||||
Q_PROPERTY(int accessingCloud READ accessingCloud WRITE setAccessingCloud NOTIFY accessingCloudChanged)
|
||||
Q_PROPERTY(bool syncToCloud READ syncToCloud WRITE setSyncToCloud NOTIFY syncToCloudChanged)
|
||||
|
||||
public:
|
||||
QMLManager();
|
||||
~QMLManager();
|
||||
|
||||
enum credentialStatus_t {
|
||||
INCOMPLETE,
|
||||
UNKNOWN,
|
||||
INVALID,
|
||||
VALID_EMAIL,
|
||||
VALID
|
||||
};
|
||||
|
||||
static QMLManager *instance();
|
||||
|
||||
QString cloudUserName() const;
|
||||
void setCloudUserName(const QString &cloudUserName);
|
||||
|
||||
QString cloudPassword() const;
|
||||
void setCloudPassword(const QString &cloudPassword);
|
||||
|
||||
bool locationServiceEnabled() const;
|
||||
void setLocationServiceEnabled(bool locationServiceEnable);
|
||||
|
||||
bool verboseEnabled() const;
|
||||
void setVerboseEnabled(bool verboseMode);
|
||||
|
||||
int distanceThreshold() const;
|
||||
void setDistanceThreshold(int distance);
|
||||
|
||||
int timeThreshold() const;
|
||||
void setTimeThreshold(int time);
|
||||
|
||||
bool loadFromCloud() const;
|
||||
void setLoadFromCloud(bool done);
|
||||
void syncLoadFromCloud();
|
||||
|
||||
QString startPageText() const;
|
||||
void setStartPageText(const QString& text);
|
||||
|
||||
credentialStatus_t credentialStatus() const;
|
||||
void setCredentialStatus(const credentialStatus_t value);
|
||||
|
||||
QString logText() const;
|
||||
void setLogText(const QString &logText);
|
||||
|
||||
int accessingCloud() const;
|
||||
void setAccessingCloud(int status);
|
||||
|
||||
bool syncToCloud() const;
|
||||
void setSyncToCloud(bool status);
|
||||
|
||||
typedef void (QMLManager::*execute_function_type)();
|
||||
|
||||
public slots:
|
||||
void applicationStateChanged(Qt::ApplicationState state);
|
||||
void savePreferences();
|
||||
void saveCloudCredentials();
|
||||
void checkCredentialsAndExecute(execute_function_type execute);
|
||||
void tryRetrieveDataFromBackend();
|
||||
void handleError(QNetworkReply::NetworkError nError);
|
||||
void handleSslErrors(const QList<QSslError> &errors);
|
||||
void retrieveUserid();
|
||||
void loadDivesWithValidCredentials();
|
||||
void loadDiveProgress(int percent);
|
||||
void provideAuth(QNetworkReply *reply, QAuthenticator *auth);
|
||||
void commitChanges(QString diveId, QString date, QString location,
|
||||
QString gps, QString duration, QString depth,
|
||||
QString airtemp, QString watertemp, QString suit,
|
||||
QString buddy, QString diveMaster, QString weight, QString notes,
|
||||
QString startpressure, QString endpressure, QString gasmix);
|
||||
|
||||
void saveChanges();
|
||||
void deleteDive(int id);
|
||||
void undoDelete(int id);
|
||||
QString addDive();
|
||||
void addDiveAborted(int id);
|
||||
void applyGpsData();
|
||||
void sendGpsData();
|
||||
void downloadGpsData();
|
||||
void populateGpsData();
|
||||
void clearGpsData();
|
||||
void finishSetup();
|
||||
void openLocalThenRemote(QString url);
|
||||
int getIndex(const QString& diveId);
|
||||
QString getNumber(const QString& diveId);
|
||||
QString getDate(const QString& diveId);
|
||||
QString getCurrentPosition();
|
||||
QString getVersion() const;
|
||||
void deleteGpsFix(quint64 when);
|
||||
void refreshDiveList();
|
||||
void screenChanged(QScreen *screen);
|
||||
qreal lastDevicePixelRatio();
|
||||
void appendTextToLog(const QString &newText);
|
||||
|
||||
private:
|
||||
QString m_cloudUserName;
|
||||
QString m_cloudPassword;
|
||||
QString m_ssrfGpsWebUserid;
|
||||
QString m_startPageText;
|
||||
QString m_logText;
|
||||
bool m_locationServiceEnabled;
|
||||
bool m_verboseEnabled;
|
||||
int m_distanceThreshold;
|
||||
int m_timeThreshold;
|
||||
GpsLocation *locationProvider;
|
||||
bool m_loadFromCloud;
|
||||
static QMLManager *m_instance;
|
||||
QNetworkReply *reply;
|
||||
QNetworkRequest request;
|
||||
struct dive *deletedDive;
|
||||
struct dive_trip *deletedTrip;
|
||||
int m_accessingCloud;
|
||||
bool m_syncToCloud;
|
||||
credentialStatus_t m_credentialStatus;
|
||||
qreal m_lastDevicePixelRatio;
|
||||
QElapsedTimer timer;
|
||||
bool alreadySaving;
|
||||
|
||||
signals:
|
||||
void cloudUserNameChanged();
|
||||
void cloudPasswordChanged();
|
||||
void locationServiceEnabledChanged();
|
||||
void verboseEnabledChanged();
|
||||
void logTextChanged();
|
||||
void timeThresholdChanged();
|
||||
void distanceThresholdChanged();
|
||||
void loadFromCloudChanged();
|
||||
void startPageTextChanged();
|
||||
void credentialStatusChanged();
|
||||
void accessingCloudChanged();
|
||||
void syncToCloudChanged();
|
||||
void sendScreenChanged(QScreen *screen);
|
||||
};
|
||||
|
||||
#endif
|
111
mobile-widgets/qmlprofile.cpp
Normal file
|
@ -0,0 +1,111 @@
|
|||
#include "qmlprofile.h"
|
||||
#include "qmlmanager.h"
|
||||
#include "profile-widget/profilewidget2.h"
|
||||
#include "core/dive.h"
|
||||
#include "core/metrics.h"
|
||||
#include <QTransform>
|
||||
#include <QScreen>
|
||||
|
||||
QMLProfile::QMLProfile(QQuickItem *parent) :
|
||||
QQuickPaintedItem(parent),
|
||||
m_devicePixelRatio(1.0),
|
||||
m_margin(0)
|
||||
{
|
||||
setAntialiasing(true);
|
||||
m_profileWidget = new ProfileWidget2(0);
|
||||
m_profileWidget->setProfileState();
|
||||
m_profileWidget->setPrintMode(true);
|
||||
m_profileWidget->setFontPrintScale(0.8);
|
||||
connect(QMLManager::instance(), &QMLManager::sendScreenChanged, this, &QMLProfile::screenChanged);
|
||||
setDevicePixelRatio(QMLManager::instance()->lastDevicePixelRatio());
|
||||
}
|
||||
|
||||
QMLProfile::~QMLProfile()
|
||||
{
|
||||
m_profileWidget->deleteLater();
|
||||
}
|
||||
|
||||
void QMLProfile::paint(QPainter *painter)
|
||||
{
|
||||
// let's look at the intended size of the content and scale our scene accordingly
|
||||
QRect painterRect = painter->viewport();
|
||||
QRect profileRect = m_profileWidget->viewport()->rect();
|
||||
// qDebug() << "profile viewport and painter viewport" << profileRect << painterRect;
|
||||
qreal sceneSize = 104; // that should give us 2% margin all around (100x100 scene)
|
||||
qreal dprComp = devicePixelRatio() * painterRect.width() / profileRect.width();
|
||||
qreal sx = painterRect.width() / sceneSize / dprComp;
|
||||
qreal sy = painterRect.height() / sceneSize / dprComp;
|
||||
|
||||
// next figure out the weird magic by which we need to shift the painter so the profile is shown
|
||||
int dpr = rint(devicePixelRatio());
|
||||
qreal magicShiftFactor = (dpr == 2 ? 0.25 : (dpr == 3 ? 0.33 : 0.0));
|
||||
|
||||
// now set up the transformations scale the profile and
|
||||
// shift the painter (taking its existing transformation into account)
|
||||
QTransform profileTransform = QTransform();
|
||||
profileTransform.scale(sx, sy);
|
||||
QTransform painterTransform = painter->transform();
|
||||
painterTransform.translate(-painterRect.width() * magicShiftFactor ,-painterRect.height() * magicShiftFactor);
|
||||
|
||||
#if PROFILE_SCALING_DEBUG
|
||||
// some debugging messages to help adjust this in case the magic above is insufficient
|
||||
QMLManager::instance()->appendTextToLog(QString("dpr %1 profile viewport %2 %3 painter viewport %4 %5").arg(dpr).arg(profileRect.width()).arg(profileRect.height())
|
||||
.arg(painterRect.width()).arg(painterRect.height()));
|
||||
QMLManager::instance()->appendTextToLog(QString("profile matrix %1 %2 %3 %4 %5 %6 %7 %8 %9").arg(profileTransform.m11()).arg(profileTransform.m12()).arg(profileTransform.m13())
|
||||
.arg(profileTransform.m21()).arg(profileTransform.m22()).arg(profileTransform.m23())
|
||||
.arg(profileTransform.m31()).arg(profileTransform.m32()).arg(profileTransform.m33()));
|
||||
QMLManager::instance()->appendTextToLog(QString("painter matrix %1 %2 %3 %4 %5 %6 %7 %8 %9").arg(painterTransform.m11()).arg(painterTransform.m12()).arg(painterTransform.m13())
|
||||
.arg(painterTransform.m21()).arg(painterTransform.m22()).arg(painterTransform.m23())
|
||||
.arg(painterTransform.m31()).arg(painterTransform.m32()).arg(painterTransform.m33()));
|
||||
qDebug() << "profile scaled by" << profileTransform.m11() << profileTransform.m22() << "and translated by" << profileTransform.m31() << profileTransform.m32();
|
||||
qDebug() << "exist profile transform" << m_profileWidget->transform() << "painter transform" << painter->transform();
|
||||
#endif
|
||||
// apply the transformation
|
||||
painter->setTransform(painterTransform);
|
||||
m_profileWidget->setTransform(profileTransform);
|
||||
|
||||
// finally, render the profile
|
||||
m_profileWidget->render(painter);
|
||||
}
|
||||
|
||||
void QMLProfile::setMargin(int margin)
|
||||
{
|
||||
m_margin = margin;
|
||||
}
|
||||
|
||||
QString QMLProfile::diveId() const
|
||||
{
|
||||
return m_diveId;
|
||||
}
|
||||
|
||||
void QMLProfile::setDiveId(const QString &diveId)
|
||||
{
|
||||
m_diveId = diveId;
|
||||
struct dive *d = get_dive_by_uniq_id(m_diveId.toInt());
|
||||
if (m_diveId.toInt() < 1)
|
||||
return;
|
||||
if (!d)
|
||||
return;
|
||||
qDebug() << "setDiveId called with valid dive" << d->number;
|
||||
m_profileWidget->plotDive(d, true);
|
||||
}
|
||||
|
||||
qreal QMLProfile::devicePixelRatio() const
|
||||
{
|
||||
return m_devicePixelRatio;
|
||||
}
|
||||
|
||||
void QMLProfile::setDevicePixelRatio(qreal dpr)
|
||||
{
|
||||
if (dpr != m_devicePixelRatio) {
|
||||
m_devicePixelRatio = dpr;
|
||||
m_profileWidget->setFontPrintScale(0.8 * dpr);
|
||||
updateDevicePixelRatio(dpr);
|
||||
emit devicePixelRatioChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void QMLProfile::screenChanged(QScreen *screen)
|
||||
{
|
||||
setDevicePixelRatio(screen->devicePixelRatio());
|
||||
}
|
40
mobile-widgets/qmlprofile.h
Normal file
|
@ -0,0 +1,40 @@
|
|||
#ifndef QMLPROFILE_H
|
||||
#define QMLPROFILE_H
|
||||
|
||||
#include <QQuickPaintedItem>
|
||||
|
||||
class ProfileWidget2;
|
||||
|
||||
class QMLProfile : public QQuickPaintedItem
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString diveId READ diveId WRITE setDiveId NOTIFY diveIdChanged)
|
||||
Q_PROPERTY(qreal devicePixelRatio READ devicePixelRatio WRITE setDevicePixelRatio NOTIFY devicePixelRatioChanged)
|
||||
|
||||
public:
|
||||
explicit QMLProfile(QQuickItem *parent = 0);
|
||||
virtual ~QMLProfile();
|
||||
|
||||
void paint(QPainter *painter);
|
||||
|
||||
QString diveId() const;
|
||||
void setDiveId(const QString &diveId);
|
||||
qreal devicePixelRatio() const;
|
||||
void setDevicePixelRatio(qreal dpr);
|
||||
|
||||
public slots:
|
||||
void setMargin(int margin);
|
||||
void screenChanged(QScreen *screen);
|
||||
private:
|
||||
QString m_diveId;
|
||||
qreal m_devicePixelRatio;
|
||||
int m_margin;
|
||||
ProfileWidget2 *m_profileWidget;
|
||||
|
||||
signals:
|
||||
void rightAlignedChanged();
|
||||
void diveIdChanged();
|
||||
void devicePixelRatioChanged();
|
||||
};
|
||||
|
||||
#endif // QMLPROFILE_H
|