main.qml 18.9 KB
Newer Older
1
/*
2
 * Copyright (C) 2014-2018 CZ.NIC
3 4 5 6 7 8 9 10
 *
 * 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
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 13 14 15 16 17 18 19 20 21 22 23
 * 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 <http://www.gnu.org/licenses/>.
 *
 * 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.
 */

24
import QtQuick 2.7
25
import QtQuick.Controls 2.1
26
import QtQuick.Window 2.1
27
import QtQuick.Layouts 1.3
28
import QtQuick.Dialogs 1.2
29
import QtQuick.Controls.Material 2.1
30
import cz.nic.mobileDatovka 1.0
31 32 33 34 35 36 37 38 39

ApplicationWindow {

    // Get deafult color theme and palette from system
    SystemPalette { id: datovkaPalette; colorGroup: SystemPalette.Active }
    id: mainWindow
    visible: true
    title: "Datovka"
    width: 800
Martin Straka's avatar
Martin Straka committed
40
    minimumWidth: 320
41
    height: 600
Martin Straka's avatar
Martin Straka committed
42 43
    minimumHeight: 240

44 45
    color: datovkaPalette.window

46 47
    // This element set default font (size) and must be hidden. Font is used
    // accross application as default font size. Do not remove it.
48 49
    Text {
        id: defaultTextFont
50 51
        visible: false
        text: "Note: set default font"
52 53
    }

54 55
    // Set default material design from system palette
    Material.theme: Material.System
56 57 58 59 60 61 62
    Material.background: datovkaPalette.window
    Material.foreground: datovkaPalette.windowText
    Material.primary: Material.Blue
    Material.accent: Material.Blue
    /* Some color saturation */
    property color readBgColor: Qt.darker(datovkaPalette.alternateBase, 1.08)
    property color headerColor: Material.color(Material.Blue)
63

64
    // define all pages for stackview
65 66
    property Component pageAboutApp: PageAboutApp {}
    property Component pageAccountDetail: PageAccountDetail {}
67
    property Component pageChangePassword: PageChangePassword {}
Martin Straka's avatar
Martin Straka committed
68
    property Component pageContactList: PageContactList {}
69
    property Component pageDataboxDetail: PageDataboxDetail {}
70
    property Component pageDataboxSearch: PageDataboxSearch {}
71 72
    property Component pageGovService: PageGovService {}
    property Component pageGovServiceList: PageGovServiceList {}
73
    property Component pageImportMessage: PageImportMessage {}
74
    property Component pageLog: PageLog {}
Karel Slaný's avatar
Karel Slaný committed
75
    property Component pageMenuAccount: PageMenuAccount {}
76 77 78 79 80 81
    property Component pageMenuDatovkaSettings: PageMenuDatovkaSettings {}
    property Component pageMenuMessage: PageMenuMessage {}
    property Component pageMenuMessageDetail: PageMenuMessageDetail {}
    property Component pageMenuMessageList: PageMenuMessageList {}
    property Component pageMessageDetail: PageMessageDetail {}
    property Component pageMessageList: PageMessageList {}
82
    property Component pageMessageSearch: PageMessageSearch {}
83
    property Component pageRecordsManagementUpload: PageRecordsManagementUpload {}
84
    property Component pageSendMessage: PageSendMessage {}
85 86 87
    property Component pageSettingsAccount: PageSettingsAccount {}
    property Component pageSettingsGeneral: PageSettingsGeneral {}
    property Component pageSettingsPin: PageSettingsPin {}
88
    property Component pageSettingsRecordsManagement: PageSettingsRecordsManagement {}
89 90
    property Component pageSettingsStorage: PageSettingsStorage {}
    property Component pageSettingsSync: PageSettingsSync {}
91

92 93 94
    // header background color
    property string mainHeaderBgColor: "#00539b"
    // dimension and style based on font pixel size and screen dpi
95 96 97 98
    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
Martin Straka's avatar
Martin Straka committed
99 100
    property int imgHeightHeader: headerHeight * 0.6
    property int imgHeight: imgHeightHeader * 0.8
101
    // inputItemHeight holds height of controls elements computed from font size
Martin Straka's avatar
Martin Straka committed
102
    property int inputItemHeight: headerHeight
103 104 105
    property int navImgHeight: headerHeight * 0.3
    property int listItemHeight: headerHeight * 1.5
    property int defaultMargin: Math.round(Screen.pixelDensity)
Martin Straka's avatar
Martin Straka committed
106
    property int acntListSpacing: defaultMargin * 4
107
    property int formItemVerticalSpacing: defaultMargin
108
    property int formButtonHorizontalSpacing: defaultMargin * 5
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126

    /* 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())
        }
    }

127 128 129
    /* Exposes nested stack to code outside the page component. */
    property var nestedStack: null

130
    /* Password/OTP input dialog, emitted from C++ */
Martin Straka's avatar
Martin Straka committed
131 132 133
    InputDialogue {
        id: inputDialog
        onFinished: {
134 135 136 137
            isds.inputDialogReturnPassword(isdsAction, pwdType, userName, pwd)
        }
        onCanceled: {
            isds.inputDialogCancelPostAction(isdsAction, userName)
Martin Straka's avatar
Martin Straka committed
138 139 140
        }
        Connections {
            target: isds
141
            onOpenDialogRequest: {
142
                 inputDialog.openInputDialog(isdsAction, pwdType, userName, title, text, placeholderText, hidePwd)
Martin Straka's avatar
Martin Straka committed
143 144 145 146
            }
        }
    }

147 148
    StackView { // Page area.
        id: mainStack
149
        anchors.fill: parent
150 151
        initialItem: appArea

152
        Item {
153
            id: appArea
154 155
            objectName: "appArea"
            anchors.fill: parent
156

157 158 159
            StackView.onActivated: {
                mainStack.forceActiveFocus()
            }
160

161 162 163 164 165 166
            Item { /* Application page stack. */
                id: mainPage
                anchors.fill: parent
                visible: true
                StackView {
                    id: pageView
167 168 169 170
                    anchors.top: parent.top
                    anchors.left: parent.left
                    anchors.right: parent.right
                    anchors.bottom: (logBar.visible) ? logBar.top : parent.bottom
171
                    visible: true
172 173 174 175
                    initialItem: PageAccountList {}

                    Component.onCompleted: {
                        nestedStack = pageView
176
                    }
177
                }
178 179 180
                LogBar {
                    id: logBar
                }
181
            }
182
            ProgressBar {
183
                id: statusBar
184
            }
185
        }
186

187 188 189 190 191
        Component { /* Pin lock screen. */
            id: lockScreen
            Rectangle  {
                objectName: "lockScreen"
                anchors.fill: parent
192
                color: datovkaPalette.base
193
                Column {
194
                    anchors.centerIn: parent
195
                    spacing: formItemVerticalSpacing
196
                    AccessibleTextField {
197
                        id: pinCodeInput
198
                        height: inputItemHeight
199
                        font.pointSize: defaultTextFont.font.pointSize
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
                        //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()
                            }
                        }
Karel Slaný's avatar
Karel Slaný committed
218
                    }
219
                    AccessibleText {
220 221 222 223 224 225 226
                        id: wrongPin
                        font.bold: true
                        visible: false
                        color: datovkaPalette.text
                        anchors.horizontalCenter: parent.horizontalCenter
                        text: qsTr("Wrong PIN code!")
                    }
227
                    AccessibleButton {
228
                        text: qsTr("Enter")
229
                        anchors.horizontalCenter: parent.horizontalCenter
230 231 232 233
                        font.pointSize: defaultTextFont.font.pointSize
                        height: inputItemHeight
                        onClicked: {
                            pinCodeInput.verifyPin()
234 235
                        }
                    }
236
                    Item {
237
                        id: blankField
238
                        width: parent.width
239 240 241
                        height: headerHeight
                        anchors.horizontalCenter: parent.horizontalCenter
                    }
242
                    Image {
243 244
                        id: datovkaLogo
                        anchors.horizontalCenter: parent.horizontalCenter
Martin Straka's avatar
Martin Straka committed
245 246
                        width: imgHeightHeader * 1.4
                        height: imgHeightHeader * 1.4
247 248
                        source: "qrc:/datovka.png"
                    }
249
                    AccessibleText {
250 251 252 253
                        id: versionLabel
                        anchors.horizontalCenter: parent.horizontalCenter
                        color: datovkaPalette.text
                        text: qsTr("Version") + ": " + settings.appVersion()
254
                    }
255
                } // Column
256 257 258 259 260 261 262 263 264 265 266 267
                Connections {
                    target: settings
                    onSendPinReply: {
                        if (success) {
                            Qt.inputMethod.hide()
                            if (mainStack.currentItem.objectName == "lockScreen") {
                                mainStack.pop(StackView.Immediate)
                            }
                        }
                        wrongPin.visible = !success
                        pinCodeInput.text = ""
                    }
268 269 270
                } // Connections
            } // Rectangle
        } // Component
