diff options
author | Dirk Hohndel <dirk@hohndel.org> | 2016-04-04 22:02:03 -0700 |
---|---|---|
committer | Dirk Hohndel <dirk@hohndel.org> | 2016-04-04 22:33:58 -0700 |
commit | 7be962bfc2879a72c32ff67518731347dcdff6de (patch) | |
tree | d05bf7ab234a448ee37a15b608e2b939f2285d07 /mobile-widgets/qml | |
parent | 2d760a7bff71c46c5aeba37c40d236ea16eefea2 (diff) | |
download | subsurface-7be962bfc2879a72c32ff67518731347dcdff6de.tar.gz |
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>
Diffstat (limited to 'mobile-widgets/qml')
27 files changed, 2432 insertions, 0 deletions
diff --git a/mobile-widgets/qml/About.qml b/mobile-widgets/qml/About.qml new file mode 100644 index 000000000..b1ca6e6bc --- /dev/null +++ b/mobile-widgets/qml/About.qml @@ -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 + } + } +} diff --git a/mobile-widgets/qml/CloudCredentials.qml b/mobile-widgets/qml/CloudCredentials.qml new file mode 100644 index 000000000..aa7c57651 --- /dev/null +++ b/mobile-widgets/qml/CloudCredentials.qml @@ -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 } + } +} diff --git a/mobile-widgets/qml/DiveDetails.qml b/mobile-widgets/qml/DiveDetails.qml new file mode 100644 index 000000000..108833470 --- /dev/null +++ b/mobile-widgets/qml/DiveDetails.qml @@ -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 + } + } + } +} diff --git a/mobile-widgets/qml/DiveDetailsEdit.qml b/mobile-widgets/qml/DiveDetailsEdit.qml new file mode 100644 index 000000000..e4338b3b8 --- /dev/null +++ b/mobile-widgets/qml/DiveDetailsEdit.qml @@ -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 + } + } +} diff --git a/mobile-widgets/qml/DiveDetailsView.qml b/mobile-widgets/qml/DiveDetailsView.qml new file mode 100644 index 000000000..ef1dc5605 --- /dev/null +++ b/mobile-widgets/qml/DiveDetailsView.qml @@ -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(); + } + } +} diff --git a/mobile-widgets/qml/DiveList.qml b/mobile-widgets/qml/DiveList.qml new file mode 100644 index 000000000..95af9a973 --- /dev/null +++ b/mobile-widgets/qml/DiveList.qml @@ -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; + } + } +} diff --git a/mobile-widgets/qml/DownloadFromDiveComputer.qml b/mobile-widgets/qml/DownloadFromDiveComputer.qml new file mode 100644 index 000000000..a062ffaa0 --- /dev/null +++ b/mobile-widgets/qml/DownloadFromDiveComputer.qml @@ -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:"" + } + } + } +} diff --git a/mobile-widgets/qml/GpsList.qml b/mobile-widgets/qml/GpsList.qml new file mode 100644 index 000000000..6903acd80 --- /dev/null +++ b/mobile-widgets/qml/GpsList.qml @@ -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" + } + } +} diff --git a/mobile-widgets/qml/Log.qml b/mobile-widgets/qml/Log.qml new file mode 100644 index 000000000..d617901de --- /dev/null +++ b/mobile-widgets/qml/Log.qml @@ -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 + } + } +} diff --git a/mobile-widgets/qml/Preferences.qml b/mobile-widgets/qml/Preferences.qml new file mode 100644 index 000000000..3ec96d198 --- /dev/null +++ b/mobile-widgets/qml/Preferences.qml @@ -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 + } + } +} diff --git a/mobile-widgets/qml/StartPage.qml b/mobile-widgets/qml/StartPage.qml new file mode 100644 index 000000000..2d70cfcb3 --- /dev/null +++ b/mobile-widgets/qml/StartPage.qml @@ -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 + } +} diff --git a/mobile-widgets/qml/SubsurfaceButton.qml b/mobile-widgets/qml/SubsurfaceButton.qml new file mode 100644 index 000000000..174d44659 --- /dev/null +++ b/mobile-widgets/qml/SubsurfaceButton.qml @@ -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 + } + } +} diff --git a/mobile-widgets/qml/TextButton.qml b/mobile-widgets/qml/TextButton.qml new file mode 100644 index 000000000..3e5a36735 --- /dev/null +++ b/mobile-widgets/qml/TextButton.qml @@ -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 } + } +} diff --git a/mobile-widgets/qml/ThemeTest.qml b/mobile-widgets/qml/ThemeTest.qml new file mode 100644 index 000000000..c0916aea0 --- /dev/null +++ b/mobile-widgets/qml/ThemeTest.qml @@ -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 + } + } +} diff --git a/mobile-widgets/qml/TopBar.qml b/mobile-widgets/qml/TopBar.qml new file mode 100644 index 000000000..024b818b0 --- /dev/null +++ b/mobile-widgets/qml/TopBar.qml @@ -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() + } + } + } +} diff --git a/mobile-widgets/qml/dive.jpg b/mobile-widgets/qml/dive.jpg Binary files differnew file mode 100644 index 000000000..56445648a --- /dev/null +++ b/mobile-widgets/qml/dive.jpg diff --git a/mobile-widgets/qml/icons/context-menu.png b/mobile-widgets/qml/icons/context-menu.png Binary files differnew file mode 100644 index 000000000..df34cfd4f --- /dev/null +++ b/mobile-widgets/qml/icons/context-menu.png diff --git a/mobile-widgets/qml/icons/context-menu.svg b/mobile-widgets/qml/icons/context-menu.svg new file mode 100644 index 000000000..e0750c57e --- /dev/null +++ b/mobile-widgets/qml/icons/context-menu.svg @@ -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>
\ No newline at end of file diff --git a/mobile-widgets/qml/icons/main-menu.png b/mobile-widgets/qml/icons/main-menu.png Binary files differnew file mode 100644 index 000000000..20729b8f5 --- /dev/null +++ b/mobile-widgets/qml/icons/main-menu.png diff --git a/mobile-widgets/qml/icons/main-menu.svg b/mobile-widgets/qml/icons/main-menu.svg new file mode 100644 index 000000000..1e89193f5 --- /dev/null +++ b/mobile-widgets/qml/icons/main-menu.svg @@ -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> diff --git a/mobile-widgets/qml/icons/menu-back.png b/mobile-widgets/qml/icons/menu-back.png Binary files differnew file mode 100644 index 000000000..dc96b7728 --- /dev/null +++ b/mobile-widgets/qml/icons/menu-back.png diff --git a/mobile-widgets/qml/icons/menu-edit.png b/mobile-widgets/qml/icons/menu-edit.png Binary files differnew file mode 100644 index 000000000..ea7dd055a --- /dev/null +++ b/mobile-widgets/qml/icons/menu-edit.png diff --git a/mobile-widgets/qml/main.qml b/mobile-widgets/qml/main.qml new file mode 100644 index 000000000..f4f6ea28b --- /dev/null +++ b/mobile-widgets/qml/main.qml @@ -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 + } + } +} diff --git a/mobile-widgets/qml/mobile-resources.qrc b/mobile-widgets/qml/mobile-resources.qrc new file mode 100644 index 000000000..e6c1fba65 --- /dev/null +++ b/mobile-widgets/qml/mobile-resources.qrc @@ -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> diff --git a/mobile-widgets/qml/theme/Theme.qml b/mobile-widgets/qml/theme/Theme.qml new file mode 100644 index 000000000..2c51ae00f --- /dev/null +++ b/mobile-widgets/qml/theme/Theme.qml @@ -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" +} diff --git a/mobile-widgets/qml/theme/Units.qml b/mobile-widgets/qml/theme/Units.qml new file mode 100644 index 000000000..7cfa5c23b --- /dev/null +++ b/mobile-widgets/qml/theme/Units.qml @@ -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 {} +} diff --git a/mobile-widgets/qml/theme/qmldir b/mobile-widgets/qml/theme/qmldir new file mode 100644 index 000000000..c654dbad6 --- /dev/null +++ b/mobile-widgets/qml/theme/qmldir @@ -0,0 +1,2 @@ +#singleton Units Units.qml +#//singleton Theme Theme.qml |