/* * Copyright (C) 2014-2017 CZ.NIC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations including * the two. */ import QtQuick 2.7 import QtQuick.Controls 2.1 import QtQuick.Window 2.1 import QtQuick.Layouts 1.3 import QtQuick.Dialogs 1.2 import QtQuick.Controls.Material 2.1 import cz.nic.mobileDatovka 1.0 ApplicationWindow { // Get deafult color theme and palette from system SystemPalette { id: datovkaPalette; colorGroup: SystemPalette.Active } id: mainWindow visible: true title: "Datovka" width: 800 height: 600 color: datovkaPalette.window // This element set default font (size) and must be hidden. Font is used // accross application as default font size. Do not remove it. Text { id: defaultTextFont visible: false text: "Note: set default font" } // Set default material design from system palette Material.theme: Material.System Material.background: datovkaPalette.base Material.foreground: datovkaPalette.text Material.primary: datovkaPalette.alternateBase Material.accent: datovkaPalette.highlight // define all pages for stackview property Component pageAboutApp: PageAboutApp {} property Component pageAccountDetail: PageAccountDetail {} property Component pageChangePassword: PageChangePassword {} property Component pageMenuAccount: PageMenuAccount {} property Component pageMenuDatovkaSettings: PageMenuDatovkaSettings {} property Component pageMenuMessage: PageMenuMessage {} property Component pageMenuMessageDetail: PageMenuMessageDetail {} property Component pageMenuMessageList: PageMenuMessageList {} property Component pageMessageDetail: PageMessageDetail {} property Component pageMessageList: PageMessageList {} property Component pageMessageSearch: PageMessageSearch {} property Component pageSettingsAccount: PageSettingsAccount {} property Component pageSettingsGeneral: PageSettingsGeneral {} property Component pageSettingsPin: PageSettingsPin {} property Component pageSettingsStorage: PageSettingsStorage {} property Component pageSettingsSync: PageSettingsSync {} // header background color property string mainHeaderBgColor: "#00539b" property int statusBarTimer: 5000 // dimension and style based on font pixel size and screen dpi property int textFontSizeInPixels: defaultTextFont.font.pixelSize property int textPointSmall: Math.round(defaultTextFont.font.pointSize * 0.7) property int textFontSizeSmall: if (textPointSmall > 0) {textPointSmall} else {8} property int headerHeight: textFontSizeInPixels * 3 property int imgHeight: headerHeight * 0.8 // inputItemHeight holds height of controls elements computed from font size property int inputItemHeight: headerHeight property int navImgHeight: headerHeight * 0.3 property int listItemHeight: headerHeight * 1.5 property int defaultMargin: Math.round(Screen.pixelDensity) property int acntListSpacing: defaultMargin * 2 property int formItemVerticalSpacing: defaultMargin property int formButtonHorizontalSpacing: defaultMargin * 5 /* compare message delivery date with current date-90days */ property int deleteAfterDays: 90 function compareMsgDate(msgDeliveryTime) { if (msgDeliveryTime == "") { // message has virus or delivery time missing return true } else { // convert qml date format to ISO date format var inputDateFormat = /(\d{2})\.(\d{2})\.(\d{4})/; var msgDate = new Date(msgDeliveryTime.replace(inputDateFormat,'$3-$2-$1')); var today = new Date() today.setDate(today.getDate()-deleteAfterDays) // compare both dates in milliseconds return (today.getTime() > msgDate.getTime()) } } /* Exposes nested stack to code outside the page component. */ property var nestedStack: null /* Password/OTP input dialog, emitted from C++ */ InputDialogue { id: inputDialog onFinished: { isds.returnInputDialogText(isdsAction, pwdType, userName, pwd) } Connections { target: isds onOpenDialogRequest: { inputDialog.openInputDialog(isdsAction, pwdType, userName, title, text, placeholderText, hidePwd) } } } StackView { // Page area. id: mainStack anchors.fill: parent initialItem: appArea Item { id: appArea objectName: "appArea" anchors.fill: parent StackView.onActivated: { mainStack.forceActiveFocus() } Item { /* Application page stack. */ id: mainPage anchors.fill: parent visible: true StackView { id: pageView anchors.fill: parent visible: true initialItem: PageAccountList {} Component.onCompleted: { nestedStack = pageView } } } Rectangle { id: statusBar visible: false anchors.left: parent.left anchors.bottom: parent.bottom anchors.right: parent.right height: headerHeight * 0.5 color: datovkaPalette.alternateBase BusyIndicator { id: busyuIndicator width: parent.height height: parent.height anchors.centerIn: parent running: false } Text { id: statusBarText color: datovkaPalette.text anchors.centerIn: parent text: mainWindow.width + " x "+ mainWindow.height Connections { target: isds onStatusBarTextChanged: { statusBar.visible = isVisible statusBarText.text = txt busyuIndicator.running = busy timerId.running = !busy } } Connections { target: files onStatusBarTextChanged: { statusBar.visible = true statusBarText.text = txt busyuIndicator.running = busy timerId.running = !busy } } Connections { target: settings onStatusBarTextChanged: { statusBar.visible = true statusBarText.text = txt busyuIndicator.running = busy timerId.running = !busy } } } Timer { id: timerId interval: statusBarTimer; running: false; repeat: false onTriggered: { timerId.running = false statusBar.visible = false statusBarText.text = "" } } } } Component { /* Pin lock screen. */ id: lockScreen Rectangle { objectName: "lockScreen" anchors.fill: parent color: datovkaPalette.base Column { anchors.centerIn: parent spacing: formItemVerticalSpacing TextField { id: pinCodeInput height: inputItemHeight font.pointSize: defaultTextFont.font.pointSize //focus: true // Forcing focus here causes troubles on mobile devices. echoMode: TextInput.Password passwordMaskDelay: 500 // milliseconds inputMethodHints: Qt.ImhDigitsOnly placeholderText: qsTr("Enter PIN code") anchors.horizontalCenter: parent.horizontalCenter horizontalAlignment: TextInput.AlignHCenter function verifyPin() { settings.verifyPin(pinCodeInput.text.toString()) } onEditingFinished: { // This function is called repeatedly when switching // windows. The condition should reduce PIN verification // calls. if (pinCodeInput.text.length > 0) { verifyPin() } } } Text { id: wrongPin font.bold: true visible: false color: datovkaPalette.text anchors.horizontalCenter: parent.horizontalCenter text: qsTr("Wrong PIN code!") } Button { text: qsTr("Enter") anchors.horizontalCenter: parent.horizontalCenter font.pointSize: defaultTextFont.font.pointSize height: inputItemHeight onClicked: { pinCodeInput.verifyPin() } } Item { id: blankField width: parent.width height: headerHeight anchors.horizontalCenter: parent.horizontalCenter } Image{ id: datovkaLogo anchors.horizontalCenter: parent.horizontalCenter width: imgHeight * 1.4 height: imgHeight * 1.4 source: "qrc:/datovka.png" } Text { id: versionLabel anchors.horizontalCenter: parent.horizontalCenter color: datovkaPalette.text text: qsTr("Version") + ": " + settings.appVersion() } } // Column Connections { target: settings onSendPinReply: { if (success) { Qt.inputMethod.hide() if (mainStack.currentItem.objectName == "lockScreen") { mainStack.pop(StackView.Immediate) } } wrongPin.visible = !success pinCodeInput.text = "" } } // Connections } // Rectangle } // Component Connections { target: locker onLockApp: { if (mainStack.currentItem.objectName == "appArea") { /* Lock application area. */ mainStack.push(lockScreen, StackView.Immediate) } } } Connections { target: interactionZfoFile onDisplayZfoFileContent: { /* * The app should be locked from within C++ code when PIN is * enabled. */ console.log("Showing zfo file: " + filePath) var fileContent = files.rawFileContent(filePath) mainStack.push(pageMessageDetail, { "pageView": mainStack, "fromLocalDb": false, "rawZfoContent": fileContent }, StackView.Immediate) /* * Next function has effect only for iOS. * Detail info is in the header file. */ files.deleteTmpFileFromStorage(filePath) } } Connections { target: dlgEmitter onDlgGetText: { /* * Create a dynamic dialogue object set content and connect * functionality. */ var dlgComponent = Qt.createComponent("qrc:/qml/dialogues/PasteInputDialogue.qml", Component.PreferSynchronous); var dlgObj; if (dlgComponent.status == Component.Ready) { finishCreation(); } else { console.log("Failed loading dlgComponent:", dlgComponent.errorString()); dlgComponent.statusChanged.connect(finishCreation); } function finishCreation() { if (dlgComponent.status == Component.Error) { // Error handling. console.log("Error loading dlgComponent:", dlgComponent.errorString()); return; } else if (dlgComponent.status != Component.Ready) { return; } dlgObj = dlgComponent.createObject(mainWindow, {"objectName": "textInputDialogue"}); if (dlgObj == null) { // Error handling. console.log("Error creating dialogue object"); return; } dlgObj.dialogue.title = title dlgObj.dialogue.content.messageText.text = message dlgObj.dialogue.content.textInput.inputMethodHints = inputMethodHints /* Must be set before setting echoMode. */ dlgObj.dialogue.dlgEchoMode = echoMode dlgObj.dialogue.content.textInput.text = text dlgObj.dialogue.content.textInput.placeholderText = placeholderText dlgObj.dialogue.explicitPasteMenu = explicitPasteMenu /* See PasteInputDialogue.qml for explanation. */ dlgObj.dialogue.standardButtons = StandardButton.Ok | StandardButton.Cancel /* Connect signals. */ dlgObj.dialogueClosed.connect(deleteObject); dlgObj.dialogue.accepted.connect(emitAccepted); dlgObj.dialogue.rejected.connect(emitRejected); dlgObj.dialogue.open(); } function deleteObject() { console.log("Destroying dialogue."); dlgObj.destroy(); mainStack.forceActiveFocus(); /* Dialogue stole focus. */ } function emitAccepted() { console.log("Accepted " + dlgObj.dialogue.content.textInput.text); dlgEmitter.emitAccepted(dlgObj.dialogue.content.textInput.text); } function emitRejected() { console.log("Rejected"); dlgEmitter.emitRejected(); } } onDlgMessage: { /* * Create a dynamic dialogue object set content and connect * functionality. */ var dlgComponent = Qt.createComponent("qrc:/qml/dialogues/MessageDialogue.qml", Component.PreferSynchronous); var dlgObj; if (dlgComponent.status == Component.Ready) { finishCreation(); } else { console.log("Failed loading dlgComponent:", dlgComponent.errorString()); dlgComponent.statusChanged.connect(finishCreation); } function finishCreation() { if (dlgComponent.status == Component.Error) { // Error handling. console.log("Error loading dlgComponent:", dlgComponent.errorString()); return; } else if (dlgComponent.status != Component.Ready) { return; } dlgObj = dlgComponent.createObject(mainWindow, {"objectName": "messageDialogue"}); if (dlgObj == null) { // Error handling. console.log("Error creating dialogue object"); return; } dlgObj.dialogue.icon = icon dlgObj.dialogue.title = title dlgObj.dialogue.content.messageText.text = message dlgObj.dialogue.content.infoMessageText.text = infoMessage dlgObj.dialogue.dlgButtons = buttons /* Connect signals. */ dlgObj.dialogueClosed.connect(gatherDataAndDeleteObject); dlgObj.dialogue.open(); } function gatherDataAndDeleteObject(button, customButton) { /* TODO -- Gather pressed button value. */ dlgEmitter.emitClosed(button, customButton) console.log("Destroying dialogue."); dlgObj.destroy(); mainStack.forceActiveFocus(); /* Dialogue stole focus. */ } function emitClosed() { console.log("Closed"); dlgEmitter.emitClosed(1, -1); } } } function navigateBack(mainStack, nestedStack, event) { if (event.key === Qt.Key_Back) { event.accepted = true if (nestedStack == null) { return } if (mainStack.currentItem.objectName == "lockScreen") { console.log("Ignoring back button on lock screen.") } else if (mainStack.depth > 1) { mainStack.pop(StackView.Immediate) } else if (nestedStack.depth > 1) { nestedStack.pop() } else { event.accepted = false Qt.quit() } } } /* Android back button. */ focus: true Keys.onReleased: { navigateBack(mainStack, nestedStack, event) } } /* We can loose the focus to StackView if any Menu or Popup is opened */ function resetFocus() { pageView.focus = true } Text { id: dummyText text: "" visible: false wrapMode: Text.NoWrap elide: Text.ElideNone } function computeMenuWidth(menu) { var w = 0.0 for (var i = 0; i < menu.contentData.length; i++) { dummyText.text = menu.contentData[i].text + "www" if (w < dummyText.width) { w = dummyText.width } } dummyText.text = "" return Math.round(w) } }