271

272 273 274
        Connections {
            target: locker
            onLockApp: {
275 276 277 278
                if (mainStack.currentItem.objectName == "appArea") {
                    /* Lock application area. */
                    mainStack.push(lockScreen, StackView.Immediate)
                }
279 280
            }
        }
281 282 283 284 285 286 287 288
        Connections {
            target: interactionZfoFile
            onDisplayZfoFileContent: {
                /*
                 * The app should be locked from within C++ code when PIN is
                 * enabled.
                 */
                console.log("Showing zfo file: " + filePath)
289
                var fileContent = files.rawFileContent(filePath)
290
                mainStack.push(pageMessageDetail, {
291
                        "pageView": mainStack,
292
                        "fromLocalDb": false,
293
                        "rawZfoContent": fileContent
294
                    }, StackView.Immediate)
295 296 297 298 299
                /*
                 * Next function has effect only for iOS.
                 * Detail info is in the header file.
                 */
                files.deleteTmpFileFromStorage(filePath)
300 301
            }
        }
302

303 304 305 306 307 308 309 310 311 312 313 314 315
        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 {
316
                    console.log("Failed loading dlgComponent:", dlgComponent.errorString());
317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
                    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();
                }
            }
372 373 374 375 376 377 378 379 380 381 382 383

            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 {
384
                    console.log("Failed loading dlgComponent:", dlgComponent.errorString());
385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403
                    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;
                    }

