1
0
Fork 0
mirror of https://git.jami.net/savoirfairelinux/jami-client-qt.git synced 2025-04-22 06:02:03 +02:00

misc: save split view states

This commit simplifies the view coordinator and restructures the main view stack to not include a split view.

This removes workaround logic for single pane view support that was previously in the view coordinator.

The main view is now a stack that may contain both single and dual pane views.

The ListSelectionView further specializes DualPaneView to add an index-based or custom selection mechanism.

Gitlab: #902
Change-Id: I81c9fe579b95c8d4774c3e491a16d7600323c40c
This commit is contained in:
Andreas Traczyk 2023-02-27 18:08:06 -05:00
parent f5f5ae13ab
commit 9ff32433cf
29 changed files with 1027 additions and 888 deletions

View file

@ -89,7 +89,7 @@ ApplicationWindow {
!UtilsAdapter.getAccountListSize()) { !UtilsAdapter.getAccountListSize()) {
// Save the window geometry and state before quitting. // Save the window geometry and state before quitting.
layoutManager.saveWindowSettings() layoutManager.saveWindowSettings()
viewCoordinator.dismissAll() viewCoordinator.deinit()
Qt.quit() Qt.quit()
} else { } else {
layoutManager.closeToTray() layoutManager.closeToTray()
@ -160,11 +160,13 @@ ApplicationWindow {
// Present the welcome view once the viewCoordinator is setup. // Present the welcome view once the viewCoordinator is setup.
viewCoordinator.initialized.connect(function() { viewCoordinator.initialized.connect(function() {
viewCoordinator.preload("SidePanel")
viewCoordinator.preload("SettingsSidePanel")
viewCoordinator.present("WelcomePage") viewCoordinator.present("WelcomePage")
viewCoordinator.preload("ConversationView") viewCoordinator.preload("ConversationView")
}) })
// Set the viewCoordinator's root item. // Set the viewCoordinator's root item.
viewCoordinator.setRootView(item) viewCoordinator.init(item)
} }
if (Qt.platform.os.toString() === "osx") { if (Qt.platform.os.toString() === "osx") {
MainApplication.setEventFilter() MainApplication.setEventFilter()

View file

@ -22,103 +22,53 @@ import QtQuick.Layouts
import net.jami.Constants 1.1 import net.jami.Constants 1.1
import net.jami.Models 1.1 import net.jami.Models 1.1
// This object should be implemented as a QML singleton, or be instantiated import "commoncomponents"
// 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.
QtObject { QtObject {
id: root id: root
required property QtObject viewManager required property QtObject viewManager
signal initialized
signal requestAppWindowWizardView signal requestAppWindowWizardView
// A map of view names to file paths for QML files that define each view. // A map of view names to file paths for QML files that define each view.
property variant resources: { property variant resources: {
"WelcomePage": "mainview/components/WelcomePage.qml",
"SidePanel": "mainview/components/SidePanel.qml", "SidePanel": "mainview/components/SidePanel.qml",
"WelcomePage": "mainview/components/WelcomePage.qml",
"ConversationView": "mainview/ConversationView.qml", "ConversationView": "mainview/ConversationView.qml",
"NewSwarmPage": "mainview/components/NewSwarmPage.qml", "NewSwarmPage": "mainview/components/NewSwarmPage.qml",
"WizardView": "wizardview/WizardView.qml", "WizardView": "wizardview/WizardView.qml",
"SettingsView": "settingsview/SettingsView.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. // The `main` view of the application window.
property Item rootView: null property StackView rootView
// HACKS. property var currentViewName: rootView && rootView.currentItem.objectName || null
property real mainViewWidth: rootView ? rootView.width : 0
property real previousWidth: mainViewWidth
property real mainViewSidePanelRectWidth: sv1 ? sv1.width : 0
property real lastSideBarSplitSize: mainViewSidePanelRectWidth
onMainViewWidthChanged: resolvePanes()
function resolvePanes(force=false) { function init(appWindow) {
if (forceSinglePane) return rootView = Qt.createQmlObject(`import QtQuick; import QtQuick.Controls
const isExpanding = previousWidth < mainViewWidth StackView { anchors.fill: parent }`,
if (mainViewWidth < JamiTheme.chatViewHeaderMinimumWidth + mainViewSidePanelRectWidth appWindow)
&& sv2.visible && (!isExpanding || force)) { initialized()
lastSideBarSplitSize = mainViewSidePanelRectWidth
singlePane = true
} else if (mainViewWidth >= lastSideBarSplitSize + JamiTheme.chatViewHeaderMinimumWidth
&& !sv2.visible && (isExpanding || force) && !layoutManager.isFullScreen) {
singlePane = false
}
previousWidth = mainViewWidth
} }
// Must be the child of `rootView`. function deinit() {
property Item splitView: null viewManager.destroyAllViews()
rootView.destroy()
// 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: { // Finds a view and gets its index within the StackView it's in.
return activeStackView ? activeStackView.currentItem : null function getStackIndex(viewName) {
} for (const [key, value] of Object.entries(viewManager.views)) {
if (value.objectName === viewName) {
// Handle single/dual pane mode. return value.StackView.index
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
} }
} }
return -1
// Emitted once at the end of setRootView. }
signal initialized()
// Create, present, and return a dialog object. // Create, present, and return a dialog object.
function presentDialog(parent, path, props={}) { function presentDialog(parent, path, props={}) {
@ -135,120 +85,42 @@ QtObject {
}, props) }, props)
} }
// Dismiss all views. // Present a view by name.
function dismissAll() { function present(viewName, props) {
for (var path in viewManager.views) { const path = resources[viewName]
viewManager.destroyView(path)
}
}
// Get a view regardless of whether it is currently active. // Check if the current view should inhibit the presentation of this view.
function getView(viewName) { if (rootView.currentItem && rootView.currentItem.inhibits.includes(viewName)) {
if (!viewManager.hasView(viewName)) { print("inhibiting view:", 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) {
return return
} }
// If the view already exists in the StackView, the function will attempt // If the view already exists in the StackView, the function will attempt
// to navigate to its StackView position by dismissing elevated views. // to navigate to its StackView position by dismissing elevated views.
if (sv.find(function(item) { if (rootView.find(function(item) {
return item.objectName === viewName; return item.objectName === viewName;
})) { }, StackView.DontLoad)) {
const viewIndex = getStackIndex(viewName) const viewIndex = getStackIndex(viewName)
if (viewIndex >= 0) { for (var i = (rootView.depth - 1); i > viewIndex; i--) {
for (var i = (sv.depth - 1); i > viewIndex; i--) { dismissObj(rootView.get(i, StackView.DontLoad))
dismissObj(sv.get(i, StackView.DontLoad))
} }
return true return
}
return false
} }
// If we are in single-pane mode and the view was previously forced into if (!viewManager.createView(path, rootView, function(view) {
// sv2, we can move it back to the top of sv1. // push the view onto the stack if it's not already there
if (singlePane && sv === sv1) { if (rootView.currentItem !== view) {
// See if the item is at the top of sv2 rootView.push(view, StackView.Immediate)
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 (!view.managed) view.presented()
}, props)) {
const obj = viewManager.createView(resources[viewName], appWindow)
if (!obj) {
print("could not create view:", viewName) 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. // Dismiss by object.
function dismissObj(obj, sv=activeStackView) { function dismissObj(obj) {
if (obj.StackView.view !== sv) { if (obj.StackView.view !== rootView) {
print("view not in the stack:", obj) print("view not in the stack:", obj)
return return
} }
@ -256,27 +128,28 @@ QtObject {
// If we are dismissing a view that is not at the top of the stack, // 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 // we need to store each of the views on top into a temporary stack
// and then restore them after the view is dismissed. // 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 const viewIndex = obj.StackView.index
var tempStack = [] var tempStack = []
for (var i = (sv.depth - 1); i > viewIndex; i--) { for (var i = (rootView.depth - 1); i > viewIndex; i--) {
var item = sv.pop(StackView.Immediate) var item = rootView.pop(StackView.Immediate)
tempStack.push(item) tempStack.push(item)
} }
// And we define a function to restore and resolve the views. // And we define a function to restore and resolve the views.
var resolveStack = () => { var resolveStack = () => {
for (var i = 0; i < tempStack.length; i++) { for (var i = 0; i < tempStack.length; i++) {
sv.push(tempStack[i], StackView.Immediate) rootView.push(tempStack[i], StackView.Immediate)
} }
if (rootView.depth > 0) rootView.currentItem.presented()
forceSinglePane = sv.currentItem.singlePaneOnly
sv.currentItem.presented()
} }
// Now we can dismiss the view at the top of the stack. // Now we can dismiss the view at the top of the stack.
const depth = sv.depth const depth = rootView.depth
if (obj === sv.get(depth - 1, StackView.DontLoad)) { if (obj === rootView.get(depth - 1, StackView.DontLoad)) {
var view = sv.pop(StackView.Immediate) var view
if (rootView.depth === 1) {
view = rootView.currentItem
rootView.clear()
} else view = rootView.pop(StackView.Immediate)
if (!view) { if (!view) {
print("could not pop view:", obj.objectName) print("could not pop view:", obj.objectName)
resolveStack() resolveStack()
@ -290,51 +163,37 @@ QtObject {
if (!viewManager.destroyView(resources[objectName])) { if (!viewManager.destroyView(resources[objectName])) {
print("could not destroy view:", objectName) print("could not destroy view:", objectName)
} }
} else { } else view.dismissed()
view.dismissed()
}
} }
resolveStack() resolveStack()
} }
// Dismiss by view name. // Dismiss a view by name or the top view if unspecified.
function dismiss(viewName) { function dismiss(viewName=undefined) {
if (!rootView) return if (!rootView || rootView.depth === 0) return
if (viewName !== undefined) {
const depth = activeStackView.depth const depth = rootView.depth
for (var i = (depth - 1); i >= 0; i--) { for (var i = (depth - 1); i >= 0; i--) {
const view = activeStackView.get(i, StackView.DontLoad) const view = rootView.get(i, StackView.DontLoad)
if (view.objectName === viewName) { if (view.objectName === viewName) {
dismissObj(view) dismissObj(view)
return return
} }
} }
return
// Check if the view is hidden on the top of sv2 (if in single-pane mode), } else {
// and dismiss it in that case. dismissObj(rootView.currentItem)
if (singlePane && sv2.currentItem && sv2.currentItem.objectName === viewName) {
dismissObj(sv2.currentItem, sv2)
} }
} }
// Move items from one stack to another. We avoid the recursive technique to function getView(viewName) {
// avoid visibility change events. return viewManager.getView(viewName)
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
} }
// Effectively hide the current view by moving it to the other StackView. // Load a view without presenting it.
// This function only works when in single-pane mode. function preload(viewName) {
function hideCurrentView() { if (!viewManager.createView(resources[viewName], null)) {
if (singlePane) move(sv1, sv2) console.log("Failed to load view: " + viewName)
}
} }
} }

View file

@ -27,9 +27,19 @@ QtObject {
// The number of views. // The number of views.
property int nViews: 0 property int nViews: 0
// Destroy all views.
function destroyAllViews() {
for (var path in views) {
destroyView(path)
}
}
function createView(path, parent=null, cb=null, props={}) { function createView(path, parent=null, cb=null, props={}) {
if (views[path] !== undefined) { if (views[path] !== undefined) {
// an instance of <path> already exists // an instance of <path> already exists
if (cb !== null) {
cb(views[path])
}
return views[path] return views[path]
} }
@ -84,4 +94,8 @@ QtObject {
function hasView(viewName) { function hasView(viewName) {
return nViews && viewPaths[viewName] !== undefined return nViews && viewPaths[viewName] !== undefined
} }
function getView(viewName) {
return views[viewPaths[viewName]] || null
}
} }

View file

@ -40,9 +40,9 @@ AppSettingsManager::AppSettingsManager(QObject* parent)
} }
QVariant 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" if (QString(value.typeName()) == "QString"
&& (value.toString() == "false" || value.toString() == "true")) && (value.toString() == "false" || value.toString() == "true"))
@ -51,10 +51,22 @@ AppSettingsManager::getValue(const Settings::Key key)
return value; 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 void
AppSettingsManager::setValue(const Settings::Key key, const QVariant& value) AppSettingsManager::setValue(const Settings::Key key, const QVariant& value)
{ {
settings_->setValue(Settings::toString(key), value); setValue(Settings::toString(key), value);
} }
QString QString

