Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
MainView.qml 20.94 KiB
/*
 * Copyright (C) 2020 by Savoir-faire Linux
 * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
 *
 * 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 <https://www.gnu.org/licenses/>.
 */
import QtQuick 2.14
import QtQuick.Window 2.14
import QtQuick.Controls 2.14
import QtQuick.Layouts 1.14
import QtQuick.Controls.Universal 2.12
import QtGraphicalEffects 1.14
import net.jami.Models 1.0
import net.jami.Adapters 1.0

// Import qml component files.
import "components"
import "../wizardview"
import "../settingsview"
import "../settingsview/components"

Window {
    id: mainViewWindow
    objectName: "mainViewWindow"

    property int minWidth: settingsViewPreferredWidth
    property int minHeight: 400

    property int mainViewWindowPreferredWidth: 650
    property int mainViewWindowPreferredHeight: 600
    property int sidePanelViewStackPreferredWidth: 250
    property int mainViewStackPreferredWidth: 250
    property int settingsViewPreferredWidth: 445
    property int onWidthChangedTriggerDistance: 5

    property int savedSidePanelViewMinWidth: 0
    property int savedSidePanelViewMaxWidth: 0
    property int savedWelcomeViewMinWidth: 0
    property int savedWelcomeViewMaxWidth: 0
    property bool sidePanelOnly: !mainViewStack.visible

    // To calculate tab bar bottom border hidden rect left margin.
    property int tabBarLeftMargin: 8
    property int tabButtonShrinkSize: 8
    property bool inSettingsView: false

    // For updating msgWebView
    property string currentConvUID: ""

    signal closeApp
    signal noAccountIsAvailable

    property string currentAccountId: AccountAdapter.currentAccountId
    onCurrentAccountIdChanged: {
        var index = UtilsAdapter.getCurrAccList().indexOf(currentAccountId)
        mainViewWindowSidePanel.refreshAccountComboBox(index)
        if (inSettingsView) {
            settingsView.accountListChanged()
            settingsView.setSelected(settingsView.selectedMenu, true)
        } else {
            backToMainView(true)
        }
    }

    function isPageInStack(objectName, stackView) {
        var foundItem = stackView.find(function (item, index) {
            return item.objectName === objectName
        })

        return foundItem ? true : false
    }

    function showWelcomeView() {
        currentConvUID = ""
        callStackView.needToCloseInCallConversationAndPotentialWindow()
        mainViewWindowSidePanel.deselectConversationSmartList()
        if (isPageInStack("callStackViewObject", sidePanelViewStack) ||
                isPageInStack("communicationPageMessageWebView", sidePanelViewStack) ||
                isPageInStack("communicationPageMessageWebView", mainViewStack) ||
                isPageInStack("callStackViewObject", mainViewStack)) {
            sidePanelViewStack.pop(StackView.Immediate)
            mainViewStack.pop(welcomePage, StackView.Immediate)
        }
        recordBox.visible = false
    }

    function pushCallStackView() {
        if (sidePanelOnly) {
            sidePanelViewStack.pop(StackView.Immediate)
            sidePanelViewStack.push(callStackView, StackView.Immediate)
        } else {
            sidePanelViewStack.pop(StackView.Immediate)
            mainViewStack.pop(welcomePage, StackView.Immediate)
            mainViewStack.push(callStackView, StackView.Immediate)
        }
    }

    function pushCommunicationMessageWebView() {
        if (sidePanelOnly) {
            sidePanelViewStack.pop(StackView.Immediate)
            sidePanelViewStack.push(communicationPageMessageWebView, StackView.Immediate)
        } else {
            mainViewStack.pop(welcomePage, StackView.Immediate)
            mainViewStack.push(communicationPageMessageWebView, StackView.Immediate)
        }
    }

    function startWizard() {
        mainViewStackLayout.currentIndex = 1
    }

    function currentAccountIsCalling() {
        return UtilsAdapter.hasCall(AccountAdapter.currentAccountId)
    }

    // Only called onWidthChanged
    function recursionStackViewItemMove(stackOne, stackTwo, depth=1) {
        // Move all items (expect the bottom item) to stacktwo by the same order in stackone.
        if (stackOne.depth === depth) {
            return
        }

        var tempItem = stackOne.pop(StackView.Immediate)
        recursionStackViewItemMove(stackOne, stackTwo, depth)
        stackTwo.push(tempItem, StackView.Immediate)
    }

    // Back to WelcomeView required, but can also check, i. e., on account switch or
    // settings exit, if there is need to switch to a current call
    function backToMainView(checkCurrentCall = false) {
        if (inSettingsView)
            return
        if (checkCurrentCall && currentAccountIsCalling()) {
            var callConv = UtilsAdapter.getCallConvForAccount(
                        AccountAdapter.currentAccountId)
            ConversationsAdapter.selectConversation(
                        AccountAdapter.currentAccountId, callConv)
        } else {
            showWelcomeView()
        }
    }

    function toggleSettingsView() {
        inSettingsView = !inSettingsView

        if (inSettingsView) {
            if (sidePanelOnly)
                sidePanelViewStack.push(settingsMenu, StackView.Immediate)
            else {
                mainViewStack.pop(welcomePage, StackView.Immediate)
                mainViewStack.push(settingsView, StackView.Immediate)
                sidePanelViewStack.push(settingsMenu, StackView.Immediate)

                var windowCurrentMinimizedSize = settingsViewPreferredWidth
                        + sidePanelViewStackPreferredWidth + onWidthChangedTriggerDistance
                if (mainViewWindow.width < windowCurrentMinimizedSize)
                    mainViewWindow.width = windowCurrentMinimizedSize
            }
        } else {
            sidePanelViewStack.pop(StackView.Immediate)
            mainViewStack.pop(StackView.Immediate)
            backToMainView(true)
        }
    }

    // ConversationSmartListViewItemDelegate provides UI information
    function setMainView(currentUserDisplayName, currentUserAlias, currentUID,
                               callStackViewShouldShow, isAudioOnly, callState) {
        if (!(communicationPageMessageWebView.jsLoaded)) {
            communicationPageMessageWebView.jsLoadedChanged.connect(
                        function(currentUserDisplayName, currentUserAlias, currentUID,
                                 callStackViewShouldShow, isAudioOnly, callState) {
                            return function() {
                                setMainView(currentUserDisplayName, currentUserAlias, currentUID,
                                            callStackViewShouldShow, isAudioOnly, callState)
                            }
                        }(currentUserDisplayName, currentUserAlias, currentUID,
                          callStackViewShouldShow, isAudioOnly, callState))
            return
        }

        if (callStackViewShouldShow) {
            if (inSettingsView) {
                toggleSettingsView()
            }
            MessagesAdapter.setupChatView(currentUID)
            communicationPageMessageWebView.headerUserAliasLabelText = currentUserAlias
            communicationPageMessageWebView.headerUserUserNameLabelText = currentUserDisplayName
            callStackView.setLinkedWebview(communicationPageMessageWebView)
            callStackView.responsibleAccountId = AccountAdapter.currentAccountId
            callStackView.responsibleConvUid = currentUID
            callStackView.updateCorrespondingUI()

            if (callState === Call.Status.IN_PROGRESS || callState === Call.Status.PAUSED) {
                UtilsAdapter.setCurrentCall(AccountAdapter.currentAccountId, currentUID)
                if (isAudioOnly)
                    callStackView.showAudioCallPage()
                else
                    callStackView.showVideoCallPage()
            } else if (callState === Call.Status.INCOMING_RINGING) {
                callStackView.showIncomingCallPage(AccountAdapter.currentAccountId,
                                                   currentUID)
            } else {
                callStackView.showOutgoingCallPage(callState)
            }
            pushCallStackView()
        } else if (!inSettingsView) {
            if (currentConvUID !== currentUID) {
                callStackView.needToCloseInCallConversationAndPotentialWindow()
                MessagesAdapter.setupChatView(currentUID)
                communicationPageMessageWebView.headerUserAliasLabelText = currentUserAlias
                communicationPageMessageWebView.headerUserUserNameLabelText = currentUserDisplayName
                pushCommunicationMessageWebView()
                communicationPageMessageWebView.focusMessageWebView()
                currentConvUID = currentUID
            } else if (isPageInStack("callStackViewObject", sidePanelViewStack)
                       || isPageInStack("callStackViewObject", mainViewStack)) {
                callStackView.needToCloseInCallConversationAndPotentialWindow()
                pushCommunicationMessageWebView()
                communicationPageMessageWebView.focusMessageWebView()
            }
        }
    }

    title: JamiStrings.appTitle
    visible: true
    width: mainViewWindowPreferredWidth
    height: mainViewWindowPreferredHeight
    minimumWidth: minWidth
    minimumHeight: minHeight

    Connections {
        target: CallAdapter

        // selectConversation causes UI update
        function onCallSetupMainViewRequired(accountId, convUid) {
            ConversationsAdapter.selectConversation(accountId, convUid)
        }
    }

    StackLayout {
        id: mainViewStackLayout

        anchors.fill: parent

        currentIndex: 0

        SplitView {
            id: splitView

            Layout.fillWidth: true
            Layout.fillHeight: true

            width: mainViewWindow.width
            height: mainViewWindow.height

            handle: Rectangle {
                implicitWidth: JamiTheme.splitViewHandlePreferredWidth
                implicitHeight: splitView.height
                color:"white"
                Rectangle {
                    implicitWidth: 1
                    implicitHeight: splitView.height
                    color: SplitHandle.pressed ? JamiTheme.pressColor :
                                                 (SplitHandle.hovered ? JamiTheme.hoverColor :
                                                                        JamiTheme.tabbarBorderColor)
                }
            }
            Rectangle {
                id: mainViewSidePanelRect

                SplitView.minimumWidth: sidePanelViewStackPreferredWidth
                SplitView.maximumWidth: (sidePanelOnly ? splitView.width :
                                                      splitView.width - sidePanelViewStackPreferredWidth)
                SplitView.fillHeight: true

                // AccountComboBox is always visible
                AccountComboBox {
                    id: accountComboBox

                    anchors.top: mainViewSidePanelRect.top
                    width: mainViewSidePanelRect.width
                    height: 64

                    visible: (mainViewWindowSidePanel.visible || settingsMenu.visible)

                    currentIndex: 0

                    Connections {
                        target: AccountAdapter

                        function onUpdateConversationForAddedContact() {
                            MessagesAdapter.updateConversationForAddedContact()
                            mainViewWindowSidePanel.clearContactSearchBar()
                            mainViewWindowSidePanel.forceReselectConversationSmartListCurrentIndex()
                        }

                        function onAccountStatusChanged() {
                            accountComboBox.resetAccountListModel()
                        }
                    }

                    onSettingBtnClicked: {
                        toggleSettingsView()
                    }

                    Component.onCompleted: {
                        AccountAdapter.setQmlObject(this)
                    }
                }

                StackView {
                    id: sidePanelViewStack

                    initialItem: mainViewWindowSidePanel

                    anchors.top: accountComboBox.visible ? accountComboBox.bottom :
                                                           mainViewSidePanelRect.top
                    width: mainViewSidePanelRect.width
                    height: accountComboBox.visible ? mainViewSidePanelRect.height - accountComboBox.height :
                                                      mainViewSidePanelRect.height

                    clip: true
                }
            }

            StackView {
                id: mainViewStack

                initialItem: welcomePage

                SplitView.maximumWidth: sidePanelOnly ?
                                            splitView.width :
                                            splitView.width - sidePanelViewStackPreferredWidth
                SplitView.minimumWidth: sidePanelViewStackPreferredWidth
                SplitView.fillHeight: true

                clip: true
            }
        }

        WizardView {
            id: wizardView

            Layout.fillWidth: true
            Layout.fillHeight: true

            onNeedToShowMainViewWindow: {
                mainViewStackLayout.currentIndex = 0
                backToMainView()
            }

            onWizardViewIsClosed: {
                mainViewStackLayout.currentIndex = 0
                backToMainView()
            }
        }
    }

    AccountListModel {
        id: accountListModel
    }

    SettingsMenu {
        id: settingsMenu

        objectName: "settingsMenu"

        visible: false

        width: mainViewSidePanelRect.width
        height: mainViewSidePanelRect.height

        onItemSelected: {
            settingsView.setSelected(index)
            if (sidePanelOnly)
                sidePanelViewStack.push(settingsView, StackView.Immediate)
        }
    }

    SidePanel {
        id: mainViewWindowSidePanel

        Connections {
            target: ConversationsAdapter

            function onNavigateToWelcomePageRequested() {
                backToMainView()
            }
        }
    }

    CallStackView {
        id: callStackView

        visible: false
        objectName: "callStackViewObject"
    }

    WelcomePage {
        id: welcomePage
        visible: false
    }

    SettingsView {
        id: settingsView

        visible: false

        onSettingsViewWindowNeedToShowMainViewWindow: {
            AccountAdapter.accountChanged(0)
            toggleSettingsView()
        }

        onSettingsViewWindowNeedToShowNewWizardWindow: {
            mainViewWindow.noAccountIsAvailable()
        }

        onSettingsBackArrowClicked: sidePanelViewStack.pop(StackView.Immediate)
    }

    MessageWebView {
        id: communicationPageMessageWebView

        objectName: "communicationPageMessageWebView"

        signal toSendMessageContentSaved(string arg)
        signal toMessagesCleared
        signal toMessagesLoaded

        visible: false

        Connections {
            target: MessagesAdapter

            function onNeedToUpdateSmartList() {
                mainViewWindowSidePanel.forceUpdateConversationSmartListView()
            }

            function onNavigateToWelcomePageRequested() {
                backToMainView()
            }
        }

        Component.onCompleted: {
            recordBox.x = Qt.binding(function() {
                var i = ((mainViewStack.visible && mainViewStack.width > 1000) ?
                             Math.round((mainViewStack.width-1000)*0.5) :
                             0)
                return mainViewStack.visible ?
                            sidePanelViewStack.width + recordBox.x_offset + i :
                            recordBox.x_offset + i

            })

            recordBox.y = Qt.binding(function() {
                return mainViewStack.visible ? mainViewStack.height + recordBox.y_offset :
                                               sidePanelViewStack.height + recordBox.y_offset
            })

            // Set qml MessageWebView object pointer to c++.
            MessagesAdapter.setQmlObject(this)
        }
    }

    onWidthChanged: {
        // Hide unnecessary stackview when width is changed.
        var widthToCompare = sidePanelViewStackPreferredWidth +
                (inSettingsView ? settingsViewPreferredWidth : mainViewStackPreferredWidth)

        if (mainViewWindow.width < widthToCompare - onWidthChangedTriggerDistance
                && mainViewStack.visible) {
            mainViewStack.visible = false

            // The find callback function is called for each item in the stack.
            var inWelcomeViewStack = mainViewStack.find(
                        function (item, index) {
                            return index > 0
                        })

            if (inSettingsView) {
                mainViewStack.pop(StackView.Immediate)
                sidePanelViewStack.push(settingsView, StackView.Immediate)
            }
            else if (inWelcomeViewStack)
                recursionStackViewItemMove(mainViewStack, sidePanelViewStack)

            mainViewWindow.update()
        } else if (mainViewWindow.width >= widthToCompare + onWidthChangedTriggerDistance
                   && !mainViewStack.visible) {
            mainViewStack.visible = true

            var inSidePanelViewStack = sidePanelViewStack.find(
                        function (item, index) {
                            return index > 0
                        })

            if (inSettingsView) {
                if (sidePanelViewStack.currentItem.objectName !== settingsMenu.objectName)
                    sidePanelViewStack.pop(StackView.Immediate)
                mainViewStack.push(settingsView, StackView.Immediate)
            } else if (inSidePanelViewStack) {
                recursionStackViewItemMove(sidePanelViewStack, mainViewStack)
                if (currentAccountIsCalling())
                    pushCallStackView()
            }

            mainViewWindow.update()
        }
    }

    AboutPopUp {
        id: aboutPopUpDialog
    }

    WelcomePageQrDialog {
        id: qrDialog
    }

    RecordBox{
        id: recordBox
        visible: false
    }

    UserProfile {
        id: userProfile
    }

    onClosing: {
        close.accepted = false
        mainViewWindow.hide()
        mainViewWindow.closeApp()
    }

    Shortcut {
        sequence: "Ctrl+M"
        context: Qt.ApplicationShortcut
        onActivated: {
            if (!inSettingsView) {
                toggleSettingsView()
            }
            settingsMenu.btnMediaSettings.clicked()
        }
    }

    Shortcut {
        sequence: "Ctrl+G"
        context: Qt.ApplicationShortcut
        onActivated: {
            if (!inSettingsView) {
                toggleSettingsView()
            }
            settingsMenu.btnGeneralSettings.clicked()
        }
    }

    Shortcut {
        sequence: "Ctrl+I"
        context: Qt.ApplicationShortcut
        onActivated: {
            if (!inSettingsView) {
                toggleSettingsView()
            }
            settingsMenu.btnAccountSettings.clicked()
        }
    }

    Shortcut {
        sequence: "Ctrl+P"
        context: Qt.ApplicationShortcut
        onActivated: {
            if (!inSettingsView) {
                toggleSettingsView()
            }
            settingsMenu.btnPluginSettings.clicked()
        }
    }

    Shortcut {
        sequence: "F10"
        context: Qt.ApplicationShortcut
        onActivated: {
            shortcutsTable.open()
        }
    }

    Shortcut {
        sequence: "F11"
        context: Qt.ApplicationShortcut
        onActivated: {
            if (mainViewWindow.visibility !== 5) // 5 = FullScreen
                mainViewWindow.visibility = "FullScreen"
            else
                mainViewWindow.visibility = "Windowed"
        }
    }

    Shortcut {
        sequence: "Ctrl+D"
        context: Qt.ApplicationShortcut
        onActivated: CallAdapter.hangUpThisCall()
        onActivatedAmbiguously: CallAdapter.hangUpThisCall()
    }

    Shortcut {
        sequence: "Ctrl+Shift+A"
        context: Qt.ApplicationShortcut
        onActivated: {
            UtilsAdapter.makePermanentCurrentConv()
            communicationPageMessageWebView.setSendContactRequestButtonVisible(false)
        }
    }

    Shortcut {
        sequence: "Ctrl+Shift+N"
        context: Qt.ApplicationShortcut
        onActivated: startWizard()
    }

    KeyBoardShortcutTable {
        id: shortcutsTable
    }
}