1
0
Fork 0
mirror of https://git.jami.net/savoirfairelinux/jami-client-qt.git synced 2025-09-10 12:03:18 +02:00

chat-view: simplify chat details panels and save split states

- Saves (session only) the open state of the swarm details panel
- Saves the details index, and falls back when not available

Gitlab: #902
Change-Id: I7ad0c53007dac95f4b1984fc1fcf0094ae59a755
This commit is contained in:
Andreas Traczyk 2023-03-07 16:24:17 -05:00
parent 8832f40b19
commit 4d2e31a5f3
10 changed files with 229 additions and 281 deletions

View file

@ -577,8 +577,8 @@ Item {
property real mainViewMargin: 30
// Details page
property real detailsPageMinWidth: 300
// Extras panel
property real extrasPanelMinWidth: 300
property int aboutBtnSize: 24
// Messages point size

View file

@ -307,12 +307,6 @@ CurrentConversation::connectModel()
Qt::UniqueConnection);
}
void
CurrentConversation::showSwarmDetails()
{
Q_EMIT showDetails();
}
void
CurrentConversation::updateErrors(const QString& convId)
{

View file

@ -61,7 +61,6 @@ public:
explicit CurrentConversation(LRCInstance* lrcInstance, QObject* parent = nullptr);
~CurrentConversation() = default;
Q_INVOKABLE void scrollToMsg(const QString& msgId);
Q_INVOKABLE void showSwarmDetails();
Q_INVOKABLE void setPreference(const QString& key, const QString& value);
Q_INVOKABLE QString getPreference(const QString& key) const;
Q_INVOKABLE MapStringString getPreferences() const;
@ -71,7 +70,7 @@ public:
Q_SIGNALS:
void reloadInteractions();
void scrollTo(const QString& msgId);
void showDetails();
void showSwarmDetails();
private Q_SLOTS:
void updateData();

View file

@ -1,8 +1,5 @@
/*
* Copyright (C) 2020-2022 Savoir-faire Linux Inc.
* Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
* Author: Trevor Tabah <trevor.tabah@savoirfairelinux.com>
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
* Copyright (C) 2020-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
@ -21,11 +18,11 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import net.jami.Models 1.1
import net.jami.Adapters 1.1
import net.jami.Constants 1.1
import net.jami.Enums 1.1
import "../../commoncomponents"
import "../js/pluginhandlerpickercreation.js" as PluginHandlerPickerCreation
@ -33,13 +30,17 @@ import "../js/pluginhandlerpickercreation.js" as PluginHandlerPickerCreation
Rectangle {
id: root
// An enum to make the details panels more readable.
enum Panel {
SwarmDetailsPanel,
MessagesResearchPanel,
AddMemberPanel
}
color: JamiTheme.chatviewBgColor
property var mapPositions: PositionManager.mapStatus
property int lastContentsSplitSize: JamiTheme.detailsPageMinWidth
property int lastDetailsSplitSize: JamiTheme.detailsPageMinWidth
property int previousWidth: width
required property bool inCallView
signal dismiss
@ -51,10 +52,6 @@ Rectangle {
function resetPanels() {
chatViewHeader.showSearch = true
swarmDetailsPanel.visible = false
addMemberPanel.visible = false
chatContents.visible = true
messagesResearchPanel.visible = false
}
function instanceMapObject() {
@ -79,21 +76,24 @@ Rectangle {
Connections {
target: CurrentConversation
function onIdChanged() {
extrasPanel.restoreState()
MessagesAdapter.loadMoreMessages()
}
}
Component.onCompleted: extrasPanel.restoreState()
onVisibleChanged: {
if (visible){
chatViewHeader.showSearch = !root.parent.showDetails
addMemberPanel.visible = false
messagesResearchPanel.visible = false
if (root.parent.showDetails) {
chatContents.visible = false
swarmDetailsPanel.visible = true
} else {
chatContents.visible = true
swarmDetailsPanel.visible = false
if (visible) {
chatViewSplitView.resolvePanes(true)
if (root.parent.objectName === "CallViewChatViewContainer") {
chatViewHeader.showSearch = !root.parent.showDetails
if (root.parent.showDetails) {
extrasPanel.switchToPanel(ChatView.SwarmDetailsPanel)
} else {
extrasPanel.closePanel()
}
}
}
}
@ -118,60 +118,9 @@ Rectangle {
}
onBackClicked: root.dismiss()
signal panelsVisibilityChange()
onPanelsVisibilityChange: {
if (!swarmDetailsPanel.visible && !messagesResearchPanel.visible) {
chatContents.visible = true
} else {
if (chatViewHeader.width - JamiTheme.detailsPageMinWidth < JamiTheme.mainViewPaneMinWidth)
chatContents.visible = false
}
}
onShowDetailsClicked: {
addMemberPanel.visible = false
messagesResearchPanel.visible = false
swarmDetailsPanel.visible = !swarmDetailsPanel.visible
panelsVisibilityChange()
}
onSearchBarOpened: {
addMemberPanel.visible = false
swarmDetailsPanel.visible = false
messagesResearchPanel.visible = true
panelsVisibilityChange()
}
onSearchBarClosed: {
chatContents.visible = true
messagesResearchPanel.visible = false
panelsVisibilityChange()
}
onWidthChanged: {
if (inCallView)
return
const isExpanding = previousWidth < width
if (!swarmDetailsPanel.visible && !addMemberPanel.visible && !messagesResearchPanel.visible)
return
if (chatViewHeader.width < JamiTheme.detailsPageMinWidth + JamiTheme.mainViewPaneMinWidth
&& !isExpanding && chatContents.visible) {
lastContentsSplitSize = chatContents.width
lastDetailsSplitSize = Math.min(JamiTheme.detailsPageMinWidth, (swarmDetailsPanel.visible
? swarmDetailsPanel.width
: addMemberPanel.visible
? addMemberPanel.width
: messagesResearchPanel.width))
chatContents.visible = false
} else if (chatViewHeader.width >= JamiTheme.mainViewPaneMinWidth + lastDetailsSplitSize
&& isExpanding && !layoutManager.isFullScreen && !chatContents.visible) {
chatContents.visible = true
}
previousWidth = width
}
onShowDetailsClicked: extrasPanel.switchToPanel(ChatView.SwarmDetailsPanel)
onSearchClicked: extrasPanel.switchToPanel(ChatView.MessagesResearchPanel)
onAddToConversationClicked: extrasPanel.switchToPanel(ChatView.AddMemberPanel)
Connections {
target: CurrentConversation
@ -183,28 +132,6 @@ Rectangle {
}
}
Connections {
target: CurrentConversationMembers
function onCountChanged() {
if (CurrentConversationMembers.count >= 8 && addMemberPanel.visible) {
swarmDetailsPanel.visible = false
addMemberPanel.visible = !addMemberPanel.visible
}
}
}
onAddToConversationClicked: {
swarmDetailsPanel.visible = false
if (addMemberPanel.visible) {
chatContents.visible = true
} else {
if (chatViewHeader.width - JamiTheme.detailsPageMinWidth < JamiTheme.mainViewPaneMinWidth)
chatContents.visible = false
}
addMemberPanel.visible = !addMemberPanel.visible
}
onPluginSelector: {
// Create plugin handler picker - PLUGINS
PluginHandlerPickerCreation.createPluginHandlerPickerObjects(
@ -270,20 +197,50 @@ Rectangle {
visible: CurrentConversation.activeCalls.length > 0 && !root.inCallView
}
SplitView {
id: chatViewMainRow
JamiSplitView {
id: chatViewSplitView
objectName: "ChatViewSplitView"
Layout.fillWidth: true
Layout.fillHeight: true
handle: Rectangle {
implicitWidth: JamiTheme.splitViewHandlePreferredWidth
implicitHeight: root.height
color: JamiTheme.primaryBackgroundColor
Rectangle {
implicitWidth: 1
implicitHeight: root.height
color: JamiTheme.tabbarBorderColor
splitViewStateKey: "Chat"
property real previousDetailsWidth: extrasPanel.width
property real previousWidth: width
onWidthChanged: resolvePanes()
// This function governs the visibility of the chatContents and tracks the
// the width of the SplitView and the details panel. This function should be
// called when the width of the SplitView changes, when the SplitView is shown,
// and when the details panel is shown. When called with force=true, it is being
// called from a visibleChanged event, and we should not update the previous widths.
function resolvePanes(force=false) {
// If the details panel is not visible, then show the chatContents.
if (!extrasPanel.visible) {
chatContents.visible = true
return
}
// Next we compute whether the SplitView is expanding or shrinking.
const isExpanding = width > previousWidth
// If the SplitView is not wide enough to show both the chatContents
// and the details panel, then hide the chatContents.
if (width < JamiTheme.mainViewPaneMinWidth + extrasPanel.width
&& (!isExpanding || force) && chatContents.visible) {
if (!force) previousDetailsWidth = extrasPanel.width
chatContents.visible = false
} else if (width >= JamiTheme.mainViewPaneMinWidth + previousDetailsWidth
&& (isExpanding || force) && !chatContents.visible) {
chatContents.visible = true
}
if (!force) previousWidth = width
}
Connections {
target: viewNode
function onPresented() { chatViewSplitView.restoreSplitViewState() }
function onDismissed() { chatViewSplitView.saveSplitViewState() }
}
ColumnLayout {
@ -358,31 +315,18 @@ Rectangle {
}
}
MessagesResearchPanel {
id: messagesResearchPanel
onResizingChanged: if (chatContents.visible) extrasPanel.previousWidth = extrasPanel.width
visible: false
SplitView.maximumWidth: root.width
SplitView.minimumWidth: JamiTheme.detailsPageMinWidth
SplitView.preferredWidth: JamiTheme.detailsPageMinWidth
}
ConversationExtrasPanel {
id: extrasPanel
SwarmDetailsPanel {
id: swarmDetailsPanel
visible: false
property int previousWidth: JamiTheme.extrasPanelMinWidth
SplitView.maximumWidth: root.width
SplitView.preferredWidth: JamiTheme.detailsPageMinWidth
SplitView.minimumWidth: JamiTheme.detailsPageMinWidth
}
SplitView.minimumWidth: JamiTheme.extrasPanelMinWidth
SplitView.preferredWidth: JamiTheme.extrasPanelMinWidth
AddMemberPanel {
id: addMemberPanel
visible: false
SplitView.maximumWidth: root.width
SplitView.preferredWidth: JamiTheme.detailsPageMinWidth
SplitView.minimumWidth: JamiTheme.detailsPageMinWidth
onVisibleChanged: chatViewSplitView.resolvePanes(true)
}
}
}

View file

@ -37,15 +37,14 @@ Rectangle {
signal addToConversationClicked
signal pluginSelector
signal showDetailsClicked
signal searchBarOpened
signal searchBarClosed
signal searchClicked
Connections {
target: CurrentConversation
enabled: true
function onTitleChanged() { title.eText = CurrentConversation.title }
function onDescriptionChanged() { description.eText = CurrentConversation.description }
function onShowDetails() { root.showDetailsClicked() }
function onShowSwarmDetails() { root.showDetailsClicked() }
}
property bool interactionButtonsVisibility: {

View file

@ -0,0 +1,87 @@
/*
* 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.Layouts
import net.jami.Adapters 1.1
StackLayout {
id: root
property int detailsIndex: -1
function isOpen(panel) { return visible && currentIndex === panel }
visible: currentIndex > -1
property bool detailsShouldOpen: false
onVisibleChanged: if (visible) detailsShouldOpen = true
function restoreState() {
// Only applies to Jami accounts, and we musn't be in a call.
if (detailsShouldOpen && !inCallView && !CurrentConversation.isSip) {
switchToPanel(ChatView.SwarmDetailsPanel, false)
} else {
closePanel()
}
}
Connections {
target: CurrentConversationMembers
function onCountChanged() {
// Close the panel if there are 8 or more members in the
// conversation AND the "Add Member" panel is currently open.
if (CurrentConversationMembers.count >= 8
&& isOpen(ChatView.AddMemberPanel)) {
closePanel();
}
}
}
// This will open the details panel if it's not already visible.
// Additionally, `toggle` being true (default) will close the panel
// if it is already open to `panel`.
function switchToPanel(panel, toggle=true) {
if (visible && toggle && currentIndex === panel) {
closePanel()
} else {
currentIndex = panel
}
}
function closePanel() {
// We need to close the panel, but not save it when appropriate.
currentIndex = -1
if (!inCallView && !CurrentConversation.isSip)
detailsShouldOpen = false
}
SwarmDetailsPanel {
id: detailsPanel
property int parentIndex: root.currentIndex
// When we change to the details panel we should load the tab index.
onParentIndexChanged: tabBarIndex = Math.min(tabBarItemsLength - 1,
Math.max(0, root.detailsIndex))
// Save it when it changes.
onTabBarIndexChanged: root.detailsIndex = tabBarIndex
}
MessagesResearchPanel {}
AddMemberPanel {}
}

View file

@ -41,7 +41,9 @@ ListView {
property var messageListModel: MessagesAdapter.mediaMessageListModel
readonly property int textType: Interaction.Type.TEXT
onMessageListModelChanged: sourceModel = root.visible ? messageListModel : null
onMessageListModelChanged: sourceModel = root.visible && messageListModel ?
messageListModel :
null
filters: ExpressionFilter {
expression: Type === proxyModel.textType

View file

@ -400,6 +400,7 @@ Rectangle {
Item {
id: chatViewContainer
objectName: "CallViewChatViewContainer"
SplitView.preferredHeight: mainColumnLayout.isHorizontal ?
root.height :

View file

@ -28,48 +28,10 @@ import "../../commoncomponents"
RowLayout {
id: root
property real messagesResearchPanel: JamiTheme.detailsPageMinWidth
//TO DO: find a design to set dynamically the size of the searchbar
property real searchBarWidth: JamiTheme.searchbarSize
property string currentConversationId: CurrentConversation.id
property bool isOpened: false
function openSearchBar() {
if (isOpened) {
textArea.forceActiveFocus()
return
}
searchBarOpened()
rectTextArea.isSearch = true
anim.start()
textArea.forceActiveFocus()
isOpened = true
}
function closeSearchbar() {
if (!isOpened)
return
searchBarClosed()
rectTextArea.isSearch = false
anim.start()
isOpened = false
}
Connections {
target: chatViewHeader
function onShowDetailsClicked() {
if (rectTextArea.isSearch)
closeSearchbar()
}
}
onCurrentConversationIdChanged: {
if (isOpened)
closeSearchbar()
}
property bool isOpen: extrasPanel.isOpen(ChatView.MessagesResearchPanel)
onIsOpenChanged: if (isOpen) textArea.forceActiveFocus()
PushButton {
id: startSearchMessages
@ -78,66 +40,26 @@ RowLayout {
normalColor: JamiTheme.chatviewBgColor
imageColor: JamiTheme.chatviewButtonColor
onClicked: {
if (rectTextArea.isSearch)
closeSearchbar()
else
openSearchBar()
}
onClicked: chatViewHeader.searchClicked()
}
SequentialAnimation {
id: anim
PropertyAnimation {
target: rectTextArea; properties: "visible"
to: true
duration: 0
}
ParallelAnimation {
NumberAnimation {
target: rectTextArea; properties: "opacity"
from: rectTextArea.isSearch ? 0 : 1
to: rectTextArea.isSearch ? 1 : 0
duration: 150
}
NumberAnimation {
target: rectTextArea; properties: "Layout.preferredWidth"
from: rectTextArea.isSearch ? 0 : root.searchBarWidth
to: rectTextArea.isSearch ? root.searchBarWidth : 0
duration: 150
}
}
PropertyAnimation {
target: rectTextArea; properties: "visible"
to: rectTextArea.isSearch
duration: 0
}
}
Rectangle {
id: rectTextArea
visible: false
Layout.preferredHeight: startSearchMessages.height
Layout.alignment: Qt.AlignVCenter
color: "transparent"
border.color: JamiTheme.chatviewTextColor
radius: 10
border.width: 2
property bool isSearch: false
property int textAreaWidth: 200
property alias searchBarWidth: root.searchBarWidth
opacity: isOpen
visible: opacity
Behavior on opacity { NumberAnimation { duration: 150 } }
onSearchBarWidthChanged: {
Layout.preferredWidth = root.searchBarWidth
}
Layout.preferredWidth: isOpen ? JamiTheme.searchbarSize : 0
Behavior on Layout.preferredWidth { NumberAnimation { duration: 150 } }
TextField {
id: textArea

View file

@ -32,6 +32,9 @@ import "../../settingsview/components"
Rectangle {
id: root
property alias tabBarIndex: tabBar.currentIndex
property int tabBarItemsLength: tabBar.contentChildren.length
color: CurrentConversation.color
property var isAdmin: !CurrentConversation.isCoreDialog &&
UtilsAdapter.getParticipantRole(CurrentAccount.id,
@ -193,74 +196,71 @@ Rectangle {
currentIndex: 0
onVisibleChanged: {
tabBar.currentIndex = 0
}
Layout.preferredWidth: root.width
Layout.preferredHeight: settingsTabButton.height
FilterTabButton {
id: settingsTabButton
property string currentItemName: itemAt(currentIndex).objectName
component DetailsTabButton: FilterTabButton {
backgroundColor: CurrentConversation.color
hoverColor: CurrentConversation.color
borderWidth: 4
bottomMargin: JamiTheme.settingsMarginSize
fontSize: JamiTheme.menuFontSize
underlineContentOnly: true
textColorHovered: UtilsAdapter.luma(root.color) ? JamiTheme.placeholderTextColorWhite : JamiTheme.placeholderTextColor
textColorHovered: UtilsAdapter.luma(root.color) ?
JamiTheme.placeholderTextColorWhite :
JamiTheme.placeholderTextColor
textColor: UtilsAdapter.luma(root.color) ?
JamiTheme.chatviewTextColorLight :
JamiTheme.chatviewTextColorDark
down: tabBar.currentIndex === 0
labelText: JamiStrings.settings
Layout.fillWidth: true
down: tabBar.currentIndex === TabBar.index
}
FilterTabButton {
id: membersTabButton
visible: !CurrentConversation.isCoreDialog
Layout.fillWidth: true
width: visible ? tabBar.width/3 : 0
backgroundColor: CurrentConversation.color
hoverColor: CurrentConversation.color
borderWidth: 4
bottomMargin: JamiTheme.settingsMarginSize
fontSize: JamiTheme.menuFontSize
underlineContentOnly: true
textColorHovered: UtilsAdapter.luma(root.color) ? JamiTheme.placeholderTextColorWhite : JamiTheme.placeholderTextColor
textColor: UtilsAdapter.luma(root.color) ?
JamiTheme.chatviewTextColorLight :
JamiTheme.chatviewTextColorDark
down: tabBar.currentIndex === 1
labelText: {
var membersNb = CurrentConversationMembers.count;
if (membersNb > 1)
return JamiStrings.members.arg(membersNb)
return JamiStrings.member
function addRemoveButtons() {
if (CurrentConversation.isCoreDialog) {
if (tabBar.contentChildren.length === 3)
tabBar.removeItem(tabBar.itemAt(1))
} else {
if (tabBar.contentChildren.length === 2) {
const obj = membersTabButtonComp.createObject(tabBar)
tabBar.insertItem(1, obj)
}
}
}
FilterTabButton {
Component.onCompleted: addRemoveButtons()
Connections {
target: CurrentConversation
function onIsCoreDialogChanged() { tabBar.addRemoveButtons() }
}
Component {
id: membersTabButtonComp
DetailsTabButton {
id: membersTabButton
objectName: "members"
visible: !CurrentConversation.isCoreDialog
labelText: {
var membersNb = CurrentConversationMembers.count;
if (membersNb > 1)
return JamiStrings.members.arg(membersNb)
return JamiStrings.member
}
}
}
DetailsTabButton {
id: settingsTabButton
objectName: "settings"
labelText: JamiStrings.settings
}
DetailsTabButton {
id: documentsTabButton
backgroundColor: CurrentConversation.color
hoverColor: CurrentConversation.color
borderWidth: 4
bottomMargin: JamiTheme.settingsMarginSize
fontSize: JamiTheme.menuFontSize
underlineContentOnly: true
Layout.fillWidth: true
textColorHovered: UtilsAdapter.luma(root.color) ? JamiTheme.placeholderTextColorWhite : JamiTheme.placeholderTextColor
textColor: UtilsAdapter.luma(root.color) ?
JamiTheme.chatviewTextColorLight :
JamiTheme.chatviewTextColorDark
down: tabBar.currentIndex === 2
objectName: "documents"
labelText: JamiStrings.documents
}
}
@ -298,7 +298,7 @@ Rectangle {
anchors.left: parent.left
anchors.right: parent.right
anchors.rightMargin: JamiTheme.settingsMarginSize
visible: tabBar.currentIndex === 0
visible: tabBar.currentItemName === "settings"
Layout.alignment: Qt.AlignTop
SwarmDetailsItem {
@ -589,7 +589,7 @@ Rectangle {
anchors.bottomMargin: JamiTheme.preferredMarginSize
anchors.fill: parent
visible: tabBar.currentIndex === 1
visible: tabBar.currentItemName === "members"
SwarmParticipantContextMenu {
id: contextMenu
@ -708,7 +708,7 @@ Rectangle {
id: documents
clip: true
visible: tabBar.currentIndex === 2
visible: tabBar.currentItemName === "documents"
anchors.fill: parent
}
}