diff --git a/src/app/MainApplicationWindow.qml b/src/app/MainApplicationWindow.qml index 782b091c..2b78a896 100644 --- a/src/app/MainApplicationWindow.qml +++ b/src/app/MainApplicationWindow.qml @@ -89,7 +89,7 @@ ApplicationWindow { !UtilsAdapter.getAccountListSize()) { // Save the window geometry and state before quitting. layoutManager.saveWindowSettings() - viewCoordinator.dismissAll() + viewCoordinator.deinit() Qt.quit() } else { layoutManager.closeToTray() @@ -160,11 +160,13 @@ ApplicationWindow { // Present the welcome view once the viewCoordinator is setup. viewCoordinator.initialized.connect(function() { + viewCoordinator.preload("SidePanel") + viewCoordinator.preload("SettingsSidePanel") viewCoordinator.present("WelcomePage") viewCoordinator.preload("ConversationView") }) // Set the viewCoordinator's root item. - viewCoordinator.setRootView(item) + viewCoordinator.init(item) } if (Qt.platform.os.toString() === "osx") { MainApplication.setEventFilter() diff --git a/src/app/ViewCoordinator.qml b/src/app/ViewCoordinator.qml index 6ea306cc..76f3034e 100644 --- a/src/app/ViewCoordinator.qml +++ b/src/app/ViewCoordinator.qml @@ -22,104 +22,54 @@ import QtQuick.Layouts import net.jami.Constants 1.1 import net.jami.Models 1.1 -// This object should be implemented as a QML singleton, or be instantiated -// once in the main application window component. The top-level application window -// contains a loader[mainview, wizardview] and "rootView" MUST parent a horizontal -// SplitView with a StackView in each pane. +import "commoncomponents" + QtObject { id: root required property QtObject viewManager + signal initialized signal requestAppWindowWizardView // A map of view names to file paths for QML files that define each view. property variant resources: { - "WelcomePage": "mainview/components/WelcomePage.qml", "SidePanel": "mainview/components/SidePanel.qml", + "WelcomePage": "mainview/components/WelcomePage.qml", "ConversationView": "mainview/ConversationView.qml", "NewSwarmPage": "mainview/components/NewSwarmPage.qml", "WizardView": "wizardview/WizardView.qml", "SettingsView": "settingsview/SettingsView.qml", + "SettingsSidePanel": "settingsview/SettingsSidePanel.qml", } - // Maybe this state needs to be toggled because the SidePanel content is replaced. - // This makes it so the state can't be inferred from loaded views in single pane mode. - property bool inSettings: viewManager.hasView("SettingsView") - property bool inWizard: viewManager.hasView("WizardView") - property bool inNewSwarm: viewManager.hasView("NewSwarmPage") - property bool inhibitConversationView: inSettings || inWizard || inNewSwarm - - property bool busy: false - // The `main` view of the application window. - property Item rootView: null + property StackView rootView - // HACKS. - property real mainViewWidth: rootView ? rootView.width : 0 - property real previousWidth: mainViewWidth - property real mainViewSidePanelRectWidth: sv1 ? sv1.width : 0 - property real lastSideBarSplitSize: mainViewSidePanelRectWidth - onMainViewWidthChanged: resolvePanes() + property var currentViewName: rootView && rootView.currentItem.objectName || null - function resolvePanes(force=false) { - if (forceSinglePane) return - const isExpanding = previousWidth < mainViewWidth - if (mainViewWidth < JamiTheme.chatViewHeaderMinimumWidth + mainViewSidePanelRectWidth - && sv2.visible && (!isExpanding || force)) { - lastSideBarSplitSize = mainViewSidePanelRectWidth - singlePane = true - } else if (mainViewWidth >= lastSideBarSplitSize + JamiTheme.chatViewHeaderMinimumWidth - && !sv2.visible && (isExpanding || force) && !layoutManager.isFullScreen) { - singlePane = false + function init(appWindow) { + rootView = Qt.createQmlObject(`import QtQuick; import QtQuick.Controls + StackView { anchors.fill: parent }`, + appWindow) + initialized() + } + + function deinit() { + viewManager.destroyAllViews() + rootView.destroy() + } + + // Finds a view and gets its index within the StackView it's in. + function getStackIndex(viewName) { + for (const [key, value] of Object.entries(viewManager.views)) { + if (value.objectName === viewName) { + return value.StackView.index + } } - previousWidth = mainViewWidth + return -1 } - // Must be the child of `rootView`. - property Item splitView: null - - // StackView objects, which are children of `splitView`. - property StackView sv1: null - property StackView sv2: null - - // The StackView object that is currently active, determined by the value - // of singlePane. - readonly property StackView activeStackView: singlePane ? sv1 : sv2 - - readonly property string currentViewName: { - if (activeStackView == null || activeStackView.depth === 0) return '' - return activeStackView.currentItem.objectName - } - - readonly property var currentView: { - return activeStackView ? activeStackView.currentItem : null - } - - // Handle single/dual pane mode. - property bool forceSinglePane: false - property bool singlePane - onForceSinglePaneChanged: { - if (forceSinglePane) singlePane = true - else resolvePanes(true) - } - - onSinglePaneChanged: { - // Hiding sv2 before moving items from, and after moving - // items to, reduces stack item visibility change events. - if (singlePane) { - sv2.visible = false - if (forceSinglePane) Qt.callLater(move, sv2, sv1) - else move(sv2, sv1) - } else { - move(sv1, sv2) - sv2.visible = true - } - } - - // Emitted once at the end of setRootView. - signal initialized() - // Create, present, and return a dialog object. function presentDialog(parent, path, props={}) { // Open the dialog once the object is created @@ -135,120 +85,42 @@ QtObject { }, props) } - // Dismiss all views. - function dismissAll() { - for (var path in viewManager.views) { - viewManager.destroyView(path) - } - } + // Present a view by name. + function present(viewName, props) { + const path = resources[viewName] - // Get a view regardless of whether it is currently active. - function getView(viewName) { - if (!viewManager.hasView(viewName)) { - return null - } - return viewManager.views[viewManager.viewPaths[viewName]] - } - - // Sets object references, onInitialized is a good time to present views. - function setRootView(obj) { - rootView = obj - splitView = rootView.splitView - sv1 = rootView.sv1 - sv1.parent = Qt.binding(() => singlePane ? rootView : splitView) - sv1.anchors.fill = Qt.binding(() => singlePane ? rootView : undefined) - sv2 = rootView.sv2 - - initialized() - resolvePanes() - } - - // Finds a view and gets its index within the StackView it's in. - function getStackIndex(viewName) { - for (const [key, value] of Object.entries(viewManager.views)) { - if (value.objectName === viewName) { - return value.StackView.index - } - } - return -1 - } - - // Load a view without presenting it. - function preload(viewName) { - if (!viewManager.createView(resources[viewName], null)) { - console.log("Failed to load view: " + viewName) - } - } - - // This function presents the view with the given viewName in the - // specified StackView. Return the view if successful. - function present(viewName, sv=activeStackView) { - if (!rootView) return - - if (viewName === "ConversationView" && inhibitConversationView) { + // Check if the current view should inhibit the presentation of this view. + if (rootView.currentItem && rootView.currentItem.inhibits.includes(viewName)) { + print("inhibiting view:", viewName) return } // If the view already exists in the StackView, the function will attempt // to navigate to its StackView position by dismissing elevated views. - if (sv.find(function(item) { + if (rootView.find(function(item) { return item.objectName === viewName; - })) { + }, StackView.DontLoad)) { const viewIndex = getStackIndex(viewName) - if (viewIndex >= 0) { - for (var i = (sv.depth - 1); i > viewIndex; i--) { - dismissObj(sv.get(i, StackView.DontLoad)) - } - return true + for (var i = (rootView.depth - 1); i > viewIndex; i--) { + dismissObj(rootView.get(i, StackView.DontLoad)) } - return false + return } - // If we are in single-pane mode and the view was previously forced into - // sv2, we can move it back to the top of sv1. - if (singlePane && sv === sv1) { - // See if the item is at the top of sv2 - if (sv2.currentItem && sv2.currentItem.objectName === viewName) { - // Move it to the top of sv1 - const view = sv2.pop(StackView.Immediate) - sv1.push(view, StackView.Immediate) - view.presented() - return view + if (!viewManager.createView(path, rootView, function(view) { + // push the view onto the stack if it's not already there + if (rootView.currentItem !== view) { + rootView.push(view, StackView.Immediate) } - } - - const obj = viewManager.createView(resources[viewName], appWindow) - if (!obj) { + if (!view.managed) view.presented() + }, props)) { print("could not create view:", viewName) - return null } - if (obj === currentView) { - print("view is current:", viewName) - return null - } - - // If we are in single-pane mode and the view should start hidden - // (requiresIndex), we can push it into sv2. - if (singlePane && sv === sv1 && obj.requiresIndex) { - sv = sv2 - } else { - forceSinglePane = obj.singlePaneOnly - } - - const view = sv.push(obj, StackView.Immediate) - if (!view) { - return null - } - if (view.objectName === '') { - view.objectName = viewName - } - view.presented() - return view } // Dismiss by object. - function dismissObj(obj, sv=activeStackView) { - if (obj.StackView.view !== sv) { + function dismissObj(obj) { + if (obj.StackView.view !== rootView) { print("view not in the stack:", obj) return } @@ -256,27 +128,28 @@ QtObject { // If we are dismissing a view that is not at the top of the stack, // we need to store each of the views on top into a temporary stack // and then restore them after the view is dismissed. - // So we get the index of the view we are dismissing. const viewIndex = obj.StackView.index var tempStack = [] - for (var i = (sv.depth - 1); i > viewIndex; i--) { - var item = sv.pop(StackView.Immediate) + for (var i = (rootView.depth - 1); i > viewIndex; i--) { + var item = rootView.pop(StackView.Immediate) tempStack.push(item) } // And we define a function to restore and resolve the views. var resolveStack = () => { for (var i = 0; i < tempStack.length; i++) { - sv.push(tempStack[i], StackView.Immediate) + rootView.push(tempStack[i], StackView.Immediate) } - - forceSinglePane = sv.currentItem.singlePaneOnly - sv.currentItem.presented() + if (rootView.depth > 0) rootView.currentItem.presented() } // Now we can dismiss the view at the top of the stack. - const depth = sv.depth - if (obj === sv.get(depth - 1, StackView.DontLoad)) { - var view = sv.pop(StackView.Immediate) + const depth = rootView.depth + if (obj === rootView.get(depth - 1, StackView.DontLoad)) { + var view + if (rootView.depth === 1) { + view = rootView.currentItem + rootView.clear() + } else view = rootView.pop(StackView.Immediate) if (!view) { print("could not pop view:", obj.objectName) resolveStack() @@ -290,51 +163,37 @@ QtObject { if (!viewManager.destroyView(resources[objectName])) { print("could not destroy view:", objectName) } - } else { - view.dismissed() - } + } else view.dismissed() } resolveStack() } - // Dismiss by view name. - function dismiss(viewName) { - if (!rootView) return - - const depth = activeStackView.depth - for (var i = (depth - 1); i >= 0; i--) { - const view = activeStackView.get(i, StackView.DontLoad) - if (view.objectName === viewName) { - dismissObj(view) - return + // Dismiss a view by name or the top view if unspecified. + function dismiss(viewName=undefined) { + if (!rootView || rootView.depth === 0) return + if (viewName !== undefined) { + const depth = rootView.depth + for (var i = (depth - 1); i >= 0; i--) { + const view = rootView.get(i, StackView.DontLoad) + if (view.objectName === viewName) { + dismissObj(view) + return + } } - } - - // Check if the view is hidden on the top of sv2 (if in single-pane mode), - // and dismiss it in that case. - if (singlePane && sv2.currentItem && sv2.currentItem.objectName === viewName) { - dismissObj(sv2.currentItem, sv2) + return + } else { + dismissObj(rootView.currentItem) } } - // Move items from one stack to another. We avoid the recursive technique to - // avoid visibility change events. - function move(from, to, depth=1) { - busy = true - var tempStack = [] - while (from.depth > depth) { - var item = from.pop(StackView.Immediate) - tempStack.push(item) - } - while (tempStack.length) { - to.push(tempStack.pop(), StackView.Immediate) - } - busy = false + function getView(viewName) { + return viewManager.getView(viewName) } - // Effectively hide the current view by moving it to the other StackView. - // This function only works when in single-pane mode. - function hideCurrentView() { - if (singlePane) move(sv1, sv2) - } + // Load a view without presenting it. + function preload(viewName) { + if (!viewManager.createView(resources[viewName], null)) { + console.log("Failed to load view: " + viewName) + } + } } diff --git a/src/app/ViewManager.qml b/src/app/ViewManager.qml index b659fd25..3cf03d6c 100644 --- a/src/app/ViewManager.qml +++ b/src/app/ViewManager.qml @@ -27,9 +27,19 @@ QtObject { // The number of views. property int nViews: 0 + // Destroy all views. + function destroyAllViews() { + for (var path in views) { + destroyView(path) + } + } + function createView(path, parent=null, cb=null, props={}) { if (views[path] !== undefined) { // an instance of already exists + if (cb !== null) { + cb(views[path]) + } return views[path] } @@ -84,4 +94,8 @@ QtObject { function hasView(viewName) { return nViews && viewPaths[viewName] !== undefined } + + function getView(viewName) { + return views[viewPaths[viewName]] || null + } } diff --git a/src/app/appsettingsmanager.cpp b/src/app/appsettingsmanager.cpp index 5ee0d1df..93f9653e 100644 --- a/src/app/appsettingsmanager.cpp +++ b/src/app/appsettingsmanager.cpp @@ -40,9 +40,9 @@ AppSettingsManager::AppSettingsManager(QObject* parent) } QVariant -AppSettingsManager::getValue(const Settings::Key key) +AppSettingsManager::getValue(const QString& key, const QVariant& defaultValue) { - auto value = settings_->value(Settings::toString(key), Settings::defaultValue(key)); + auto value = settings_->value(key, defaultValue); if (QString(value.typeName()) == "QString" && (value.toString() == "false" || value.toString() == "true")) @@ -51,10 +51,22 @@ AppSettingsManager::getValue(const Settings::Key key) return value; } +void +AppSettingsManager::setValue(const QString& key, const QVariant& value) +{ + settings_->setValue(key, value); +} + +QVariant +AppSettingsManager::getValue(const Settings::Key key) +{ + return getValue(Settings::toString(key), Settings::defaultValue(key)); +} + void AppSettingsManager::setValue(const Settings::Key key, const QVariant& value) { - settings_->setValue(Settings::toString(key), value); + setValue(Settings::toString(key), value); } QString diff --git a/src/app/appsettingsmanager.h b/src/app/appsettingsmanager.h index 1425440f..79279382 100644 --- a/src/app/appsettingsmanager.h +++ b/src/app/appsettingsmanager.h @@ -109,8 +109,12 @@ public: explicit AppSettingsManager(QObject* parent = nullptr); ~AppSettingsManager() = default; + Q_INVOKABLE QVariant getValue(const QString& key, const QVariant& defaultValue = {}); + Q_INVOKABLE void setValue(const QString& key, const QVariant& value = {}); + Q_INVOKABLE QVariant getValue(const Settings::Key key); - Q_INVOKABLE void setValue(const Settings::Key key, const QVariant& value); + Q_INVOKABLE void setValue(const Settings::Key key, const QVariant& value = {}); + QString getLanguage(); void loadTranslations(); diff --git a/src/app/commoncomponents/BaseView.qml b/src/app/commoncomponents/BaseView.qml index cdd51ce9..d535b4c6 100644 --- a/src/app/commoncomponents/BaseView.qml +++ b/src/app/commoncomponents/BaseView.qml @@ -25,12 +25,8 @@ Rectangle { // only be destroyed when its parent is destroyed. property bool managed: true - // True if this view functions in a single-pane context only. - property bool singlePaneOnly: false - - // True if this view requires and initial selection from - // a group of menu options when in single-pane mode (e.g. settings). - property bool requiresIndex: false + // A list of view names that this view inhibits the presentation of. + property var inhibits: [] function dismiss() { viewCoordinator.dismiss(objectName) } diff --git a/src/app/commoncomponents/DualPaneView.qml b/src/app/commoncomponents/DualPaneView.qml new file mode 100644 index 00000000..a4dc8ce8 --- /dev/null +++ b/src/app/commoncomponents/DualPaneView.qml @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2023 Savoir-faire Linux Inc. + * + * 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 . + */ + +import QtQuick +import QtQuick.Controls + +import net.jami.Adapters 1.1 +import net.jami.Constants 1.1 + +BaseView { + id: viewNode + + required property Item leftPaneItem + required property Item rightPaneItem + + property alias leftPane: leftPane + property alias rightPane: rightPane + + property alias splitViewStateKey: splitView.splitViewStateKey + + property real leftPaneMinWidth: JamiTheme.mainViewLeftPaneMinWidth + property real rightPaneMinWidth: JamiTheme.mainViewPaneMinWidth + + property bool isSinglePane + + onPresented: { + leftPaneItem.parent = leftPane + rightPaneItem.parent = rightPane + + splitView.restoreSplitViewState() + + resolvePanes() + } + onDismissed: splitView.saveSplitViewState() + + Component.onCompleted: { + // Avoid double triggering this handler during instantiation. + onIsSinglePaneChanged.connect(isSinglePaneChangedHandler) + } + + property real previousLeftPaneWidth: leftPane.width + onWidthChanged: resolvePanes() + function resolvePanes() { + isSinglePane = width < rightPaneMinWidth + previousLeftPaneWidth + } + + // Override this if needed. + property var isSinglePaneChangedHandler: function() { + rightPaneItem.parent = isSinglePane ? leftPane : rightPane + } + + JamiSplitView { + id: splitView + anchors.fill: parent + splitViewStateKey: viewNode.objectName + + Item { + id: leftPane + onWidthChanged: if (!isSinglePane) previousLeftPaneWidth = width + SplitView.minimumWidth: isSinglePane ? + viewNode.width : + viewNode.leftPaneMinWidth + SplitView.maximumWidth: isSinglePane ? + viewNode.width : + viewNode.width - rightPaneMinWidth + SplitView.preferredWidth: viewNode.leftPaneMinWidth + clip: true + } + Item { + id: rightPane + clip: true + } + } +} diff --git a/src/app/commoncomponents/JamiSplitView.qml b/src/app/commoncomponents/JamiSplitView.qml new file mode 100644 index 00000000..f7fc6d8a --- /dev/null +++ b/src/app/commoncomponents/JamiSplitView.qml @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2023 Savoir-faire Linux Inc. + * + * 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 . + */ + +import QtQuick +import QtQuick.Controls + +import net.jami.Adapters 1.1 +import net.jami.Constants 1.1 + +SplitView { + id: root + + property string splitViewStateKey: objectName + property bool autoManageState: !(parent instanceof BaseView) + + function saveSplitViewState() { + UtilsAdapter.setAppValue("sv_" + splitViewStateKey, root.saveState()) + } + + function restoreSplitViewState() { + root.restoreState(UtilsAdapter.getAppValue("sv_" + splitViewStateKey)) + } + + onResizingChanged: if (!resizing) saveSplitViewState() + onVisibleChanged: { + if (!autoManageState) return + visible ? restoreSplitViewState() : saveSplitViewState() + } + + handle: Rectangle { + implicitWidth: JamiTheme.splitViewHandlePreferredWidth + implicitHeight: root.height + color: JamiTheme.primaryBackgroundColor + Rectangle { + implicitWidth: 1 + implicitHeight: root.height + color: JamiTheme.tabbarBorderColor + } + } +} diff --git a/src/app/commoncomponents/ListSelectionView.qml b/src/app/commoncomponents/ListSelectionView.qml new file mode 100644 index 00000000..186d20f5 --- /dev/null +++ b/src/app/commoncomponents/ListSelectionView.qml @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2023 Savoir-faire Linux Inc. + * + * 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 . + */ + +import QtQuick + +DualPaneView { + id: viewNode + + property bool hideRightPaneInSinglePaneMode : false + + Component.onCompleted: { + if (hideRightPaneInSinglePaneMode) return + onIndexChanged.connect(function() { + if (hasValidSelection) { + if (selectionFallback && isSinglePane) + rightPaneItem.parent = leftPane + return + } + if (!isSinglePane) dismiss() + else isSinglePaneChangedHandler() + }) + } + + // True if we should dismiss to the left pane if in single pane mode. + // Also causes selection of a default index (0) in dual pane mode. + property bool selectionFallback: false + + // When this property is set, the view updates its display to show the + // corresponding item if `hasValidSelection` has no override. + property int index: -1 + function selectIndex(index) { viewNode.index = index } + + // Override this predicate if needed. + property bool hasValidSelection: viewNode.index >= 0 + onHasValidSelectionChanged: isSinglePaneChangedHandler() + + // Override BaseView.dismiss with some selection logic. + function dismiss() { + if (isSinglePane) { + if (!selectionFallback) viewCoordinator.dismiss(objectName) + else if (isSinglePane && leftPane.children.length > 1) { + rightPaneItem.parent = null + leftPaneItem.deselect() + } + } else viewCoordinator.dismiss(objectName) + } + + onPresented: isSinglePaneChangedHandler() + + onDismissed: { + if (leftPaneItem) { + leftPaneItem.indexSelected.disconnect(selectIndex) + leftPaneItem.deselect() + } + } + + onLeftPaneItemChanged: { + if (leftPaneItem) leftPaneItem.indexSelected.connect(selectIndex) + } + isSinglePaneChangedHandler: () => { + if (hideRightPaneInSinglePaneMode) return + // When transitioning from split to single pane, we need to move + // the right pane item to left stack view if it has a valid index. + if (isSinglePane) { + if (hasValidSelection) { + rightPaneItem.parent = leftPane + } + } else { + rightPaneItem.parent = rightPane + // We may need a default selection of item 0 here. + if (!hasValidSelection && selectionFallback) leftPaneItem.select(0) + } + } +} diff --git a/src/app/commoncomponents/ModalTextEdit.qml b/src/app/commoncomponents/ModalTextEdit.qml index 7a58ee45..d6ddd6ed 100644 --- a/src/app/commoncomponents/ModalTextEdit.qml +++ b/src/app/commoncomponents/ModalTextEdit.qml @@ -17,6 +17,7 @@ import QtQuick +import net.jami.Adapters 1.1 import net.jami.Constants 1.1 // This component is used to display and edit a value. @@ -32,6 +33,7 @@ Loader { required property string placeholderText property string staticText: "" property string dynamicText + property bool inputIsValid: true property string infoTipText property bool isPersistent: true diff --git a/src/app/commoncomponents/SidePanelBase.qml b/src/app/commoncomponents/SidePanelBase.qml new file mode 100644 index 00000000..7bfc76ff --- /dev/null +++ b/src/app/commoncomponents/SidePanelBase.qml @@ -0,0 +1,14 @@ +import QtQuick + +Rectangle { + id: root + + anchors.fill: parent + + // Override these if needed. + property var select: function() {} + property var deselect: function() {} + + signal indexSelected(int index) + signal deselected +} diff --git a/src/app/constant/JamiTheme.qml b/src/app/constant/JamiTheme.qml index 63ccdf68..7f2a8abe 100644 --- a/src/app/constant/JamiTheme.qml +++ b/src/app/constant/JamiTheme.qml @@ -290,6 +290,8 @@ Item { // Sizes + property real mainViewLeftPaneMinWidth: 300 + property real mainViewPaneMinWidth: 430 property real qrCodeImageSize: 256 property real splitViewHandlePreferredWidth: 4 property real indicatorFontSize: calcSize(6) @@ -421,7 +423,6 @@ Item { property real chatViewHairLineSize: 1 property real chatViewMaximumWidth: 900 property real chatViewHeaderPreferredHeight: 64 - property real chatViewHeaderMinimumWidth: 430 property real chatViewFooterPreferredHeight: 50 property real chatViewFooterMaximumHeight: 280 property real chatViewFooterRowSpacing: 1 diff --git a/src/app/currentconversation.h b/src/app/currentconversation.h index 4c3a4eb5..08f09c05 100644 --- a/src/app/currentconversation.h +++ b/src/app/currentconversation.h @@ -69,6 +69,7 @@ public: CurrentConversationMembers* uris() const; Q_SIGNALS: + void reloadInteractions(); void scrollTo(const QString& msgId); void showDetails(); diff --git a/src/app/mainview/ConversationView.qml b/src/app/mainview/ConversationView.qml index f96de59f..57be86d1 100644 --- a/src/app/mainview/ConversationView.qml +++ b/src/app/mainview/ConversationView.qml @@ -27,15 +27,19 @@ import net.jami.Constants 1.1 import "../commoncomponents" import "components" -BaseView { - id: root +ListSelectionView { + id: viewNode objectName: "ConversationView" managed: false - onPresented: { - if (!visible && viewCoordinator.singlePane && - CurrentConversation.id !== '') { - viewCoordinator.present(objectName) + splitViewStateKey: "Main" + hasValidSelection: CurrentConversation.id !== '' + + Connections { + target: CurrentConversation + function onReloadInteractions() { + UtilsAdapter.clearInteractionsCache(CurrentAccount.id, CurrentConversation.id) + MessagesAdapter.loadMoreMessages() } } @@ -54,13 +58,15 @@ BaseView { color: JamiTheme.transparentColor - StackLayout { + leftPaneItem: viewCoordinator.getView("SidePanel") + + rightPaneItem: StackLayout { currentIndex: !CurrentConversation.hasCall ? 0 : 1 - onCurrentIndexChanged: chatView.parent = currentIndex == 1 ? + onCurrentIndexChanged: chatView.parent = currentIndex === 1 ? callStackView.chatViewContainer : chatViewContainer - anchors.fill: root + anchors.fill: parent Item { id: chatViewContainer @@ -86,7 +92,7 @@ BaseView { onDismiss: { if (parent == chatViewContainer) { - root.dismiss() + viewNode.dismiss() } else { callStackView.chatViewContainer.visible = false callStackView.contentView.forceActiveFocus() diff --git a/src/app/mainview/MainView.qml b/src/app/mainview/MainView.qml index 6d347630..9f55a972 100644 --- a/src/app/mainview/MainView.qml +++ b/src/app/mainview/MainView.qml @@ -59,48 +59,6 @@ Rectangle { onWidthChanged: Qt.callLater(JamiQmlUtils.updateMessageBarButtonsPoints) onHeightChanged: Qt.callLater(JamiQmlUtils.updateMessageBarButtonsPoints) - // Needed by ViewCoordinator. - property alias splitView: splitView - property alias sv1: sv1 - property alias sv2: sv2 - - StackView { - id: mainStackView - anchors.fill: parent - - initialItem: SplitView { - id: splitView - - handle: Rectangle { - implicitWidth: JamiTheme.splitViewHandlePreferredWidth - implicitHeight: splitView.height - color: JamiTheme.primaryBackgroundColor - Rectangle { - implicitWidth: 1 - implicitHeight: splitView.height - color: JamiTheme.tabbarBorderColor - } - } - - StackView { - id: sv1 - objectName: "sv1" - SplitView.minimumWidth: 300 - SplitView.preferredWidth: 300 - SplitView.fillHeight: true - clip: true - initialItem: SidePanel {} - } - - StackView { - id: sv2 - objectName: "sv2" - SplitView.fillHeight: true - clip: true - } - } - } - Component.onCompleted: { JamiQmlUtils.mainViewRectObj = mainView } diff --git a/src/app/mainview/components/AccountComboBox.qml b/src/app/mainview/components/AccountComboBox.qml index 4b72f25f..adea2fc1 100644 --- a/src/app/mainview/components/AccountComboBox.qml +++ b/src/app/mainview/components/AccountComboBox.qml @@ -30,9 +30,13 @@ import "../../commoncomponents" Label { id: root - signal settingBtnClicked property alias popup: comboBoxPopup + width: parent ? parent.width : o + height: JamiTheme.accountListItemHeight + + property bool inSettings: viewCoordinator.currentViewName === "SettingsView" + // TODO: remove these refresh hacks use QAbstractItemModels correctly Connections { target: AccountAdapter @@ -86,6 +90,7 @@ Label { MouseArea { id: mouseArea + enabled: visible anchors.fill: parent hoverEnabled: true onClicked: { @@ -206,18 +211,20 @@ Label { id: settingsButton anchors.verticalCenter: parent.verticalCenter - source: !viewCoordinator.inSettings ? + source: !inSettings ? JamiResources.settings_24dp_svg : JamiResources.round_close_24dp_svg normalColor: JamiTheme.backgroundColor imageColor: JamiTheme.textColor - toolTipText: !viewCoordinator.inSettings ? + toolTipText: !inSettings ? JamiStrings.openSettings : JamiStrings.closeSettings onClicked: { - settingBtnClicked() + !inSettings ? + viewCoordinator.present("SettingsView") : + viewCoordinator.dismiss("SettingsView") background.state = "normal" } } diff --git a/src/app/mainview/components/ChatView.qml b/src/app/mainview/components/ChatView.qml index 8b968c54..5ffc835b 100644 --- a/src/app/mainview/components/ChatView.qml +++ b/src/app/mainview/components/ChatView.qml @@ -110,7 +110,7 @@ Rectangle { Layout.fillWidth: true Layout.preferredHeight: JamiTheme.chatViewHeaderPreferredHeight Layout.maximumHeight: JamiTheme.chatViewHeaderPreferredHeight - Layout.minimumWidth: JamiTheme.chatViewHeaderMinimumWidth + Layout.minimumWidth: JamiTheme.mainViewPaneMinWidth DropArea { anchors.fill: parent @@ -125,7 +125,7 @@ Rectangle { if (!swarmDetailsPanel.visible && !messagesResearchPanel.visible) { chatContents.visible = true } else { - if (chatViewHeader.width - JamiTheme.detailsPageMinWidth < JamiTheme.chatViewHeaderMinimumWidth) + if (chatViewHeader.width - JamiTheme.detailsPageMinWidth < JamiTheme.mainViewPaneMinWidth) chatContents.visible = false } } @@ -157,7 +157,7 @@ Rectangle { if (!swarmDetailsPanel.visible && !addMemberPanel.visible && !messagesResearchPanel.visible) return - if (chatViewHeader.width < JamiTheme.detailsPageMinWidth + JamiTheme.chatViewHeaderMinimumWidth + if (chatViewHeader.width < JamiTheme.detailsPageMinWidth + JamiTheme.mainViewPaneMinWidth && !isExpanding && chatContents.visible) { lastContentsSplitSize = chatContents.width lastDetailsSplitSize = Math.min(JamiTheme.detailsPageMinWidth, (swarmDetailsPanel.visible @@ -166,7 +166,7 @@ Rectangle { ? addMemberPanel.width : messagesResearchPanel.width)) chatContents.visible = false - } else if (chatViewHeader.width >= JamiTheme.chatViewHeaderMinimumWidth + lastDetailsSplitSize + } else if (chatViewHeader.width >= JamiTheme.mainViewPaneMinWidth + lastDetailsSplitSize && isExpanding && !layoutManager.isFullScreen && !chatContents.visible) { chatContents.visible = true } @@ -199,7 +199,7 @@ Rectangle { if (addMemberPanel.visible) { chatContents.visible = true } else { - if (chatViewHeader.width - JamiTheme.detailsPageMinWidth < JamiTheme.chatViewHeaderMinimumWidth) + if (chatViewHeader.width - JamiTheme.detailsPageMinWidth < JamiTheme.mainViewPaneMinWidth) chatContents.visible = false } addMemberPanel.visible = !addMemberPanel.visible @@ -277,19 +277,19 @@ Rectangle { handle: Rectangle { implicitWidth: JamiTheme.splitViewHandlePreferredWidth - implicitHeight: viewCoordinator.splitView.height + implicitHeight: root.height color: JamiTheme.primaryBackgroundColor Rectangle { implicitWidth: 1 - implicitHeight: viewCoordinator.splitView.height + implicitHeight: root.height color: JamiTheme.tabbarBorderColor } } ColumnLayout { id: chatContents - SplitView.maximumWidth: viewCoordinator.splitView.width - SplitView.minimumWidth: JamiTheme.chatViewHeaderMinimumWidth + SplitView.maximumWidth: root.width + SplitView.minimumWidth: JamiTheme.mainViewPaneMinWidth SplitView.fillWidth: true StackLayout { @@ -362,7 +362,7 @@ Rectangle { id: messagesResearchPanel visible: false - SplitView.maximumWidth: viewCoordinator.splitView.width + SplitView.maximumWidth: root.width SplitView.minimumWidth: JamiTheme.detailsPageMinWidth SplitView.preferredWidth: JamiTheme.detailsPageMinWidth } @@ -371,7 +371,7 @@ Rectangle { id: swarmDetailsPanel visible: false - SplitView.maximumWidth: viewCoordinator.splitView.width + SplitView.maximumWidth: root.width SplitView.preferredWidth: JamiTheme.detailsPageMinWidth SplitView.minimumWidth: JamiTheme.detailsPageMinWidth } @@ -380,7 +380,7 @@ Rectangle { id: addMemberPanel visible: false - SplitView.maximumWidth: viewCoordinator.splitView.width + SplitView.maximumWidth: root.width SplitView.preferredWidth: JamiTheme.detailsPageMinWidth SplitView.minimumWidth: JamiTheme.detailsPageMinWidth } diff --git a/src/app/mainview/components/NewSwarmPage.qml b/src/app/mainview/components/NewSwarmPage.qml index 3f6aa80e..9cba1623 100644 --- a/src/app/mainview/components/NewSwarmPage.qml +++ b/src/app/mainview/components/NewSwarmPage.qml @@ -26,10 +26,9 @@ import net.jami.Constants 1.1 import "../../commoncomponents" -BaseView { - id: root - - color: JamiTheme.chatviewBgColor +DualPaneView { + id: viewNode + objectName: "NewSwarmPage" signal createSwarmClicked(string title, string description, string avatar) signal removeMember(string convId, string member) @@ -42,188 +41,199 @@ BaseView { property var members: [] - RowLayout { - id: labelsMember - Layout.topMargin: 16 - Layout.preferredWidth: root.width - Layout.preferredHeight: childrenRect.height - spacing: 16 - visible: root.members.length + splitViewStateKey: "Main" + inhibits: ["ConversationView"] - Label { - text: JamiStrings.to - font.bold: true - color: JamiTheme.textColor - Layout.leftMargin: 16 - } + leftPaneItem: viewCoordinator.getView("SidePanel") + rightPaneItem: Rectangle { + id: root + color: JamiTheme.chatviewBgColor - Flow { + anchors.fill: parent + + RowLayout { + id: labelsMember Layout.topMargin: 16 - Layout.preferredWidth: root.width - 80 - Layout.preferredHeight: childrenRect.height + 16 - spacing: 8 + Layout.preferredWidth: root.width + Layout.preferredHeight: childrenRect.height + spacing: 16 + visible: viewNode.members.length - Repeater { - id: repeater + Label { + text: JamiStrings.to + font.bold: true + color: JamiTheme.textColor + Layout.leftMargin: 16 + } - delegate: Rectangle { - id: delegate - radius: (delegate.height + 12) / 2 - width: label.width + 36 - height: label.height + 12 + Flow { + Layout.topMargin: 16 + Layout.preferredWidth: root.width - 80 + Layout.preferredHeight: childrenRect.height + 16 + spacing: 8 - RowLayout { - anchors.centerIn: parent + Repeater { + id: repeater - Label { - id: label - text: UtilsAdapter.getBestNameForUri(CurrentAccount.id, modelData.uri) - color: JamiTheme.textColor - Layout.leftMargin: 8 + delegate: Rectangle { + id: delegate + radius: (delegate.height + 12) / 2 + width: label.width + 36 + height: label.height + 12 + + RowLayout { + anchors.centerIn: parent + + Label { + id: label + text: UtilsAdapter.getBestNameForUri(CurrentAccount.id, modelData.uri) + color: JamiTheme.textColor + Layout.leftMargin: 8 + } + + PushButton { + id: removeUserBtn + + preferredSize: 24 + + source: JamiResources.round_close_24dp_svg + toolTipText: JamiStrings.removeMember + + normalColor: "transparent" + imageColor: "transparent" + + onClicked: removeMember(modelData.convId, modelData.uri) + } } - PushButton { - id: removeUserBtn + color: JamiTheme.selectedColor + } + model: viewNode.members + } + } + } - preferredSize: 24 + Rectangle { + anchors.top: labelsMember.bottom + visible: labelsMember.visible + height: 1 + width: root.width + color: "transparent" + border.width: 1 + border.color: JamiTheme.selectedColor + } - source: JamiResources.round_close_24dp_svg - toolTipText: JamiStrings.removeMember + ColumnLayout { + id: mainLayout + objectName: "mainLayout" + anchors.centerIn: root - normalColor: "transparent" - imageColor: "transparent" + PhotoboothView { + id: currentAccountAvatar - onClicked: root.removeMember(modelData.convId, modelData.uri) + Layout.alignment: Qt.AlignCenter + darkTheme: UtilsAdapter.luma(root.color) + width: avatarSize + height: avatarSize + + newItem: true + imageId: root.visible ? "temp" : "" + avatarSize: 180 + buttonSize: JamiTheme.smartListAvatarSize + } + + EditableLineEdit { + id: title + objectName: "titleLineEdit" + Layout.alignment: Qt.AlignCenter + Layout.topMargin: JamiTheme.preferredMarginSize + Layout.preferredWidth: JamiTheme.preferredFieldWidth + + font.pointSize: JamiTheme.titleFontSize + + verticalAlignment: Text.AlignVCenter + + placeholderText: JamiStrings.swarmName + tooltipText: JamiStrings.swarmName + backgroundColor: root.color + color: UtilsAdapter.luma(backgroundColor) ? + JamiTheme.chatviewTextColorLight : + JamiTheme.chatviewTextColorDark + placeholderTextColor: { + if (editable) { + if (UtilsAdapter.luma(root.color)) { + return JamiTheme.placeholderTextColorWhite + } else { + return JamiTheme.placeholderTextColor + } + } else { + if (UtilsAdapter.luma(root.color)) { + return JamiTheme.chatviewTextColorLight + } else { + return JamiTheme.chatviewTextColorDark } } - - color: JamiTheme.selectedColor } - model: root.members } - } - } - Rectangle { - anchors.top: labelsMember.bottom - visible: labelsMember.visible - height: 1 - width: root.width - color: "transparent" - border.width: 1 - border.color: JamiTheme.selectedColor - } + EditableLineEdit { + id: description + objectName: "descriptionLineEdit" + Layout.alignment: Qt.AlignCenter + Layout.topMargin: JamiTheme.preferredMarginSize + Layout.preferredWidth: JamiTheme.preferredFieldWidth - ColumnLayout { - id: mainLayout - objectName: "mainLayout" - anchors.centerIn: root + font.pointSize: JamiTheme.menuFontSize - PhotoboothView { - id: currentAccountAvatar + verticalAlignment: Text.AlignVCenter - Layout.alignment: Qt.AlignCenter - darkTheme: UtilsAdapter.luma(root.color) - width: avatarSize - height: avatarSize - - newItem: true - imageId: root.visible ? "temp" : "" - avatarSize: 180 - buttonSize: JamiTheme.smartListAvatarSize - } - - EditableLineEdit { - id: title - objectName: "titleLineEdit" - Layout.alignment: Qt.AlignCenter - Layout.topMargin: JamiTheme.preferredMarginSize - Layout.preferredWidth: JamiTheme.preferredFieldWidth - - font.pointSize: JamiTheme.titleFontSize - - verticalAlignment: Text.AlignVCenter - - placeholderText: JamiStrings.swarmName - tooltipText: JamiStrings.swarmName - backgroundColor: root.color - color: UtilsAdapter.luma(backgroundColor) ? - JamiTheme.chatviewTextColorLight : - JamiTheme.chatviewTextColorDark - placeholderTextColor: { - if (editable) { - if (UtilsAdapter.luma(root.color)) { - return JamiTheme.placeholderTextColorWhite + placeholderText: JamiStrings.addADescription + tooltipText: JamiStrings.addADescription + backgroundColor: root.color + color: UtilsAdapter.luma(backgroundColor) ? + JamiTheme.chatviewTextColorLight : + JamiTheme.chatviewTextColorDark + placeholderTextColor: { + if (editable) { + if (UtilsAdapter.luma(root.color)) { + return JamiTheme.placeholderTextColorWhite + } else { + return JamiTheme.placeholderTextColor + } } else { - return JamiTheme.placeholderTextColor - } - } else { - if (UtilsAdapter.luma(root.color)) { - return JamiTheme.chatviewTextColorLight - } else { - return JamiTheme.chatviewTextColorDark + if (UtilsAdapter.luma(root.color)) { + return JamiTheme.chatviewTextColorLight + } else { + return JamiTheme.chatviewTextColorDark + } } } } - } - EditableLineEdit { - id: description - objectName: "descriptionLineEdit" - Layout.alignment: Qt.AlignCenter - Layout.topMargin: JamiTheme.preferredMarginSize - Layout.preferredWidth: JamiTheme.preferredFieldWidth + MaterialButton { + id: btnCreateSwarm - font.pointSize: JamiTheme.menuFontSize - - verticalAlignment: Text.AlignVCenter - - placeholderText: JamiStrings.addADescription - tooltipText: JamiStrings.addADescription - backgroundColor: root.color - color: UtilsAdapter.luma(backgroundColor) ? - JamiTheme.chatviewTextColorLight : - JamiTheme.chatviewTextColorDark - placeholderTextColor: { - if (editable) { - if (UtilsAdapter.luma(root.color)) { - return JamiTheme.placeholderTextColorWhite - } else { - return JamiTheme.placeholderTextColor - } - } else { - if (UtilsAdapter.luma(root.color)) { - return JamiTheme.chatviewTextColorLight - } else { - return JamiTheme.chatviewTextColorDark - } + TextMetrics { + id: textSize + font.weight: Font.Bold + font.pixelSize: JamiTheme.wizardViewButtonFontPixelSize + font.capitalization: Font.AllUppercase + text: btnCreateSwarm.text } + + Layout.alignment: Qt.AlignCenter + Layout.topMargin: JamiTheme.preferredMarginSize + autoAccelerator: true + + preferredWidth: textSize.width + 2 * JamiTheme.buttontextWizzardPadding + + primary: true + text: JamiStrings.createTheSwarm + + onClicked: createSwarmClicked(title.text, + description.text, + UtilsAdapter.tempCreationImage()) } } - - MaterialButton { - id: btnCreateSwarm - - TextMetrics{ - id: textSize - font.weight: Font.Bold - font.pixelSize: JamiTheme.wizardViewButtonFontPixelSize - font.capitalization: Font.AllUppercase - text: btnCreateSwarm.text - } - - Layout.alignment: Qt.AlignCenter - Layout.topMargin: JamiTheme.preferredMarginSize - autoAccelerator: true - - preferredWidth: textSize.width + 2*JamiTheme.buttontextWizzardPadding - - primary: true - text: JamiStrings.createTheSwarm - - onClicked: createSwarmClicked(title.text, - description.text, - UtilsAdapter.tempCreationImage()) - } } } diff --git a/src/app/mainview/components/OngoingCallPage.qml b/src/app/mainview/components/OngoingCallPage.qml index 2e68c30f..53798fd3 100644 --- a/src/app/mainview/components/OngoingCallPage.qml +++ b/src/app/mainview/components/OngoingCallPage.qml @@ -119,7 +119,7 @@ Rectangle { } onWidthChanged: { - if (chatViewContainer.visible && root.width < JamiTheme.chatViewHeaderMinimumWidth * 2) { + if (chatViewContainer.visible && root.width < JamiTheme.mainViewPaneMinWidth * 2) { callPageMainRect.visible = false } else { callPageMainRect.visible = true @@ -146,7 +146,7 @@ Rectangle { id: callPageMainRect SplitView.preferredHeight: mainColumnLayout.isHorizontal ? root.height : (root.height / 3) * 2 - SplitView.minimumWidth: JamiTheme.chatViewHeaderMinimumWidth + SplitView.minimumWidth: JamiTheme.mainViewPaneMinWidth SplitView.fillWidth: true TapHandler { @@ -404,15 +404,15 @@ Rectangle { root.height : root.height / 3 SplitView.preferredWidth: mainColumnLayout.isHorizontal ? - JamiTheme.chatViewHeaderMinimumWidth : + JamiTheme.mainViewPaneMinWidth : root.width - SplitView.minimumWidth: JamiTheme.chatViewHeaderMinimumWidth + SplitView.minimumWidth: JamiTheme.mainViewPaneMinWidth visible: false clip: true property bool showDetails: false onVisibleChanged: { - if (visible && root.width < JamiTheme.chatViewHeaderMinimumWidth * 2) { + if (visible && root.width < JamiTheme.mainViewPaneMinWidth * 2) { callPageMainRect.visible = false } else { callPageMainRect.visible = true diff --git a/src/app/mainview/components/SidePanel.qml b/src/app/mainview/components/SidePanel.qml index 721b1ec3..d5eeb059 100644 --- a/src/app/mainview/components/SidePanel.qml +++ b/src/app/mainview/components/SidePanel.qml @@ -29,7 +29,7 @@ import net.jami.Models 1.1 import "../../commoncomponents" import "../../settingsview/components" -BaseView { +SidePanelBase { id: root objectName: "SidePanel" @@ -67,9 +67,9 @@ BaseView { } function toggleCreateSwarmView() { - if (!viewCoordinator.inNewSwarm) { + if (!inNewSwarm) { viewCoordinator.present("NewSwarmPage") - const newSwarmPage = viewCoordinator.currentView + const newSwarmPage = viewCoordinator.getView("NewSwarmPage") newSwarmPage.removeMember.connect((convId, member) => { removeMember(convId, member) }) @@ -84,7 +84,6 @@ BaseView { let convuid = ConversationsAdapter.createSwarm(title, description, avatar, uris) viewCoordinator.dismiss("NewSwarmPage") LRCInstance.selectConversation(convuid) - }) } else { viewCoordinator.dismiss("NewSwarmPage") @@ -99,10 +98,12 @@ BaseView { sidePanelTabBar.selectTab(tabIndex) } + property bool inNewSwarm: viewCoordinator.currentViewName === "NewSwarmPage" + property var highlighted: [] property var highlightedMembers: [] onHighlightedMembersChanged: { - if (viewCoordinator.inNewSwarm) { + if (inNewSwarm) { const newSwarmPage = viewCoordinator.getView("NewSwarmPage") newSwarmPage.members = highlightedMembers } @@ -180,86 +181,65 @@ BaseView { color: JamiTheme.backgroundColor } - header: AccountComboBox { - width: parent.width - height: JamiTheme.accountListItemHeight - onSettingBtnClicked: { - !viewCoordinator.inSettings ? - viewCoordinator.present("SettingsView") : - viewCoordinator.dismiss("SettingsView")} - } + header: AccountComboBox {} - StackLayout { + Item { anchors.fill: parent - currentIndex: viewCoordinator.inSettings ? 0 : 1 + RowLayout { + id: titleBar - SettingsMenu { - id: settingsMenu - objectName: "settingsMenu" + visible: swarmMemberSearchList.visible - Layout.fillWidth: true - Layout.fillHeight: true - } + height: 40 + anchors.top: parent.top + anchors.topMargin: 10 + anchors.left: parent.left + anchors.leftMargin: 15 + anchors.right: parent.right + anchors.rightMargin: 15 - Item { - Layout.fillWidth: true - Layout.fillHeight: true + Label { + id: title - RowLayout { - id: titleBar + height: parent.height + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter - visible: swarmMemberSearchList.visible + color: JamiTheme.textColor - height: 40 - anchors.top: parent.top - anchors.topMargin: 10 - anchors.left: parent.left - anchors.leftMargin: 15 - anchors.right: parent.right - anchors.rightMargin: 15 + font.bold: true + font.pointSize: JamiTheme.contactEventPointSize - Label { - id: title - - height: parent.height - Layout.fillWidth: true - Layout.alignment: Qt.AlignVCenter - - color: JamiTheme.textColor - - font.bold: true - font.pointSize: JamiTheme.contactEventPointSize - - text: JamiStrings.createSwarm - } - - PushButton { - radius: JamiTheme.primaryRadius - - imageColor: JamiTheme.textColor - imagePadding: 8 - normalColor: JamiTheme.secondaryBackgroundColor - - preferredSize: titleBar.height - - source: JamiResources.round_close_24dp_svg - toolTipText: JamiStrings.cancel - - onClicked: toggleCreateSwarmView() - } + text: JamiStrings.createSwarm } - RowLayout { - id: startBar + PushButton { + radius: JamiTheme.primaryRadius - height: 40 - anchors.top: titleBar.visible ? titleBar.bottom : parent.top - anchors.topMargin: 10 - anchors.left: parent.left - anchors.leftMargin: 15 - anchors.right: parent.right - anchors.rightMargin: 15 + imageColor: JamiTheme.textColor + imagePadding: 8 + normalColor: JamiTheme.secondaryBackgroundColor + + preferredSize: titleBar.height + + source: JamiResources.round_close_24dp_svg + toolTipText: JamiStrings.cancel + + onClicked: toggleCreateSwarmView() + } + } + + RowLayout { + id: startBar + + height: 40 + anchors.top: titleBar.visible ? titleBar.bottom : parent.top + anchors.topMargin: 10 + anchors.left: parent.left + anchors.leftMargin: 15 + anchors.right: parent.right + anchors.rightMargin: 15 Shortcut { sequence: "Ctrl+F" @@ -269,232 +249,230 @@ BaseView { } } - ContactSearchBar { - id: contactSearchBar + ContactSearchBar { + id: contactSearchBar - Layout.fillHeight: true - Layout.fillWidth: true + Layout.fillHeight: true + Layout.fillWidth: true - onContactSearchBarTextChanged: function (text) { - print(text) - // not calling positionViewAtBeginning will cause - // sort animation visual bugs - conversationListView.positionViewAtBeginning() - ConversationsAdapter.ignoreFiltering(root.highlighted) - ConversationsAdapter.setFilter(text) - } - - onReturnPressedWhileSearching: { - var listView = searchResultsListView.count ? - searchResultsListView : - conversationListView - if (listView.count) - listView.model.select(0) - } + onContactSearchBarTextChanged: function (text) { + // not calling positionViewAtBeginning will cause + // sort animation visual bugs + conversationListView.positionViewAtBeginning() + ConversationsAdapter.ignoreFiltering(root.highlighted) + ConversationsAdapter.setFilter(text) } - PushButton { - id: startConversation - - Layout.alignment: Qt.AlignLeft - radius: JamiTheme.primaryRadius - - imageColor: JamiTheme.textColor - imagePadding: 8 - normalColor: JamiTheme.secondaryBackgroundColor - - preferredSize: startBar.height - - visible: !swarmMemberSearchList.visible && CurrentAccount.type !== Profile.Type.SIP - - source: smartListLayout.visible ? JamiResources.create_swarm_svg : JamiResources.round_close_24dp_svg - toolTipText: smartListLayout.visible ? JamiStrings.startSwarm : JamiStrings.cancel - - onClicked: toggleCreateSwarmView() + onReturnPressedWhileSearching: { + var listView = searchResultsListView.count ? + searchResultsListView : + conversationListView + if (listView.count) + listView.model.select(0) } } - SidePanelTabBar { - id: sidePanelTabBar + PushButton { + id: startConversation - visible: ConversationsAdapter.pendingRequestCount && - !contactSearchBar.textContent && smartListLayout.visible - anchors.top: startBar.bottom - anchors.topMargin: visible ? 10 : 0 - width: page.width - height: visible ? 42 : 0 - contentHeight: visible ? 42 : 0 + Layout.alignment: Qt.AlignLeft + radius: JamiTheme.primaryRadius + + imageColor: JamiTheme.textColor + imagePadding: 8 + normalColor: JamiTheme.secondaryBackgroundColor + + preferredSize: startBar.height + + visible: !swarmMemberSearchList.visible && CurrentAccount.type !== Profile.Type.SIP + + source: smartListLayout.visible ? JamiResources.create_swarm_svg : JamiResources.round_close_24dp_svg + toolTipText: smartListLayout.visible ? JamiStrings.startSwarm : JamiStrings.cancel + + onClicked: toggleCreateSwarmView() } + } - Rectangle { - id: searchStatusRect + SidePanelTabBar { + id: sidePanelTabBar - visible: searchStatusText.text !== "" && smartListLayout.visible + visible: ConversationsAdapter.pendingRequestCount && + !contactSearchBar.textContent && smartListLayout.visible + anchors.top: startBar.bottom + anchors.topMargin: visible ? 10 : 0 + width: page.width + height: visible ? 42 : 0 + contentHeight: visible ? 42 : 0 + } - anchors.top: sidePanelTabBar.bottom - anchors.topMargin: visible ? 10 : 0 - width: parent.width - height: visible ? 42 : 0 + Rectangle { + id: searchStatusRect - color: JamiTheme.backgroundColor + visible: searchStatusText.text !== "" && smartListLayout.visible - Text { - id: searchStatusText + anchors.top: sidePanelTabBar.bottom + anchors.topMargin: visible ? 10 : 0 + width: parent.width + height: visible ? 42 : 0 - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - anchors.leftMargin: 32 - anchors.right: parent.right - anchors.rightMargin: 32 - color: JamiTheme.textColor - wrapMode: Text.WordWrap - font.pointSize: JamiTheme.filterItemFontSize - } + color: JamiTheme.backgroundColor + + Text { + id: searchStatusText + + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: 32 + anchors.right: parent.right + anchors.rightMargin: 32 + color: JamiTheme.textColor + wrapMode: Text.WordWrap + font.pointSize: JamiTheme.filterItemFontSize } + } - ColumnLayout { - id: smartListLayout + ColumnLayout { + id: smartListLayout - width: parent.width - anchors.top: searchStatusRect.bottom - anchors.topMargin: (sidePanelTabBar.visible || - searchStatusRect.visible) ? 0 : 12 - anchors.bottom: parent.bottom + width: parent.width + anchors.top: searchStatusRect.bottom + anchors.topMargin: (sidePanelTabBar.visible || + searchStatusRect.visible) ? 0 : 12 + anchors.bottom: parent.bottom - spacing: 4 + spacing: 4 - visible: !swarmMemberSearchList.visible + visible: !swarmMemberSearchList.visible - ConversationListView { - id: searchResultsListView + ConversationListView { + id: searchResultsListView - visible: count - opacity: visible ? 1 : 0 + visible: count + opacity: visible ? 1 : 0 - Layout.topMargin: 10 - Layout.alignment: Qt.AlignTop - Layout.fillWidth: true - Layout.preferredHeight: visible ? contentHeight : 0 - Layout.maximumHeight: { - var otherContentHeight = conversationListView.contentHeight + 16 - if (conversationListView.visible) - if (otherContentHeight < parent.height / 2) - return parent.height - otherContentHeight - else - return parent.height / 2 + Layout.topMargin: 10 + Layout.alignment: Qt.AlignTop + Layout.fillWidth: true + Layout.preferredHeight: visible ? contentHeight : 0 + Layout.maximumHeight: { + var otherContentHeight = conversationListView.contentHeight + 16 + if (conversationListView.visible) + if (otherContentHeight < parent.height / 2) + return parent.height - otherContentHeight else - return parent.height - } - - model: SearchResultsListModel - headerLabel: JamiStrings.searchResults - headerVisible: visible + return parent.height / 2 + else + return parent.height } - ConversationListView { - id: conversationListView - - visible: count - - Layout.preferredWidth: parent.width - Layout.fillHeight: true - - model: ConversationListModel - headerLabel: JamiStrings.conversations - headerVisible: searchResultsListView.visible - } + model: SearchResultsListModel + headerLabel: JamiStrings.searchResults + headerVisible: visible } - ColumnLayout { - id: swarmMemberSearchList + ConversationListView { + id: conversationListView - visible: viewCoordinator.inNewSwarm + visible: count - width: parent.width - anchors.top: searchStatusRect.bottom - anchors.topMargin: (sidePanelTabBar.visible || - searchStatusRect.visible) ? 0 : 12 - anchors.bottom: parent.bottom + Layout.preferredWidth: parent.width + Layout.fillHeight: true - spacing: 4 + model: ConversationListModel + headerLabel: JamiStrings.conversations + headerVisible: searchResultsListView.visible + } + } - Text { - font.bold: true - font.pointSize: JamiTheme.contactEventPointSize + ColumnLayout { + id: swarmMemberSearchList - Layout.margins: 16 - Layout.maximumHeight: 24 - Layout.alignment: Qt.AlignTop - Layout.fillWidth: true + visible: inNewSwarm - wrapMode: Text.Wrap + width: parent.width + anchors.top: searchStatusRect.bottom + anchors.topMargin: (sidePanelTabBar.visible || + searchStatusRect.visible) ? 0 : 12 + anchors.bottom: parent.bottom - text: { - if (highlightedMembers.length === 0) - return JamiStrings.youCanAdd7 - return JamiStrings.youCanAddMore.arg(7 - Math.min(highlightedMembers.length, 7)) - } - color: JamiTheme.textColor + spacing: 4 + + Text { + font.bold: true + font.pointSize: JamiTheme.contactEventPointSize + + Layout.margins: 16 + Layout.maximumHeight: 24 + Layout.alignment: Qt.AlignTop + Layout.fillWidth: true + + wrapMode: Text.Wrap + + text: { + if (highlightedMembers.length === 0) + return JamiStrings.youCanAdd7 + return JamiStrings.youCanAddMore.arg(7 - Math.min(highlightedMembers.length, 7)) } + color: JamiTheme.textColor + } - JamiListView { - id: swarmCurrentConversationList + JamiListView { + id: swarmCurrentConversationList - Layout.preferredWidth: parent.width - Layout.fillHeight: true + Layout.preferredWidth: parent.width + Layout.fillHeight: true - model: ConversationListModel - delegate: SmartListItemDelegate { - interactive: false + model: ConversationListModel + delegate: SmartListItemDelegate { + interactive: false - onVisibleChanged: { - if (!swarmCurrentConversationList.visible) { - highlighted = false - root.clearHighlighted() - } - } - - Component.onCompleted: { - // Note: when scrolled down, this delegate will be - // destroyed from the memory. So, re-add the highlighted - // status if necessary - if (Array.from(root.highlighted).find(r => r === UID)) { - highlighted = true - } - } - - onHighlightedChanged: function onHighlightedChanged() { - if (highlighted && Array.from(root.highlighted).find(r => r === UID)) { - // Due to scrolling destruction/reconstruction - return - } - var currentHighlighted = root.highlighted - if (!root.refreshHighlighted(UID, highlighted)) { - highlighted = false - return - } - if (highlighted) { - root.highlighted.push(UID) - } else { - root.highlighted = Array.from(root.highlighted).filter(r => r !== UID) - } - root.clearContactSearchBar() + onVisibleChanged: { + if (!swarmCurrentConversationList.visible) { + highlighted = false + root.clearHighlighted() } } - currentIndex: model.currentFilteredRow - Timer { - id: locationIconTimer - - property bool showIconArrow: true - property bool isSharingPosition: PositionManager.positionShareConvIdsCount !== 0 - property bool isReceivingPosition: PositionManager.sharingUrisCount !== 0 - - interval: 750 - running: isSharingPosition || isReceivingPosition - repeat: true - onTriggered: {showIconArrow = !showIconArrow} + Component.onCompleted: { + // Note: when scrolled down, this delegate will be + // destroyed from the memory. So, re-add the highlighted + // status if necessary + if (Array.from(root.highlighted).find(r => r === UID)) { + highlighted = true + } } + + onHighlightedChanged: function onHighlightedChanged() { + if (highlighted && Array.from(root.highlighted).find(r => r === UID)) { + // Due to scrolling destruction/reconstruction + return + } + var currentHighlighted = root.highlighted + if (!root.refreshHighlighted(UID, highlighted)) { + highlighted = false + return + } + if (highlighted) { + root.highlighted.push(UID) + } else { + root.highlighted = Array.from(root.highlighted).filter(r => r !== UID) + } + root.clearContactSearchBar() + } + } + currentIndex: model.currentFilteredRow + + Timer { + id: locationIconTimer + + property bool showIconArrow: true + property bool isSharingPosition: PositionManager.positionShareConvIdsCount !== 0 + property bool isReceivingPosition: PositionManager.sharingUrisCount !== 0 + + interval: 750 + running: isSharingPosition || isReceivingPosition + repeat: true + onTriggered: {showIconArrow = !showIconArrow} } } } diff --git a/src/app/mainview/components/SmartListItemDelegate.qml b/src/app/mainview/components/SmartListItemDelegate.qml index 6398288f..1559855d 100644 --- a/src/app/mainview/components/SmartListItemDelegate.qml +++ b/src/app/mainview/components/SmartListItemDelegate.qml @@ -66,12 +66,6 @@ ItemDelegate { } } - onVisibleChanged: { - if (visible) - return - UtilsAdapter.clearInteractionsCache(root.accountId, root.convId) - } - Connections { target: MessagesAdapter function onTimestampUpdated() { diff --git a/src/app/mainview/components/WelcomePage.qml b/src/app/mainview/components/WelcomePage.qml index b445cf27..cf1bcd68 100644 --- a/src/app/mainview/components/WelcomePage.qml +++ b/src/app/mainview/components/WelcomePage.qml @@ -27,20 +27,27 @@ import net.jami.Models 1.1 import "../../commoncomponents" import "../js/keyboardshortcuttablecreation.js" as KeyboardShortcutTableCreation -BaseView { - id: root +ListSelectionView { + id: viewNode + objectName: "WelcomePage" + + splitViewStateKey: "Main" + hideRightPaneInSinglePaneMode: true color: JamiTheme.secondaryBackgroundColor - JamiFlickable { - id: welcomeView + onPresented: LRCInstance.deselectConversation() + leftPaneItem: viewCoordinator.getView("SidePanel") + rightPaneItem: JamiFlickable { + id: root + MouseArea { anchors.fill: parent enabled: visible - onClicked: welcomeView.forceActiveFocus() + onClicked: root.forceActiveFocus() } - anchors.fill: root + anchors.fill: parent contentHeight: Math.max(root.height, welcomePageLayout.implicitHeight) contentWidth: Math.max(300, root.width) diff --git a/src/app/settingsview/SettingsSidePanel.qml b/src/app/settingsview/SettingsSidePanel.qml new file mode 100644 index 00000000..930178e6 --- /dev/null +++ b/src/app/settingsview/SettingsSidePanel.qml @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2023 Savoir-faire Linux Inc. + * + * 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 . + */ + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import net.jami.Adapters 1.1 +import net.jami.Constants 1.1 +import net.jami.Enums 1.1 +import net.jami.Models 1.1 + +import "../mainview/components" +import "../commoncomponents" +import "components" + +SidePanelBase { + id: root + objectName: "SettingsSidePanel" + + property var select: function(index) { + buttonGroup.checkedButton = buttonGroup.buttons[index] + } + property var deselect: function() { buttonGroup.checkedButton = null } + + color: JamiTheme.backgroundColor + + Page { + id: page + + anchors.fill: parent + + background: Rectangle { + color: JamiTheme.backgroundColor + } + + header: AccountComboBox {} + + // Bind to requests for a settings page to be selected via shorcut. + Connections { + target: JamiQmlUtils + function onSettingsPageRequested(index) { + viewCoordinator.present("SettingsView") + buttonGroup.checkedButton = buttonGroup.buttons[index] + } + } + + ButtonGroup { + id: buttonGroup + buttons: settingsButtons.children + + onCheckedButtonChanged: { + for (var i = 0; i < buttons.length; i++) + if (buttons[i] === checkedButton) { + indexSelected(i) + return + } + indexSelected(-1) + } + } + + Column { + id: settingsButtons + + spacing: 0 + anchors.left: parent.left + anchors.right: parent.right + height: childrenRect.height + + component SMB: PushButton { + normalColor: root.color + + preferredHeight: 64 + preferredMargin: 24 + + anchors.left: parent.left + anchors.right: parent.right + + buttonTextFont.pointSize: JamiTheme.textFontSize + 2 + textHAlign: Text.AlignLeft + + imageColor: JamiTheme.textColor + imageContainerHeight: 40 + imageContainerWidth: 40 + + pressedColor: Qt.lighter(JamiTheme.pressedButtonColor, 1.25) + checkedColor: JamiTheme.smartListSelectedColor + hoveredColor: JamiTheme.smartListHoveredColor + + duration: 0 + checkable: true + radius: 0 + } + + SMB { + buttonText: JamiStrings.accountSettingsMenuTitle + source: JamiResources.account_24dp_svg + } + + SMB { + buttonText: JamiStrings.generalSettingsTitle + source: JamiResources.gear_black_24dp_svg + } + + SMB { + buttonText: JamiStrings.avSettingsMenuTitle + source: JamiResources.media_black_24dp_svg + } + + SMB { + buttonText: JamiStrings.pluginSettingsTitle + source: JamiResources.plugin_settings_black_24dp_svg + } + } + } +} diff --git a/src/app/settingsview/SettingsView.qml b/src/app/settingsview/SettingsView.qml index c60b49a3..c50760ab 100644 --- a/src/app/settingsview/SettingsView.qml +++ b/src/app/settingsview/SettingsView.qml @@ -29,19 +29,9 @@ import "components" import "../commoncomponents" import "../mainview/js/contactpickercreation.js" as ContactPickerCreation -BaseView { - id: root +ListSelectionView { + id: viewNode objectName: "SettingsView" - requiresIndex: true - - onDismissed: { - settingsViewRect.stopBooth() - if (UtilsAdapter.getAccountListSize() === 0) { - viewCoordinator.requestAppWindowWizardView() - } else { - AccountAdapter.changeAccount(0) - } - } enum SettingsMenu { Account, @@ -50,9 +40,24 @@ BaseView { Plugin } - onVisibleChanged: if(visible) setSelected(selectedMenu, true) + splitViewStateKey: "Main" + inhibits: ["ConversationView"] - property int selectedMenu: SettingsView.Account + leftPaneItem: viewCoordinator.getView("SettingsSidePanel") + + onDismissed: { + // Trigger an update to messages if needed. + CurrentConversation.reloadInteractions() + settingsViewRect.stopBooth() + if (UtilsAdapter.getAccountListSize() === 0) { + viewCoordinator.requestAppWindowWizardView() + } else { + AccountAdapter.changeAccount(0) + } + } + + selectionFallback: true + property int selectedMenu: index onSelectedMenuChanged: { if (selectedMenu === SettingsView.Account) { pageIdCurrentAccountSettings.updateAccountInfoDisplayed() @@ -61,15 +66,10 @@ BaseView { } } - function setSelected(idx, recovery = false) { - if (selectedMenu === idx && !recovery) return - selectedMenu = idx - } - - Rectangle { + rightPaneItem: Rectangle { id: settingsViewRect - anchors.fill: root + anchors.fill: parent color: JamiTheme.secondaryBackgroundColor signal stopBooth @@ -98,6 +98,7 @@ BaseView { title: { switch(selectedMenu){ + default: case SettingsView.Account: return JamiStrings.accountSettingsTitle case SettingsView.General: @@ -109,7 +110,7 @@ BaseView { } } - onBackArrowClicked: viewCoordinator.hideCurrentView() + onBackArrowClicked: viewNode.dismiss() } JamiFlickable { @@ -137,6 +138,7 @@ BaseView { currentIndex: { switch(selectedMenu){ + default: case SettingsView.Account: return pageIdCurrentAccountSettingsPage case SettingsView.General: diff --git a/src/app/settingsview/components/SettingsHeader.qml b/src/app/settingsview/components/SettingsHeader.qml index 2d299af0..49c365a5 100644 --- a/src/app/settingsview/components/SettingsHeader.qml +++ b/src/app/settingsview/components/SettingsHeader.qml @@ -37,7 +37,7 @@ RowLayout { Layout.preferredWidth: JamiTheme.preferredFieldHeight Layout.preferredHeight: JamiTheme.preferredFieldHeight - visible: viewCoordinator.singlePane + visible: viewNode.isSinglePane onClicked: backArrowClicked() } diff --git a/src/app/settingsview/components/SettingsMenu.qml b/src/app/settingsview/components/SettingsMenu.qml deleted file mode 100644 index 77910d9a..00000000 --- a/src/app/settingsview/components/SettingsMenu.qml +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) 2020-2023 Savoir-faire Linux Inc. - * Author: Andreas Traczyk - * - * 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 . - */ - -import QtQuick -import QtQuick.Controls - -import net.jami.Models 1.1 -import net.jami.Constants 1.1 - -import "../../commoncomponents" -import "../../settingsview" - -Rectangle { - id: root - - color: JamiTheme.backgroundColor - - // The following bindings provide the settings menu selection persistence behavior. - property bool singlePane: viewCoordinator.singlePane - onSinglePaneChanged: { - if (!viewCoordinator.singlePane && viewCoordinator.inSettings) { - const idx = viewCoordinator.currentView.selectedMenu - buttonGroup.checkedButton = buttonGroup.buttons[idx] - } - } - onVisibleChanged: buttonGroup.checkedButton = visible && !viewCoordinator.singlePane ? - buttonGroup.buttons[0] : - null - - // Bind to requests for a settings page to be selected via shorcut. - Connections { - target: JamiQmlUtils - function onSettingsPageRequested(index) { - buttonGroup.checkedButton = buttonGroup.buttons[index] - } - } - - ButtonGroup { - id: buttonGroup - buttons: settingsButtons.children - - // When the selection changes, we present the SettingsView at - // the selected index. - onCheckedButtonChanged: { - for (var i = 0; i < buttons.length; i++) - if (buttons[i] === checkedButton) { - if (viewCoordinator.singlePane) { - viewCoordinator.present("SettingsView").selectedMenu = i - } else if (!viewCoordinator.busy) { - var settingsView = viewCoordinator.getView("SettingsView") - settingsView.selectedMenu = i - } - } - } - } - - Column { - id: settingsButtons - - spacing: 0 - anchors.left: parent.left - anchors.right: parent.right - height: childrenRect.height - - component SMB: SettingsMenuButton { normalColor: root.color } - - SMB { - buttonText: JamiStrings.accountSettingsMenuTitle - source: JamiResources.account_24dp_svg - } - - SMB { - buttonText: JamiStrings.generalSettingsTitle - source: JamiResources.gear_black_24dp_svg - } - - SMB { - buttonText: JamiStrings.avSettingsMenuTitle - source: JamiResources.media_black_24dp_svg - } - - SMB { - buttonText: JamiStrings.pluginSettingsTitle - source: JamiResources.plugin_settings_black_24dp_svg - } - } -} - diff --git a/src/app/utilsadapter.cpp b/src/app/utilsadapter.cpp index b56f5bd1..7aba8d7a 100644 --- a/src/app/utilsadapter.cpp +++ b/src/app/utilsadapter.cpp @@ -50,6 +50,46 @@ UtilsAdapter::UtilsAdapter(AppSettingsManager* settingsManager, } } +QVariant +UtilsAdapter::getAppValue(const QString& key, const QVariant& defaultValue) +{ + return settingsManager_->getValue(key, defaultValue); +} + +void +UtilsAdapter::setAppValue(const QString& key, const QVariant& value) +{ + settingsManager_->setValue(key, value); +} + +QVariant +UtilsAdapter::getAppValue(const Settings::Key key) +{ + return settingsManager_->getValue(key); +} + +void +UtilsAdapter::setAppValue(const Settings::Key key, const QVariant& value) +{ + if (key == Settings::Key::BaseZoom) { + if (value.toDouble() < 0.1 || value.toDouble() > 2.0) + return; + } + settingsManager_->setValue(key, value); + // If we change the lang preference, reload the translations + if (key == Settings::Key::LANG) { + settingsManager_->loadTranslations(); + Q_EMIT changeLanguage(); + } else if (key == Settings::Key::BaseZoom) + Q_EMIT changeFontSize(); + else if (key == Settings::Key::EnableExperimentalSwarm) + Q_EMIT showExperimentalCallSwarm(); + else if (key == Settings::Key::ShowChatviewHorizontally) + Q_EMIT chatviewPositionChanged(); + else if (key == Settings::Key::AppTheme) + Q_EMIT appThemeChanged(); +} + const QString UtilsAdapter::getProjectCredits() { @@ -352,34 +392,6 @@ UtilsAdapter::setSystemTrayIconVisible(bool visible) systemTray_->setVisible(visible); } -QVariant -UtilsAdapter::getAppValue(const Settings::Key key) -{ - return settingsManager_->getValue(key); -} - -void -UtilsAdapter::setAppValue(const Settings::Key key, const QVariant& value) -{ - if (key == Settings::Key::BaseZoom) { - if (value.toDouble() < 0.1 || value.toDouble() > 2.0) - return; - } - settingsManager_->setValue(key, value); - // If we change the lang preference, reload the translations - if (key == Settings::Key::LANG) { - settingsManager_->loadTranslations(); - Q_EMIT changeLanguage(); - } else if (key == Settings::Key::BaseZoom) - Q_EMIT changeFontSize(); - else if (key == Settings::Key::EnableExperimentalSwarm) - Q_EMIT showExperimentalCallSwarm(); - else if (key == Settings::Key::ShowChatviewHorizontally) - Q_EMIT chatviewPositionChanged(); - else if (key == Settings::Key::AppTheme) - Q_EMIT appThemeChanged(); -} - QString UtilsAdapter::getDirDocument() { diff --git a/src/app/utilsadapter.h b/src/app/utilsadapter.h index eef34dca..ee8101cb 100644 --- a/src/app/utilsadapter.h +++ b/src/app/utilsadapter.h @@ -36,8 +36,7 @@ #if defined(WIN32) && __has_include() #include -#define WATCHSYSTEMTHEME \ - __has_include() +#define WATCHSYSTEMTHEME __has_include() #if WATCHSYSTEMTHEME #include @@ -74,6 +73,11 @@ public: QObject* parent = nullptr); ~UtilsAdapter() = default; + Q_INVOKABLE QVariant getAppValue(const QString& key, const QVariant& defaultValue = {}); + Q_INVOKABLE void setAppValue(const QString& key, const QVariant& value); + Q_INVOKABLE QVariant getAppValue(const Settings::Key key); + Q_INVOKABLE void setAppValue(const Settings::Key key, const QVariant& value); + Q_INVOKABLE const QString getProjectCredits(); Q_INVOKABLE const QString getVersionStr(); Q_INVOKABLE void setClipboardText(QString text); @@ -110,8 +114,6 @@ public: Q_INVOKABLE bool isImage(const QString& fileExt); Q_INVOKABLE QString humanFileSize(qint64 fileSize); Q_INVOKABLE void setSystemTrayIconVisible(bool visible); - Q_INVOKABLE QVariant getAppValue(const Settings::Key key); - Q_INVOKABLE void setAppValue(const Settings::Key key, const QVariant& value); Q_INVOKABLE QString getDirDocument(); Q_INVOKABLE QString getDirScreenshot(); Q_INVOKABLE QString getDirDownload(); diff --git a/src/app/wizardview/WizardView.qml b/src/app/wizardview/WizardView.qml index bd0d7514..f749eda8 100644 --- a/src/app/wizardview/WizardView.qml +++ b/src/app/wizardview/WizardView.qml @@ -33,7 +33,8 @@ import "components" BaseView { id: root objectName: "WizardView" - singlePaneOnly: true + + inhibits: ["ConversationView"] // signal to redirect the page to main view signal loaderSourceChangeRequested(int sourceToLoad)