View file

@ -109,8 +109,12 @@ public:
explicit AppSettingsManager(QObject* parent = nullptr); explicit AppSettingsManager(QObject* parent = nullptr);
~AppSettingsManager() = default; ~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 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(); QString getLanguage();
void loadTranslations(); void loadTranslations();

View file

@ -25,12 +25,8 @@ Rectangle {
// only be destroyed when its parent is destroyed. // only be destroyed when its parent is destroyed.
property bool managed: true property bool managed: true
// True if this view functions in a single-pane context only. // A list of view names that this view inhibits the presentation of.
property bool singlePaneOnly: false property var inhibits: []
// 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
function dismiss() { viewCoordinator.dismiss(objectName) } function dismiss() { viewCoordinator.dismiss(objectName) }

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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
}
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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
}
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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)
}
}
}

View file

@ -17,6 +17,7 @@
import QtQuick import QtQuick
import net.jami.Adapters 1.1
import net.jami.Constants 1.1 import net.jami.Constants 1.1
// This component is used to display and edit a value. // This component is used to display and edit a value.
@ -32,6 +33,7 @@ Loader {
required property string placeholderText required property string placeholderText
property string staticText: "" property string staticText: ""
property string dynamicText property string dynamicText
property bool inputIsValid: true property bool inputIsValid: true
property string infoTipText property string infoTipText
property bool isPersistent: true property bool isPersistent: true

View file

@ -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
}