404
                    dlgObj.dialogue.icon = icon
405 406 407
                    dlgObj.dialogue.title = title
                    dlgObj.dialogue.content.messageText.text = message
                    dlgObj.dialogue.content.infoMessageText.text = infoMessage
408
                    dlgObj.dialogue.dlgButtons = buttons
409 410 411 412 413 414 415

                    /* Connect signals. */
                    dlgObj.dialogueClosed.connect(gatherDataAndDeleteObject);

                    dlgObj.dialogue.open();
                }

416
                function gatherDataAndDeleteObject(button, customButton) {
417
                    /* TODO -- Gather pressed button value. */
418
                    dlgEmitter.emitClosed(button, customButton)
419 420 421 422 423 424 425 426 427 428 429 430

                    console.log("Destroying dialogue.");
                    dlgObj.destroy();

                    mainStack.forceActiveFocus(); /* Dialogue stole focus. */
                }

                function emitClosed() {
                    console.log("Closed");
                    dlgEmitter.emitClosed(1, -1);
                }
            }
431 432
        }

433
        function navigateBack(mainStack, nestedStack, event) {
434 435
            if (event.key === Qt.Key_Back) {
                event.accepted = true
436 437 438 439 440 441 442 443 444 445

                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) {
446
                    nestedStack.pop()
447 448 449 450 451 452
                } else {
                    event.accepted = false
                    Qt.quit()
                }
            }
        }
453 454 455 456 457 458

        /* Android back button. */
        focus: true
        Keys.onReleased: {
            navigateBack(mainStack, nestedStack, event)
        }
459
    }
460

461 462 463 464 465
    /* We can loose the focus to StackView if any Menu or Popup is opened */
    function resetFocus() {
        pageView.focus = true
    }

466 467 468 469 470 471 472 473
    Text {
        id: dummyText
        text: ""
        visible: false
        wrapMode: Text.NoWrap
        elide: Text.ElideNone
    }
    function computeMenuWidth(menu) {
474
        var w = 0.0
475
        for (var i = 0; i < menu.contentData.length; i++) {
476
            dummyText.text = menu.contentData[i].text + "www"
477 478 479 480 481
            if (w < dummyText.width) {
                w = dummyText.width
            }
        }
        dummyText.text = ""
482
        return Math.round(w)
483
    }
484
}