mirror of
https://github.com/subsurface/subsurface.git
synced 2024-12-05 00:21:29 +00:00
4b0fe3bc38
While the startup flow should make it obvious when a user is not correctly logged into the cloud, we still do see fairly frequent situations where a user has an incorrect password on a mobile device and is confused about why their data isn't syncing with their PC. Now this is clearly shown in the main menu. Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
954 lines
28 KiB
QML
954 lines
28 KiB
QML
// SPDX-License-Identifier: GPL-2.0
|
|
import QtQuick 2.6
|
|
import QtQuick.Controls 2.0
|
|
import QtQuick.Controls.Material 2.1
|
|
import QtQuick.Window 2.2
|
|
import QtQuick.Dialogs 1.2
|
|
import QtQuick.Layouts 1.2
|
|
import QtQuick.Window 2.2
|
|
import org.subsurfacedivelog.mobile 1.0
|
|
import org.kde.kirigami 2.4 as Kirigami
|
|
import QtGraphicalEffects 1.0
|
|
import QtQuick.Templates 2.0 as QtQuickTemplates
|
|
|
|
Kirigami.ApplicationWindow {
|
|
id: rootItem
|
|
title: qsTr("Subsurface-mobile")
|
|
reachableModeEnabled: false // while it's a good idea, it seems to confuse more than help
|
|
wideScreen: false // workaround for probably Kirigami bug. See commits.
|
|
|
|
// ensure we get all information on screen rotation
|
|
Screen.orientationUpdateMask: Qt.LandscapeOrientation | Qt.PortraitOrientation | Qt.InvertedLandscapeOrientation | Qt.InvertedPortraitOrientation
|
|
|
|
// the documentation claims that the ApplicationWindow should pick up the font set on
|
|
// the C++ side. But as a matter of fact, it doesn't, unless you add this line:
|
|
font: Qt.application.font
|
|
background: Rectangle { color: subsurfaceTheme.backgroundColor }
|
|
|
|
// we want to use our own colors for Kirigami, so let's define our colorset
|
|
Kirigami.Theme.inherit: false
|
|
Kirigami.Theme.colorSet: Kirigami.Theme.Button
|
|
Kirigami.Theme.backgroundColor: subsurfaceTheme.backgroundColor
|
|
Kirigami.Theme.textColor: subsurfaceTheme.textColor
|
|
|
|
// next setup the tab bar on top
|
|
pageStack.globalToolBar.style: Kirigami.ApplicationHeaderStyle.Breadcrumb
|
|
pageStack.globalToolBar.showNavigationButtons: (Kirigami.ApplicationHeaderStyle.ShowBackButton | Kirigami.ApplicationHeaderStyle.ShowForwardButton)
|
|
pageStack.globalToolBar.minimumHeight: 0
|
|
pageStack.globalToolBar.preferredHeight: Math.round(Kirigami.Units.gridUnit * (Qt.platform.os == "ios" ? 2 : 1.5))
|
|
pageStack.globalToolBar.maximumHeight: Kirigami.Units.gridUnit * 2
|
|
|
|
property alias notificationText: manager.notificationText
|
|
property alias pluggedInDeviceName: manager.pluggedInDeviceName
|
|
property alias defaultCylinderIndex: settingsWindow.defaultCylinderIndex
|
|
property bool filterToggle: false
|
|
property string filterPattern: ""
|
|
property int colWidth: undefined
|
|
|
|
// signal that the profile (and possibly other code) listens to so they
|
|
// can redraw if settings are changed
|
|
signal settingsChanged()
|
|
|
|
onNotificationTextChanged: {
|
|
// once the app is fully initialized and the UI is running, we use passive
|
|
// notifications to show the notification text, but during initialization
|
|
// we instead dump the information into the textBlock below
|
|
if (initialized) {
|
|
if (notificationText !== "") {
|
|
var actionEnd = notificationText.indexOf("]")
|
|
if (notificationText.startsWith("[") && actionEnd !== -1) {
|
|
// we have a notification text that starts with our special syntax to indication
|
|
// an action that the user can take (the actual action is always opening the context drawer
|
|
// so the action text should always be something that can then be found in the context drawer)
|
|
showPassiveNotification(notificationText.substring(actionEnd + 1), 5000, notificationText.substring(1,actionEnd),
|
|
function() { contextDrawer.open() })
|
|
} else {
|
|
showPassiveNotification(notificationText, 5000)
|
|
}
|
|
}
|
|
} else {
|
|
textBlock.text = textBlock.text + "\n" + notificationText
|
|
}
|
|
}
|
|
visible: false
|
|
|
|
BusyIndicator {
|
|
id: busy
|
|
running: false
|
|
height: 6 * Kirigami.Units.gridUnit
|
|
width: 6 * Kirigami.Units.gridUnit
|
|
anchors.centerIn: parent
|
|
}
|
|
|
|
function showBusy(msg) {
|
|
if (msg !== undefined && msg !== "")
|
|
showPassiveNotification(msg, 15000) // show for 15 seconds
|
|
busy.running = true
|
|
}
|
|
|
|
function hideBusy() {
|
|
busy.running = false
|
|
// hiding notifications is no longer supported???
|
|
// hidePassiveNotification()
|
|
}
|
|
|
|
function returnTopPage() {
|
|
for (var i=pageStack.depth; i>1; i--) {
|
|
pageStack.pop()
|
|
}
|
|
if (pageStack.currentItem !== diveList) {
|
|
showDiveList()
|
|
}
|
|
detailsWindow.endEditMode()
|
|
}
|
|
|
|
function scrollToTop() {
|
|
diveList.scrollToTop()
|
|
}
|
|
|
|
function showPage(page) {
|
|
if (page === statistics) {
|
|
manager.appendTextToLog("switching to statistics page, clearing out stack")
|
|
pageStack.clear()
|
|
}
|
|
if (pageStack.currentItem === statistics) {
|
|
manager.appendTextToLog("switching away from statistics page, clearing out stack")
|
|
pageStack.clear()
|
|
}
|
|
|
|
if (page !== mapPage)
|
|
hackToOpenMap = 0 // we really want a different page
|
|
if (globalDrawer.drawerOpen)
|
|
globalDrawer.close()
|
|
var i=pageIndex(page)
|
|
if (i === -1)
|
|
pageStack.push(page)
|
|
else
|
|
pageStack.currentIndex = i
|
|
manager.appendTextToLog("switched to page " + page.title)
|
|
}
|
|
|
|
function showMap() {
|
|
showPage(mapPage)
|
|
}
|
|
|
|
function showDiveList() {
|
|
showPage(diveList)
|
|
}
|
|
|
|
function pageIndex(pageToFind) {
|
|
if (pageStack.contentItem !== null) {
|
|
for (var i = 0; i < pageStack.contentItem.contentChildren.length; i++) {
|
|
if (pageStack.contentItem.contentChildren[i] === pageToFind)
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
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.buddyModel = manager.buddyList
|
|
detailsWindow.buddyIndex = -1
|
|
detailsWindow.buddyText = ""
|
|
detailsWindow.depth = ""
|
|
detailsWindow.diveguideModel = manager.diveguideList
|
|
detailsWindow.diveguideIndex = -1
|
|
detailsWindow.diveguideText = ""
|
|
detailsWindow.notes = ""
|
|
detailsWindow.location = ""
|
|
detailsWindow.gps = ""
|
|
detailsWindow.duration = ""
|
|
detailsWindow.suitModel = manager.suitList
|
|
detailsWindow.suitIndex = -1
|
|
detailsWindow.suitText = ""
|
|
detailsWindow.cylinderModel0 = manager.cylinderListInit
|
|
detailsWindow.cylinderModel1 = manager.cylinderListInit
|
|
detailsWindow.cylinderModel2 = manager.cylinderListInit
|
|
detailsWindow.cylinderModel3 = manager.cylinderListInit
|
|
detailsWindow.cylinderModel4 = manager.cylinderListInit
|
|
detailsWindow.cylinderIndex0 = PrefEquipment.default_cylinder == "" ? -1 : detailsWindow.cylinderModel0.indexOf(PrefEquipment.default_cylinder)
|
|
detailsWindow.usedCyl = ["",]
|
|
detailsWindow.weight = ""
|
|
detailsWindow.usedGas = []
|
|
detailsWindow.startpressure = []
|
|
detailsWindow.endpressure = []
|
|
showPage(detailsWindow)
|
|
}
|
|
|
|
contextDrawer: Kirigami.ContextDrawer {
|
|
id: contextDrawer
|
|
closePolicy: QtQuickTemplates.Popup.CloseOnPressOutside
|
|
}
|
|
|
|
globalDrawer: Kirigami.GlobalDrawer {
|
|
id: globalDrawer
|
|
height: rootItem.height
|
|
rightPadding: 0
|
|
enabled: (Backend.cloud_verification_status === Enums.CS_NOCLOUD ||
|
|
Backend.cloud_verification_status === Enums.CS_VERIFIED)
|
|
topContent: Image {
|
|
source: "qrc:/qml/icons/dive.jpg"
|
|
// it's a 4x3 image, but clip if it takes too much space (making sure the text fits)
|
|
property int myHeight: Math.min(Math.max(rootItem.height * 0.3, textblock.height + Kirigami.Units.largeSpacing), parent.width * 0.75)
|
|
Layout.fillWidth: true
|
|
Layout.maximumHeight: myHeight
|
|
sourceSize.width: parent.width
|
|
fillMode: Image.PreserveAspectCrop
|
|
LinearGradient {
|
|
anchors {
|
|
left: parent.left
|
|
right: parent.right
|
|
top: parent.top
|
|
}
|
|
height: Math.min(textblock.height * 2, parent.myHeight)
|
|
start: Qt.point(0, 0)
|
|
end: Qt.point(0, height)
|
|
gradient: Gradient {
|
|
GradientStop {
|
|
position: 0.0
|
|
color: Qt.rgba(0, 0, 0, 0.8)
|
|
}
|
|
GradientStop {
|
|
position: 1.0
|
|
color: "transparent"
|
|
}
|
|
}
|
|
}
|
|
ColumnLayout {
|
|
id: textblock
|
|
anchors {
|
|
left: parent.left
|
|
top: parent.top
|
|
}
|
|
RowLayout {
|
|
width: Math.min(implicitWidth, parent.width)
|
|
Layout.margins: Kirigami.Units.smallSpacing
|
|
Image {
|
|
source: "qrc:/qml/subsurface-mobile-icon.png"
|
|
fillMode: Image.PreserveAspectCrop
|
|
sourceSize.width: Kirigami.Units.iconSizes.large
|
|
width: Kirigami.Units.iconSizes.large
|
|
Layout.margins: Kirigami.Units.smallSpacing
|
|
}
|
|
Kirigami.Heading {
|
|
Layout.fillWidth: true
|
|
visible: text.length > 0
|
|
level: 1
|
|
color: "white"
|
|
text: "Subsurface"
|
|
wrapMode: Text.NoWrap
|
|
elide: Text.ElideRight
|
|
font.weight: Font.Normal
|
|
Layout.margins: Kirigami.Units.smallSpacing
|
|
}
|
|
}
|
|
RowLayout {
|
|
Layout.margins: Kirigami.Units.smallSpacing
|
|
Kirigami.Heading {
|
|
Layout.fillWidth: true
|
|
visible: text.length > 0
|
|
level: 3
|
|
color: "white"
|
|
text: PrefCloudStorage.cloud_storage_email
|
|
wrapMode: Text.NoWrap
|
|
elide: Text.ElideRight
|
|
font.weight: Font.Normal
|
|
}
|
|
}
|
|
RowLayout {
|
|
Layout.leftMargin: Kirigami.Units.smallSpacing
|
|
Layout.topMargin: 0
|
|
Kirigami.Heading {
|
|
Layout.fillWidth: true
|
|
Layout.topMargin: 0
|
|
visible: text.length > 0
|
|
level: 5
|
|
color: "white"
|
|
text: manager.passwordState
|
|
wrapMode: Text.NoWrap
|
|
elide: Text.ElideRight
|
|
font.weight: Font.Normal
|
|
}
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.leftMargin: Kirigami.Units.smallSpacing
|
|
Layout.topMargin: 0
|
|
Kirigami.Heading {
|
|
Layout.fillWidth: true
|
|
Layout.topMargin: 0
|
|
visible: text.length > 0
|
|
level: 5
|
|
color: "white"
|
|
text: manager.syncState
|
|
wrapMode: Text.NoWrap
|
|
elide: Text.ElideRight
|
|
font.weight: Font.Normal
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
resetMenuOnTriggered: false
|
|
|
|
actions: [
|
|
Kirigami.Action {
|
|
icon {
|
|
name: ":/icons/ic_home.svg"
|
|
}
|
|
text: qsTr("Dive list")
|
|
onTriggered: {
|
|
manager.appendTextToLog("requested dive list with credential status " + Backend.cloud_verification_status)
|
|
returnTopPage()
|
|
globalDrawer.close()
|
|
}
|
|
},
|
|
Kirigami.Action {
|
|
icon {
|
|
name: ":/icons/ic_sync.svg"
|
|
}
|
|
text: qsTr("Dive management")
|
|
Kirigami.Action {
|
|
icon {
|
|
name: ":/go-previous-symbolic"
|
|
}
|
|
text: qsTr("Back")
|
|
onTriggered: globalDrawer.pop()
|
|
}
|
|
Kirigami.Action {
|
|
icon {
|
|
name: ":/icons/ic_add.svg"
|
|
}
|
|
text: qsTr("Add dive manually")
|
|
onTriggered: {
|
|
globalDrawer.close()
|
|
returnTopPage() // otherwise odd things happen with the page stack
|
|
startAddDive()
|
|
}
|
|
}
|
|
Kirigami.Action {
|
|
// this of course assumes a white background - theming means this needs to change again
|
|
icon {
|
|
name: ":/icons/downloadDC-black.svg"
|
|
}
|
|
text: qsTr("Download from DC")
|
|
enabled: true
|
|
onTriggered: {
|
|
globalDrawer.close()
|
|
downloadFromDc.dcImportModel.clearTable()
|
|
showPage(downloadFromDc)
|
|
}
|
|
}
|
|
Kirigami.Action {
|
|
icon {
|
|
name: ":/icons/cloud_sync.svg"
|
|
}
|
|
text: qsTr("Manual sync with cloud")
|
|
visible: Backend.cloud_verification_status !== Enums.CS_NOCLOUD
|
|
onTriggered: {
|
|
globalDrawer.close()
|
|
detailsWindow.endEditMode()
|
|
manager.saveChangesCloud(true);
|
|
showPassiveNotification(qsTr("Completed manual sync with cloud\n") + manager.syncState)
|
|
globalDrawer.close()
|
|
}
|
|
}
|
|
Kirigami.Action {
|
|
icon {
|
|
name: PrefCloudStorage.cloud_auto_sync ? ":/icons/ic_cloud_done.svg" : ":/icons/ic_cloud_off.svg"
|
|
}
|
|
text: (PrefCloudStorage.cloud_auto_sync ? "\u2611 " : "\u2610 ") + qsTr("Auto cloud sync")
|
|
visible: Backend.cloud_verification_status !== Enums.CS_NOCLOUD
|
|
onTriggered: {
|
|
PrefCloudStorage.cloud_auto_sync = !PrefCloudStorage.cloud_auto_sync
|
|
manager.setGitLocalOnly(!PrefCloudStorage.cloud_auto_sync)
|
|
if (!PrefCloudStorage.cloud_auto_sync) {
|
|
showPassiveNotification(qsTr("Turning off automatic sync to cloud causes all data to only be \
|
|
stored locally. This can be very useful in situations with limited or no network access. Please choose 'Manual sync with cloud' \
|
|
if you have network connectivity and want to sync your data to cloud storage."), 10000)
|
|
}
|
|
}
|
|
}
|
|
Kirigami.Action {
|
|
icon {
|
|
name: ":/icons/sigma.svg"
|
|
}
|
|
text: qsTr("Dive summary")
|
|
onTriggered: {
|
|
globalDrawer.close()
|
|
showPage(diveSummaryWindow)
|
|
detailsWindow.endEditMode()
|
|
}
|
|
}
|
|
Kirigami.Action {
|
|
icon {
|
|
name: ":/icons/ic_cloud_upload.svg"
|
|
}
|
|
text: qsTr("Export")
|
|
onTriggered: {
|
|
globalDrawer.close()
|
|
showPage(exportWindow)
|
|
detailsWindow.endEditMode()
|
|
}
|
|
}
|
|
},
|
|
Kirigami.Action {
|
|
icon {
|
|
name: ":/icons/map-globe.svg"
|
|
}
|
|
text: qsTr("Location")
|
|
visible: true
|
|
Kirigami.Action {
|
|
icon {
|
|
name: ":/go-previous-symbolic"
|
|
}
|
|
text: qsTr("Back")
|
|
onTriggered: globalDrawer.pop()
|
|
}
|
|
Kirigami.Action {
|
|
icon {
|
|
name: ":/icons/map-globe.svg"
|
|
}
|
|
text: mapPage.title
|
|
onTriggered: {
|
|
showMap()
|
|
}
|
|
}
|
|
},
|
|
Kirigami.Action {
|
|
icon {
|
|
name: ":/icons/office-chart-bar-stacked.svg"
|
|
}
|
|
|
|
text: qsTr("Statistics")
|
|
onTriggered: {
|
|
showPage(statistics)
|
|
}
|
|
},
|
|
Kirigami.Action {
|
|
icon {
|
|
name: ":/icons/ic_settings.svg"
|
|
}
|
|
text: qsTr("Settings")
|
|
onTriggered: {
|
|
globalDrawer.close()
|
|
settingsWindow.defaultCylinderModel = manager.defaultCylinderListInit
|
|
PrefEquipment.default_cylinder === "" ? defaultCylinderIndex = "-1" : defaultCylinderIndex = settingsWindow.defaultCylinderModel.indexOf(PrefEquipment.default_cylinder)
|
|
showPage(settingsWindow)
|
|
detailsWindow.endEditMode()
|
|
}
|
|
},
|
|
Kirigami.Action {
|
|
icon {
|
|
name: ":/icons/ic_help_outline.svg"
|
|
}
|
|
text: qsTr("Help")
|
|
Kirigami.Action {
|
|
icon {
|
|
name: ":/go-previous-symbolic"
|
|
}
|
|
text: qsTr("Back")
|
|
onTriggered: globalDrawer.pop()
|
|
}
|
|
Kirigami.Action {
|
|
icon {
|
|
name: ":/icons/ic_info_outline.svg"
|
|
}
|
|
text: qsTr("About")
|
|
onTriggered: {
|
|
globalDrawer.close()
|
|
showPage(aboutWindow)
|
|
detailsWindow.endEditMode()
|
|
}
|
|
}
|
|
Kirigami.Action {
|
|
icon {
|
|
name: ":/icons/ic_help_outline.svg"
|
|
}
|
|
text: qsTr("Show user manual")
|
|
onTriggered: {
|
|
Qt.openUrlExternally("https://www.subsurface-divelog.org/subsurface-mobile-user-manual/")
|
|
}
|
|
}
|
|
Kirigami.Action {
|
|
icon {
|
|
name: ":/icons/recycle.svg"
|
|
}
|
|
text: qsTr("Contribute to Subsurface")
|
|
onTriggered: {
|
|
Qt.openUrlExternally("https://www.subsurface-divelog.org/contribute/")
|
|
}
|
|
}
|
|
Kirigami.Action {
|
|
icon {
|
|
name: ":/icons/contact_support.svg"
|
|
}
|
|
text: qsTr("Ask for support")
|
|
onTriggered: {
|
|
if (!manager.createSupportEmail()) {
|
|
manager.copyAppLogToClipboard()
|
|
showPassiveNotification(qsTr("failed to open email client, please manually create support email to support@subsurface-divelog.org - the logs have been copied to the clipboard and can be pasted into that email."), 6000)
|
|
} else {
|
|
globalDrawer.close()
|
|
}
|
|
}
|
|
}
|
|
Kirigami.Action{
|
|
icon {
|
|
name: ":/icons/account_circle.svg"
|
|
}
|
|
text: qsTr("Reset forgotten Subsurface Cloud password")
|
|
onTriggered: {
|
|
Qt.openUrlExternally("https://cloud.subsurface-divelog.org/passwordreset")
|
|
globalDrawer.close()
|
|
}
|
|
}
|
|
},
|
|
Kirigami.Action {
|
|
icon {
|
|
name: ":/icons/ic_adb.svg"
|
|
}
|
|
text: qsTr("Developer")
|
|
visible: PrefDisplay.show_developer
|
|
Kirigami.Action {
|
|
icon {
|
|
name: ":/go-previous-symbolic"
|
|
}
|
|
text: qsTr("Back")
|
|
onTriggered: globalDrawer.pop()
|
|
}
|
|
Kirigami.Action {
|
|
text: qsTr("App log")
|
|
onTriggered: {
|
|
globalDrawer.close()
|
|
showPage(logWindow)
|
|
}
|
|
}
|
|
Kirigami.Action {
|
|
text: qsTr("Test busy indicator (toggle)")
|
|
onTriggered: {
|
|
if (busy.running) {
|
|
hideBusy()
|
|
} else {
|
|
showBusy()
|
|
}
|
|
}
|
|
}
|
|
Kirigami.Action {
|
|
text: qsTr("Test notification text")
|
|
onTriggered: {
|
|
showPassiveNotification(qsTr("Test notification text"), 5000)
|
|
}
|
|
}
|
|
Kirigami.Action {
|
|
text: qsTr("Theme information")
|
|
onTriggered: {
|
|
globalDrawer.close()
|
|
showPage(themetest)
|
|
}
|
|
}
|
|
|
|
Kirigami.Action {
|
|
text: qsTr("Enable verbose logging (currently: %1)").arg(manager.verboseEnabled)
|
|
onTriggered: {
|
|
showPassiveNotification(qsTr("Not persistent"), 3000)
|
|
globalDrawer.close()
|
|
manager.verboseEnabled = true
|
|
}
|
|
}
|
|
|
|
Kirigami.Action {
|
|
text: qsTr("Access local cloud cache dirs")
|
|
onTriggered: {
|
|
globalDrawer.close()
|
|
showPage(recoverCache)
|
|
}
|
|
}
|
|
}
|
|
] // end actions
|
|
}
|
|
|
|
property double regularFontsize: subsurfaceTheme.regularPointSize
|
|
|
|
FontMetrics {
|
|
id: fontMetrics
|
|
font.pointSize: regularFontsize
|
|
}
|
|
|
|
onRegularFontsizeChanged: {
|
|
manager.appendTextToLog("regular font size changed to " + regularFontsize)
|
|
rootItem.font.pointSize = regularFontsize
|
|
}
|
|
|
|
function setupUnits() {
|
|
// since Kirigami was initially instantiated, the font size may have
|
|
// changed, so recalculate the gridUnit
|
|
var kirigamiGridUnit = fontMetrics.height
|
|
|
|
// some screens are too narrow for Subsurface-mobile to render well
|
|
// things don't look greate with fewer than 21 gridUnits in a row
|
|
var numColumns = Math.max(Math.floor(rootItem.width / (21 * kirigamiGridUnit)), 1)
|
|
if (Screen.primaryOrientation === Qt.PortraitOrientation && PrefDisplay.singleColumnPortrait) {
|
|
manager.appendTextToLog("show only one column in portrait mode");
|
|
numColumns = 1;
|
|
}
|
|
rootItem.colWidth = numColumns > 1 ? Math.floor(rootItem.width / numColumns) : rootItem.width;
|
|
|
|
// If we can't fit 21 gridUnits into a line, let the user know and suggest using a smaller font
|
|
var widthInGridUnits = Math.floor(rootItem.colWidth / kirigamiGridUnit)
|
|
if (widthInGridUnits < 21) {
|
|
showPassiveNotification(qsTr("Font size likely too big for the display, switching to smaller font suggested"), 3000)
|
|
}
|
|
manager.appendTextToLog(numColumns + " columns with column width of " + rootItem.colWidth)
|
|
manager.appendTextToLog("width in Grid Units " + widthInGridUnits + " original gridUnit " + Kirigami.Units.gridUnit + " now " + kirigamiGridUnit)
|
|
if (Kirigami.Units.gridUnit !== kirigamiGridUnit) {
|
|
// change our global grid unit and prevent Kirigami from resizing our rootItem
|
|
var fixWidth = rootItem.width
|
|
var fixHeight = rootItem.height
|
|
Kirigami.Units.gridUnit = kirigamiGridUnit * 1.0
|
|
rootItem.width = fixWidth
|
|
rootItem.height = fixHeight
|
|
}
|
|
|
|
pageStack.defaultColumnWidth = rootItem.colWidth
|
|
manager.appendTextToLog("Done setting up sizes width " + rootItem.width + " gridUnit " + kirigamiGridUnit)
|
|
}
|
|
|
|
QtObject {
|
|
id: screenSizeObject
|
|
|
|
property int initialWidth: rootItem.width
|
|
property int initialHeight: rootItem.height
|
|
property bool firstChange: true
|
|
property int lastOrientation: undefined
|
|
|
|
Component.onCompleted: {
|
|
// break the binding
|
|
initialWidth = initialWidth * 1
|
|
manager.appendTextToLog("[screensetup] screenSizeObject constructor completed, initial width " + initialWidth)
|
|
setupUnits()
|
|
}
|
|
}
|
|
|
|
onWidthChanged: {
|
|
manager.appendTextToLog("[screensetup] width changed now " + width + " x " + height + " vs screen " + Screen.width + " x " + Screen.height)
|
|
|
|
if (screenSizeObject.lastOrientation === undefined) {
|
|
manager.appendTextToLog("[screensetup] found initial orientation " + Screen.orientation)
|
|
screenSizeObject.lastOrientation = Screen.orientation
|
|
}
|
|
manager.appendTextToLog("[screensetup] window width changed to " + width + " orientation " + Screen.orientation)
|
|
// on Android devices we often get incorrect size updates during startup from Kirigami and we need to ignore those,
|
|
// or more specifically, reset the Kirigami sizes when we notice them
|
|
if (Screen.orientation === screenSizeObject.lastOrientation) {
|
|
// not rotation
|
|
if (width > Screen.width || height > Screen.height) {
|
|
manager.appendTextToLog("[screensetup] received size update that exceeds screen size")
|
|
if (screenSizeObject.initialWidth !== undefined) {
|
|
manager.appendTextToLog("[screensetup] resetting to initial size " + screenSizeObject.initialWidth + " x " + screenSizeObject.initialHeight)
|
|
rootItem.width = screenSizeObject.initialWidth
|
|
rootItem.height = screenSizeObject.initialHeight
|
|
} else {
|
|
// we don't have a size that we believe, yet - using Screen size is almost certainly wrong
|
|
manager.appendTextToLog("[screensetup] restricting to screen size " + Screen.width + " x " + Screen.height)
|
|
rootItem.width = Screen.width
|
|
rootItem.heigh = Screen.height
|
|
}
|
|
} else {
|
|
// this could be a realistic size
|
|
if (screenSizeObject.initialWidth !== undefined) {
|
|
if (screenSizeObject.initialHeight < height) {
|
|
manager.appendTextToLog("[screensetup] remembering better height")
|
|
screenSizeObject.initialHeight = height
|
|
}
|
|
if (screenSizeObject.initialWidth < width) {
|
|
manager.appendTextToLog("[screensetup] remembering better height")
|
|
screenSizeObject.initialWidth = width
|
|
}
|
|
setupUnits()
|
|
}
|
|
}
|
|
} else {
|
|
manager.appendTextToLog("[screensetup] remembering new orientation")
|
|
screenSizeObject.lastOrientation = Screen.orientation
|
|
}
|
|
}
|
|
|
|
property int hackToOpenMap: 0 /* Otherpage */
|
|
/* I really want an enum, but those are painful in QML, so let's use numbers
|
|
* 0 (Otherpage) - the last page selected was a non-map page
|
|
* 1 (MapSelected) - the map page was selected by the user
|
|
* 2 (MapForced) - the map page was forced by this hack
|
|
*/
|
|
|
|
pageStack.onCurrentItemChanged: {
|
|
// This is called whenever the user navigates using the breadcrumbs in the header
|
|
|
|
if (pageStack.currentItem === null) {
|
|
manager.appendTextToLog("there's no current page")
|
|
} else {
|
|
// horrible, insane hack to make picking the mapPage work
|
|
// for some reason I cannot figure out, whenever the mapPage is selected
|
|
// we immediately switch back to the page before it - so force-prevent
|
|
// that undersired behavior
|
|
if (pageStack.currentItem.objectName === mapPage.objectName) {
|
|
// remember that we actively picked the mapPage
|
|
if (hackToOpenMap !== 2 /* MapForced */ ) {
|
|
manager.appendTextToLog("pageStack switched to map")
|
|
hackToOpenMap = 1 /* MapSelected */
|
|
} else {
|
|
manager.appendTextToLog("pageStack forced back to map")
|
|
}
|
|
} else if (pageStack.currentItem.objectName !== mapPage.objectName &&
|
|
pageStack.lastItem.objectName === mapPage.objectName &&
|
|
hackToOpenMap === 1 /* MapSelected */) {
|
|
// if we just picked the mapPage and are suddenly back on a different page
|
|
// force things back to the mapPage
|
|
manager.appendTextToLog("pageStack wrong page, switching back to map")
|
|
pageStack.currentIndex = pageStack.contentItem.contentChildren.length - 1
|
|
hackToOpenMap = 2 /* MapForced */
|
|
} else {
|
|
// if we picked a different page reset the mapPage hack
|
|
manager.appendTextToLog("pageStack switched to " + pageStack.currentItem.objectName)
|
|
hackToOpenMap = 0 /* Otherpage */
|
|
}
|
|
|
|
// disable the left swipe to go back when on the map page
|
|
pageStack.interactive = pageStack.currentItem.objectName !== mapPage.objectName
|
|
|
|
// is there a better way to reload the map markers instead of doing that
|
|
// every time the map page is shown - e.g. link to the dive list model somehow?
|
|
if (pageStack.currentItem.objectName === mapPage.objectName)
|
|
mapPage.reloadMap()
|
|
|
|
// In case we land on any page, not being the DiveDetails (which can be
|
|
// in multiple states, such as add, edit or view), just end the edit/add mode
|
|
if (pageStack.currentItem.objectName !== "DiveDetails" &&
|
|
(detailsWindow.state === 'edit' || detailsWindow.state === 'add')) {
|
|
detailsWindow.endEditMode()
|
|
}
|
|
}
|
|
}
|
|
|
|
QMLManager {
|
|
id: manager
|
|
}
|
|
|
|
property bool initialized: manager.initialized
|
|
|
|
onInitializedChanged: {
|
|
if (initialized) {
|
|
hideBusy()
|
|
manager.appendTextToLog("initialization completed - showing the dive list")
|
|
showPage(diveList) // we want to make sure that gets on the stack
|
|
diveList.diveListModel = diveModel
|
|
|
|
if (Qt.platform.os === "android") {
|
|
manager.appendTextToLog("if we got started by a plugged in device, switch to download page -- pluggedInDeviceName = " + pluggedInDeviceName)
|
|
if (pluggedInDeviceName !== "")
|
|
// if we were started with a dive computer plugged in,
|
|
// immediately switch to download page
|
|
showDownloadForPluggedInDevice()
|
|
}
|
|
}
|
|
}
|
|
|
|
Label {
|
|
id: textBlock
|
|
visible: !initialized
|
|
color: subsurfaceTheme.textColor
|
|
text: qsTr("Subsurface-mobile starting up")
|
|
font.pointSize: subsurfaceTheme.headingPointSize
|
|
topPadding: 2 * Kirigami.Units.gridUnit
|
|
leftPadding: Kirigami.Units.gridUnit
|
|
}
|
|
|
|
StartPage {
|
|
id: startPage
|
|
anchors.fill: parent
|
|
visible: initialized &&
|
|
Backend.cloud_verification_status !== Enums.CS_NOCLOUD &&
|
|
Backend.cloud_verification_status !== Enums.CS_VERIFIED
|
|
onVisibleChanged: {
|
|
manager.appendTextToLog("StartPage visibility changed to " + visible)
|
|
if (!initialized) {
|
|
manager.appendTextToLog("not yet initialized, show busy spinner")
|
|
showBusy()
|
|
}
|
|
if (visible) {
|
|
pageStack.clear()
|
|
} else if (initialized) {
|
|
showDiveList()
|
|
}
|
|
}
|
|
Component.onCompleted: {
|
|
if (Screen.manufacturer + " " + Screen.model + " " + Screen.name !== " ")
|
|
manager.appendTextToLog("Running on " + Screen.manufacturer + " " + Screen.model + " " + Screen.name)
|
|
manager.appendTextToLog("StartPage completed -- initialized is " + initialized)
|
|
}
|
|
}
|
|
|
|
DiveList {
|
|
id: diveList
|
|
visible: false
|
|
}
|
|
|
|
StatisticsPage {
|
|
id: statistics
|
|
visible: false
|
|
}
|
|
|
|
Settings {
|
|
id: settingsWindow
|
|
}
|
|
|
|
DeleteAccount {
|
|
id: deleteAccount
|
|
}
|
|
|
|
CopySettings {
|
|
id: settingsCopyWindow
|
|
visible: false
|
|
}
|
|
|
|
About {
|
|
id: aboutWindow
|
|
visible: false
|
|
}
|
|
|
|
Export {
|
|
id: exportWindow
|
|
visible: false
|
|
}
|
|
|
|
DiveDetails {
|
|
id: detailsWindow
|
|
visible: false
|
|
}
|
|
|
|
TripDetails {
|
|
id: tripEditWindow
|
|
visible: false
|
|
}
|
|
|
|
Log {
|
|
id: logWindow
|
|
visible: false
|
|
}
|
|
|
|
DownloadFromDiveComputer {
|
|
id: downloadFromDc
|
|
visible: false
|
|
}
|
|
|
|
MapPage {
|
|
id: mapPage
|
|
visible: false
|
|
}
|
|
|
|
RecoverCache {
|
|
id: recoverCache
|
|
visible: false
|
|
}
|
|
|
|
/* this shouldn't be exposed unless someone will finish the work
|
|
DivePlannerSetup {
|
|
id: divePlannerSetupWindow
|
|
visible: false
|
|
}
|
|
|
|
DivePlannerEdit {
|
|
id: divePlannerEditWindow
|
|
visible: false
|
|
}
|
|
|
|
DivePlannerView {
|
|
id: divePlannerViewWindow
|
|
visible: false
|
|
}
|
|
|
|
DivePlannerManager {
|
|
id: divePlannerManagerWindow
|
|
visible: false
|
|
}
|
|
*/
|
|
DiveSummary {
|
|
id: diveSummaryWindow
|
|
visible: false
|
|
}
|
|
|
|
ThemeTest {
|
|
id: themetest
|
|
visible: false
|
|
}
|
|
|
|
function showDownloadPage(vendor, product, connection) {
|
|
manager.appendTextToLog("show download page for " + vendor + " / " + product + " / " + connection)
|
|
downloadFromDc.dcImportModel.clearTable()
|
|
if (vendor !== undefined && product !== undefined && connection !== undefined) {
|
|
downloadFromDc.setupUSB = true
|
|
// set up the correct values on the download page
|
|
// setting the currentIndex to -1, first, helps to ensure
|
|
// that the comboBox does get updated in the UI
|
|
if (vendor !== -1) {
|
|
downloadFromDc.vendor = -1
|
|
downloadFromDc.vendor = vendor
|
|
}
|
|
if (product !== -1) {
|
|
downloadFromDc.product = -1
|
|
downloadFromDc.product = product
|
|
}
|
|
if (connection !== -1) {
|
|
downloadFromDc.connection = -1
|
|
downloadFromDc.connection = connection
|
|
}
|
|
} else {
|
|
downloadFromDc.setupUSB = false
|
|
}
|
|
|
|
showPage(downloadFromDc)
|
|
}
|
|
|
|
function showDownloadForPluggedInDevice() {
|
|
// don't add this unless the dive list is already shown
|
|
if (pageIndex(diveList) === -1)
|
|
return
|
|
manager.appendTextToLog("plugged in device name changed to " + pluggedInDeviceName)
|
|
/* if we recognized the device, we'll pass in a triple of ComboBox indeces as "vendor;product;connection" */
|
|
var vendorProductConnection = pluggedInDeviceName.split(';')
|
|
if (vendorProductConnection.length === 3)
|
|
showDownloadPage(vendorProductConnection[0], vendorProductConnection[1], vendorProductConnection[2])
|
|
else
|
|
showDownloadPage()
|
|
}
|
|
|
|
onPluggedInDeviceNameChanged: {
|
|
if (detailsWindow.state === 'edit' || detailsWindow.state === 'add') {
|
|
/* we're in the middle of editing / adding a dive */
|
|
manager.appendTextToLog("Download page requested by Android Intent, but adding/editing dive; no action taken")
|
|
} else {
|
|
// we want to show the downloads page
|
|
// note that if Subsurface-mobile was started because a USB device was plugged in, this is run too early;
|
|
// we catch this in the function below and instead switch to the download page in the completion signal
|
|
// handler for the startPage
|
|
showDownloadForPluggedInDevice()
|
|
}
|
|
}
|
|
onClosing: {
|
|
// this duplicates the check that is already in the onBackRequested signal handler of the DiveList
|
|
if (globalDrawer.visible) {
|
|
globalDrawer.close()
|
|
close.accepted = false
|
|
}
|
|
if (contextDrawer.visible) {
|
|
contextDrawer.close()
|
|
close.accepted = false
|
|
}
|
|
}
|
|
}
|