View file

@ -290,6 +290,8 @@ Item {
// Sizes // Sizes
property real mainViewLeftPaneMinWidth: 300
property real mainViewPaneMinWidth: 430
property real qrCodeImageSize: 256 property real qrCodeImageSize: 256
property real splitViewHandlePreferredWidth: 4 property real splitViewHandlePreferredWidth: 4
property real indicatorFontSize: calcSize(6) property real indicatorFontSize: calcSize(6)
@ -421,7 +423,6 @@ Item {
property real chatViewHairLineSize: 1 property real chatViewHairLineSize: 1
property real chatViewMaximumWidth: 900 property real chatViewMaximumWidth: 900
property real chatViewHeaderPreferredHeight: 64 property real chatViewHeaderPreferredHeight: 64
property real chatViewHeaderMinimumWidth: 430
property real chatViewFooterPreferredHeight: 50 property real chatViewFooterPreferredHeight: 50
property real chatViewFooterMaximumHeight: 280 property real chatViewFooterMaximumHeight: 280
property real chatViewFooterRowSpacing: 1 property real chatViewFooterRowSpacing: 1

View file

@ -69,6 +69,7 @@ public:
CurrentConversationMembers* uris() const; CurrentConversationMembers* uris() const;
Q_SIGNALS: Q_SIGNALS:
void reloadInteractions();
void scrollTo(const QString& msgId); void scrollTo(const QString& msgId);
void showDetails(); void showDetails();

View file

@ -27,15 +27,19 @@ import net.jami.Constants 1.1
import "../commoncomponents" import "../commoncomponents"
import "components" import "components"
BaseView { ListSelectionView {
id: root id: viewNode
objectName: "ConversationView" objectName: "ConversationView"
managed: false managed: false
onPresented: { splitViewStateKey: "Main"
if (!visible && viewCoordinator.singlePane && hasValidSelection: CurrentConversation.id !== ''
CurrentConversation.id !== '') {
viewCoordinator.present(objectName) Connections {
target: CurrentConversation
function onReloadInteractions() {
UtilsAdapter.clearInteractionsCache(CurrentAccount.id, CurrentConversation.id)
MessagesAdapter.loadMoreMessages()
} }
} }
@ -54,13 +58,15 @@ BaseView {
color: JamiTheme.transparentColor color: JamiTheme.transparentColor
StackLayout { leftPaneItem: viewCoordinator.getView("SidePanel")
rightPaneItem: StackLayout {
currentIndex: !CurrentConversation.hasCall ? 0 : 1 currentIndex: !CurrentConversation.hasCall ? 0 : 1
onCurrentIndexChanged: chatView.parent = currentIndex == 1 ? onCurrentIndexChanged: chatView.parent = currentIndex === 1 ?
callStackView.chatViewContainer : callStackView.chatViewContainer :
chatViewContainer chatViewContainer
anchors.fill: root anchors.fill: parent
Item { Item {
id: chatViewContainer id: chatViewContainer
@ -86,7 +92,7 @@ BaseView {
onDismiss: { onDismiss: {
if (parent == chatViewContainer) { if (parent == chatViewContainer) {
root.dismiss() viewNode.dismiss()
} else { } else {
callStackView.chatViewContainer.visible = false callStackView.chatViewContainer.visible = false
callStackView.contentView.forceActiveFocus() callStackView.contentView.forceActiveFocus()

View file

@ -59,48 +59,6 @@ Rectangle {
onWidthChanged: Qt.callLater(JamiQmlUtils.updateMessageBarButtonsPoints) onWidthChanged: Qt.callLater(JamiQmlUtils.updateMessageBarButtonsPoints)
onHeightChanged: 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: { Component.onCompleted: {
JamiQmlUtils.mainViewRectObj = mainView JamiQmlUtils.mainViewRectObj = mainView
} }

View file

@ -30,9 +30,13 @@ import "../../commoncomponents"
Label { Label {
id: root id: root
signal settingBtnClicked
property alias popup: comboBoxPopup 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 // TODO: remove these refresh hacks use QAbstractItemModels correctly
Connections { Connections {
target: AccountAdapter target: AccountAdapter
@ -86,6 +90,7 @@ Label {
MouseArea { MouseArea {
id: mouseArea id: mouseArea
enabled: visible
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
onClicked: { onClicked: {
@ -206,18 +211,20 @@ Label {
id: settingsButton id: settingsButton
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
source: !viewCoordinator.inSettings ? source: !inSettings ?
JamiResources.settings_24dp_svg : JamiResources.settings_24dp_svg :
JamiResources.round_close_24dp_svg JamiResources.round_close_24dp_svg
normalColor: JamiTheme.backgroundColor normalColor: JamiTheme.backgroundColor
imageColor: JamiTheme.textColor imageColor: JamiTheme.textColor
toolTipText: !viewCoordinator.inSettings ? toolTipText: !inSettings ?
JamiStrings.openSettings : JamiStrings.openSettings :
JamiStrings.closeSettings JamiStrings.closeSettings
onClicked: { onClicked: {
settingBtnClicked() !inSettings ?
viewCoordinator.present("SettingsView") :
viewCoordinator.dismiss("SettingsView")
background.state = "normal" background.state = "normal"
} }
} }

View file

@ -110,7 +110,7 @@ Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: JamiTheme.chatViewHeaderPreferredHeight Layout.preferredHeight: JamiTheme.chatViewHeaderPreferredHeight
Layout.maximumHeight: JamiTheme.chatViewHeaderPreferredHeight Layout.maximumHeight: JamiTheme.chatViewHeaderPreferredHeight
Layout.minimumWidth: JamiTheme.chatViewHeaderMinimumWidth Layout.minimumWidth: JamiTheme.mainViewPaneMinWidth
DropArea { DropArea {
anchors.fill: parent anchors.fill: parent
@ -125,7 +125,7 @@ Rectangle {
if (!swarmDetailsPanel.visible && !messagesResearchPanel.visible) { if (!swarmDetailsPanel.visible && !messagesResearchPanel.visible) {
chatContents.visible = true chatContents.visible = true
} else { } else {
if (chatViewHeader.width - JamiTheme.detailsPageMinWidth < JamiTheme.chatViewHeaderMinimumWidth) if (chatViewHeader.width - JamiTheme.detailsPageMinWidth < JamiTheme.mainViewPaneMinWidth)
chatContents.visible = false chatContents.visible = false
} }
} }
@ -157,7 +157,7 @@ Rectangle {
if (!swarmDetailsPanel.visible && !addMemberPanel.visible && !messagesResearchPanel.visible) if (!swarmDetailsPanel.visible && !addMemberPanel.visible && !messagesResearchPanel.visible)
return return
if (chatViewHeader.width < JamiTheme.detailsPageMinWidth + JamiTheme.chatViewHeaderMinimumWidth if (chatViewHeader.width < JamiTheme.detailsPageMinWidth + JamiTheme.mainViewPaneMinWidth
&& !isExpanding && chatContents.visible) { && !isExpanding && chatContents.visible) {
lastContentsSplitSize = chatContents.width lastContentsSplitSize = chatContents.width
lastDetailsSplitSize = Math.min(JamiTheme.detailsPageMinWidth, (swarmDetailsPanel.visible lastDetailsSplitSize = Math.min(JamiTheme.detailsPageMinWidth, (swarmDetailsPanel.visible
@ -166,7 +166,7 @@ Rectangle {
? addMemberPanel.width ? addMemberPanel.width
: messagesResearchPanel.width)) : messagesResearchPanel.width))
chatContents.visible = false chatContents.visible = false
} else if (chatViewHeader.width >= JamiTheme.chatViewHeaderMinimumWidth + lastDetailsSplitSize } else if (chatViewHeader.width >= JamiTheme.mainViewPaneMinWidth + lastDetailsSplitSize
&& isExpanding && !layoutManager.isFullScreen && !chatContents.visible) { && isExpanding && !layoutManager.isFullScreen && !chatContents.visible) {
chatContents.visible = true chatContents.visible = true
} }
@ -199,7 +199,7 @@ Rectangle {
if (addMemberPanel.visible) { if (addMemberPanel.visible) {
chatContents.visible = true chatContents.visible = true
} else { } else {
if (chatViewHeader.width - JamiTheme.detailsPageMinWidth < JamiTheme.chatViewHeaderMinimumWidth) if (chatViewHeader.width - JamiTheme.detailsPageMinWidth < JamiTheme.mainViewPaneMinWidth)
chatContents.visible = false chatContents.visible = false
} }
addMemberPanel.visible = !addMemberPanel.visible addMemberPanel.visible = !addMemberPanel.visible
@ -277,19 +277,19 @@ Rectangle {
handle: Rectangle { handle: Rectangle {
implicitWidth: JamiTheme.splitViewHandlePreferredWidth implicitWidth: JamiTheme.splitViewHandlePreferredWidth
implicitHeight: viewCoordinator.splitView.height implicitHeight: root.height
color: JamiTheme.primaryBackgroundColor color: JamiTheme.primaryBackgroundColor
Rectangle { Rectangle {
implicitWidth: 1 implicitWidth: 1
implicitHeight: viewCoordinator.splitView.height implicitHeight: root.height
color: JamiTheme.tabbarBorderColor color: JamiTheme.tabbarBorderColor
} }
} }
ColumnLayout { ColumnLayout {
id: chatContents id: chatContents
SplitView.maximumWidth: viewCoordinator.splitView.width SplitView.maximumWidth: root.width
SplitView.minimumWidth: JamiTheme.chatViewHeaderMinimumWidth SplitView.minimumWidth: JamiTheme.mainViewPaneMinWidth
SplitView.fillWidth: true SplitView.fillWidth: true
StackLayout { StackLayout {
@ -362,7 +362,7 @@ Rectangle {
id: messagesResearchPanel id: messagesResearchPanel
visible: false visible: false
SplitView.maximumWidth: viewCoordinator.splitView.width SplitView.maximumWidth: root.width
SplitView.minimumWidth: JamiTheme.detailsPageMinWidth SplitView.minimumWidth: JamiTheme.detailsPageMinWidth
SplitView.preferredWidth: JamiTheme.detailsPageMinWidth SplitView.preferredWidth: JamiTheme.detailsPageMinWidth
} }
@ -371,7 +371,7 @@ Rectangle {
id: swarmDetailsPanel id: swarmDetailsPanel
visible: false visible: false
SplitView.maximumWidth: viewCoordinator.splitView.width SplitView.maximumWidth: root.width
SplitView.preferredWidth: JamiTheme.detailsPageMinWidth SplitView.preferredWidth: JamiTheme.detailsPageMinWidth
SplitView.minimumWidth: JamiTheme.detailsPageMinWidth SplitView.minimumWidth: JamiTheme.detailsPageMinWidth
} }
@ -380,7 +380,7 @@ Rectangle {
id: addMemberPanel id: addMemberPanel
visible: false visible: false
SplitView.maximumWidth: viewCoordinator.splitView.width SplitView.maximumWidth: root.width
SplitView.preferredWidth: JamiTheme.detailsPageMinWidth SplitView.preferredWidth: JamiTheme.detailsPageMinWidth
SplitView.minimumWidth: JamiTheme.detailsPageMinWidth SplitView.minimumWidth: JamiTheme.detailsPageMinWidth
} }

View file

@ -26,10 +26,9 @@ import net.jami.Constants 1.1
import "../../commoncomponents" import "../../commoncomponents"
BaseView { DualPaneView {
id: root id: viewNode
objectName: "NewSwarmPage"
color: JamiTheme.chatviewBgColor
signal createSwarmClicked(string title, string description, string avatar) signal createSwarmClicked(string title, string description, string avatar)
signal removeMember(string convId, string member) signal removeMember(string convId, string member)
@ -42,13 +41,23 @@ BaseView {
property var members: [] property var members: []
splitViewStateKey: "Main"
inhibits: ["ConversationView"]
leftPaneItem: viewCoordinator.getView("SidePanel")
rightPaneItem: Rectangle {
id: root
color: JamiTheme.chatviewBgColor
anchors.fill: parent
RowLayout { RowLayout {
id: labelsMember id: labelsMember
Layout.topMargin: 16 Layout.topMargin: 16
Layout.preferredWidth: root.width Layout.preferredWidth: root.width
Layout.preferredHeight: childrenRect.height Layout.preferredHeight: childrenRect.height
spacing: 16 spacing: 16
visible: root.members.length visible: viewNode.members.length
Label { Label {
text: JamiStrings.to text: JamiStrings.to
@ -93,13 +102,13 @@ BaseView {
normalColor: "transparent" normalColor: "transparent"
imageColor: "transparent" imageColor: "transparent"
onClicked: root.removeMember(modelData.convId, modelData.uri) onClicked: removeMember(modelData.convId, modelData.uri)
} }
} }
color: JamiTheme.selectedColor color: JamiTheme.selectedColor
} }
model: root.members model: viewNode.members
} }
} }
} }
@ -227,3 +236,4 @@ BaseView {
} }
} }
} }
}

View file

@ -119,7 +119,7 @@ Rectangle {
} }
onWidthChanged: { onWidthChanged: {
if (chatViewContainer.visible && root.width < JamiTheme.chatViewHeaderMinimumWidth * 2) { if (chatViewContainer.visible && root.width < JamiTheme.mainViewPaneMinWidth * 2) {
callPageMainRect.visible = false callPageMainRect.visible = false
} else { } else {
callPageMainRect.visible = true callPageMainRect.visible = true
@ -146,7 +146,7 @@ Rectangle {
id: callPageMainRect id: callPageMainRect
SplitView.preferredHeight: mainColumnLayout.isHorizontal ? root.height : (root.height / 3) * 2 SplitView.preferredHeight: mainColumnLayout.isHorizontal ? root.height : (root.height / 3) * 2
SplitView.minimumWidth: JamiTheme.chatViewHeaderMinimumWidth SplitView.minimumWidth: JamiTheme.mainViewPaneMinWidth
SplitView.fillWidth: true SplitView.fillWidth: true
TapHandler { TapHandler {
@ -404,15 +404,15 @@ Rectangle {
root.height : root.height :
root.height / 3 root.height / 3
SplitView.preferredWidth: mainColumnLayout.isHorizontal ? SplitView.preferredWidth: mainColumnLayout.isHorizontal ?
JamiTheme.chatViewHeaderMinimumWidth : JamiTheme.mainViewPaneMinWidth :
root.width root.width
SplitView.minimumWidth: JamiTheme.chatViewHeaderMinimumWidth SplitView.minimumWidth: JamiTheme.mainViewPaneMinWidth
visible: false visible: false
clip: true clip: true
property bool showDetails: false property bool showDetails: false
onVisibleChanged: { onVisibleChanged: {
if (visible && root.width < JamiTheme.chatViewHeaderMinimumWidth * 2) { if (visible && root.width < JamiTheme.mainViewPaneMinWidth * 2) {
callPageMainRect.visible = false callPageMainRect.visible = false
} else { } else {
callPageMainRect.visible = true callPageMainRect.visible = true

View file

@ -29,7 +29,7 @@ import net.jami.Models 1.1
import "../../commoncomponents" import "../../commoncomponents"
import "../../settingsview/components" import "../../settingsview/components"
BaseView { SidePanelBase {
id: root id: root
objectName: "SidePanel" objectName: "SidePanel"
@ -67,9 +67,9 @@ BaseView {
} }
function toggleCreateSwarmView() { function toggleCreateSwarmView() {
if (!viewCoordinator.inNewSwarm) { if (!inNewSwarm) {
viewCoordinator.present("NewSwarmPage") viewCoordinator.present("NewSwarmPage")
const newSwarmPage = viewCoordinator.currentView const newSwarmPage = viewCoordinator.getView("NewSwarmPage")
newSwarmPage.removeMember.connect((convId, member) => { newSwarmPage.removeMember.connect((convId, member) => {
removeMember(convId, member) removeMember(convId, member)
}) })
@ -84,7 +84,6 @@ BaseView {
let convuid = ConversationsAdapter.createSwarm(title, description, avatar, uris) let convuid = ConversationsAdapter.createSwarm(title, description, avatar, uris)
viewCoordinator.dismiss("NewSwarmPage") viewCoordinator.dismiss("NewSwarmPage")
LRCInstance.selectConversation(convuid) LRCInstance.selectConversation(convuid)
}) })
} else { } else {
viewCoordinator.dismiss("NewSwarmPage") viewCoordinator.dismiss("NewSwarmPage")
@ -99,10 +98,12 @@ BaseView {
sidePanelTabBar.selectTab(tabIndex) sidePanelTabBar.selectTab(tabIndex)
} }
property bool inNewSwarm: viewCoordinator.currentViewName === "NewSwarmPage"
property var highlighted: [] property var highlighted: []
property var highlightedMembers: [] property var highlightedMembers: []
onHighlightedMembersChanged: { onHighlightedMembersChanged: {
if (viewCoordinator.inNewSwarm) { if (inNewSwarm) {
const newSwarmPage = viewCoordinator.getView("NewSwarmPage") const newSwarmPage = viewCoordinator.getView("NewSwarmPage")
newSwarmPage.members = highlightedMembers newSwarmPage.members = highlightedMembers
} }
@ -180,31 +181,10 @@ BaseView {
color: JamiTheme.backgroundColor color: JamiTheme.backgroundColor
} }
header: AccountComboBox { header: AccountComboBox {}
width: parent.width
height: JamiTheme.accountListItemHeight
onSettingBtnClicked: {
!viewCoordinator.inSettings ?
viewCoordinator.present("SettingsView") :
viewCoordinator.dismiss("SettingsView")}
}
StackLayout {
anchors.fill: parent
currentIndex: viewCoordinator.inSettings ? 0 : 1
SettingsMenu {
id: settingsMenu
objectName: "settingsMenu"
Layout.fillWidth: true
Layout.fillHeight: true
}
Item { Item {
Layout.fillWidth: true anchors.fill: parent
Layout.fillHeight: true
RowLayout { RowLayout {
id: titleBar id: titleBar
@ -276,7 +256,6 @@ BaseView {
Layout.fillWidth: true Layout.fillWidth: true
onContactSearchBarTextChanged: function (text) { onContactSearchBarTextChanged: function (text) {
print(text)
// not calling positionViewAtBeginning will cause // not calling positionViewAtBeginning will cause
// sort animation visual bugs // sort animation visual bugs
conversationListView.positionViewAtBeginning() conversationListView.positionViewAtBeginning()
@ -408,7 +387,7 @@ BaseView {
ColumnLayout { ColumnLayout {
id: swarmMemberSearchList id: swarmMemberSearchList
visible: viewCoordinator.inNewSwarm visible: inNewSwarm
width: parent.width width: parent.width
anchors.top: searchStatusRect.bottom anchors.top: searchStatusRect.bottom
@ -500,4 +479,3 @@ BaseView {
} }
} }
} }
}

View file

@ -66,12 +66,6 @@ ItemDelegate {
} }
} }
onVisibleChanged: {
if (visible)
return
UtilsAdapter.clearInteractionsCache(root.accountId, root.convId)
}
Connections { Connections {
target: MessagesAdapter target: MessagesAdapter
function onTimestampUpdated() { function onTimestampUpdated() {

View file

@ -27,20 +27,27 @@ import net.jami.Models 1.1
import "../../commoncomponents" import "../../commoncomponents"
import "../js/keyboardshortcuttablecreation.js" as KeyboardShortcutTableCreation import "../js/keyboardshortcuttablecreation.js" as KeyboardShortcutTableCreation
BaseView { ListSelectionView {
id: root id: viewNode
objectName: "WelcomePage"
splitViewStateKey: "Main"
hideRightPaneInSinglePaneMode: true
color: JamiTheme.secondaryBackgroundColor color: JamiTheme.secondaryBackgroundColor
JamiFlickable { onPresented: LRCInstance.deselectConversation()
id: welcomeView leftPaneItem: viewCoordinator.getView("SidePanel")
rightPaneItem: JamiFlickable {
id: root
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
enabled: visible enabled: visible
onClicked: welcomeView.forceActiveFocus() onClicked: root.forceActiveFocus()
} }
anchors.fill: root anchors.fill: parent
contentHeight: Math.max(root.height, welcomePageLayout.implicitHeight) contentHeight: Math.max(root.height, welcomePageLayout.implicitHeight)
contentWidth: Math.max(300, root.width) contentWidth: Math.max(300, root.width)

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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
}
}
}
}

View file

@ -29,19 +29,9 @@ import "components"
import "../commoncomponents" import "../commoncomponents"
import "../mainview/js/contactpickercreation.js" as ContactPickerCreation import "../mainview/js/contactpickercreation.js" as ContactPickerCreation
BaseView { ListSelectionView {
id: root id: viewNode
objectName: "SettingsView" objectName: "SettingsView"
requiresIndex: true
onDismissed: {
settingsViewRect.stopBooth()
if (UtilsAdapter.getAccountListSize() === 0) {
viewCoordinator.requestAppWindowWizardView()
} else {
AccountAdapter.changeAccount(0)
}
}
enum SettingsMenu { enum SettingsMenu {
Account, Account,
@ -50,9 +40,24 @@ BaseView {
Plugin 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: { onSelectedMenuChanged: {
if (selectedMenu === SettingsView.Account) { if (selectedMenu === SettingsView.Account) {
pageIdCurrentAccountSettings.updateAccountInfoDisplayed() pageIdCurrentAccountSettings.updateAccountInfoDisplayed()
@ -61,15 +66,10 @@ BaseView {
} }
} }
function setSelected(idx, recovery = false) { rightPaneItem: Rectangle {
if (selectedMenu === idx && !recovery) return
selectedMenu = idx
}
Rectangle {
id: settingsViewRect id: settingsViewRect
anchors.fill: root anchors.fill: parent
color: JamiTheme.secondaryBackgroundColor color: JamiTheme.secondaryBackgroundColor
signal stopBooth signal stopBooth
@ -98,6 +98,7 @@ BaseView {
title: { title: {
switch(selectedMenu){ switch(selectedMenu){
default:
case SettingsView.Account: case SettingsView.Account:
return JamiStrings.accountSettingsTitle return JamiStrings.accountSettingsTitle
case SettingsView.General: case SettingsView.General:
@ -109,7 +110,7 @@ BaseView {
} }
} }
onBackArrowClicked: viewCoordinator.hideCurrentView() onBackArrowClicked: viewNode.dismiss()
} }
JamiFlickable { JamiFlickable {
@ -137,6 +138,7 @@ BaseView {
currentIndex: { currentIndex: {
switch(selectedMenu){ switch(selectedMenu){
default:
case SettingsView.Account: case SettingsView.Account:
return pageIdCurrentAccountSettingsPage return pageIdCurrentAccountSettingsPage
case SettingsView.General: case SettingsView.General:

View file

@ -37,7 +37,7 @@ RowLayout {
Layout.preferredWidth: JamiTheme.preferredFieldHeight Layout.preferredWidth: JamiTheme.preferredFieldHeight
Layout.preferredHeight: JamiTheme.preferredFieldHeight Layout.preferredHeight: JamiTheme.preferredFieldHeight
visible: viewCoordinator.singlePane visible: viewNode.isSinglePane
onClicked: backArrowClicked() onClicked: backArrowClicked()
} }

View file

@ -1,103 +0,0 @@
/*
* Copyright (C) 2020-2023 Savoir-faire Linux Inc.
* Author: Andreas Traczyk <andreas.traczyk@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 <http://www.gnu.org/licenses/>.
*/
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
}
}
}

View file

@ -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 const QString
UtilsAdapter::getProjectCredits() UtilsAdapter::getProjectCredits()
{ {
@ -352,34 +392,6 @@ UtilsAdapter::setSystemTrayIconVisible(bool visible)
systemTray_->setVisible(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 QString
UtilsAdapter::getDirDocument() UtilsAdapter::getDirDocument()
{ {

View file

@ -36,8 +36,7 @@
#if defined(WIN32) && __has_include(<winrt/Windows.Foundation.h>) #if defined(WIN32) && __has_include(<winrt/Windows.Foundation.h>)
#include <winrt/Windows.Foundation.h> #include <winrt/Windows.Foundation.h>
#define WATCHSYSTEMTHEME \ #define WATCHSYSTEMTHEME __has_include(<winrt/Windows.UI.ViewManagement.h>)
__has_include(<winrt/Windows.UI.ViewManagement.h>)
#if WATCHSYSTEMTHEME #if WATCHSYSTEMTHEME
#include <winrt/Windows.UI.ViewManagement.h> #include <winrt/Windows.UI.ViewManagement.h>
@ -74,6 +73,11 @@ public:
QObject* parent = nullptr); QObject* parent = nullptr);
~UtilsAdapter() = default; ~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 getProjectCredits();
Q_INVOKABLE const QString getVersionStr(); Q_INVOKABLE const QString getVersionStr();
Q_INVOKABLE void setClipboardText(QString text); Q_INVOKABLE void setClipboardText(QString text);
@ -110,8 +114,6 @@ public:
Q_INVOKABLE bool isImage(const QString& fileExt); Q_INVOKABLE bool isImage(const QString& fileExt);
Q_INVOKABLE QString humanFileSize(qint64 fileSize); Q_INVOKABLE QString humanFileSize(qint64 fileSize);
Q_INVOKABLE void setSystemTrayIconVisible(bool visible); 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 getDirDocument();
Q_INVOKABLE QString getDirScreenshot(); Q_INVOKABLE QString getDirScreenshot();
Q_INVOKABLE QString getDirDownload(); Q_INVOKABLE QString getDirDownload();

View file

@ -33,7 +33,8 @@ import "components"
BaseView { BaseView {
id: root id: root
objectName: "WizardView" objectName: "WizardView"
singlePaneOnly: true
inhibits: ["ConversationView"]
// signal to redirect the page to main view // signal to redirect the page to main view
signal loaderSourceChangeRequested(int sourceToLoad) signal loaderSourceChangeRequested(int sourceToLoad)