1
0
Fork 0
mirror of https://git.jami.net/savoirfairelinux/jami-client-qt.git synced 2025-09-02 13:13:39 +02:00

Feature: search messages

Change-Id: Ia458e2e6ee183cad9d0418af0dbbbcd990f14281
GitLab: #918
This commit is contained in:
Nicolas Vengeon 2023-02-03 11:29:37 -05:00 committed by Sébastien Blin
parent c2d81149be
commit 06ab19f213
16 changed files with 710 additions and 161 deletions

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="M39.8 41.95 26.65 28.8q-1.5 1.3-3.5 2.025-2 .725-4.25.725-5.4 0-9.15-3.75T6 18.75q0-5.3 3.75-9.05 3.75-3.75 9.1-3.75 5.3 0 9.025 3.75 3.725 3.75 3.725 9.05 0 2.15-.7 4.15-.7 2-2.1 3.75L42 39.75Zm-20.95-13.4q4.05 0 6.9-2.875Q28.6 22.8 28.6 18.75t-2.85-6.925Q22.9 8.95 18.85 8.95q-4.1 0-6.975 2.875T9 18.75q0 4.05 2.875 6.925t6.975 2.875Z"/></svg>

After

Width:  |  Height:  |  Size: 417 B

View file

@ -354,6 +354,12 @@ Item {
property string noNetworkConnectivity: qsTr("No network connectivity") property string noNetworkConnectivity: qsTr("No network connectivity")
property string deletedMessage: qsTr("Deleted message") property string deletedMessage: qsTr("Deleted message")
//MessagesResearch
property string jumpTo: qsTr("Jump to")
property string messages: qsTr("Messages")
property string files: qsTr("Files")
property string search: qsTr("Search")
// Chatview footer // Chatview footer
property string jumpToLatest: qsTr("Jump to latest") property string jumpToLatest: qsTr("Jump to latest")
property string typeIndicatorSingle: qsTr("{} is typing…") property string typeIndicatorSingle: qsTr("{} is typing…")

View file

@ -376,7 +376,7 @@ Item {
property real swarmDetailsPageDocumentsMargins: 5 property real swarmDetailsPageDocumentsMargins: 5
property real swarmDetailsPageDocumentsMediaRadius: 15 property real swarmDetailsPageDocumentsMediaRadius: 15
property real swarmDetailsPageDocumentsPaperClipSize: 24 property real swarmDetailsPageDocumentsPaperClipSize: 24
property real swarmDetailsPageDocumentsMediaSize: 175 property real swarmDetailsPageDocumentsMediaSize: 150
//Call information //Call information
property real textFontPointSize: calcSize(10) property real textFontPointSize: calcSize(10)
@ -401,6 +401,11 @@ Item {
// Modal Popup // Modal Popup
property real modalPopupRadius: 20 property real modalPopupRadius: 20
//MessagesResearch
property color blueLinkColor: darkTheme ? "#3366BB" : "#0645AD"
property real jumpToFontSize: calcSize(13)
property real searchbarSize: 200
// MessageWebView // MessageWebView
property real chatViewHairLineSize: 1 property real chatViewHairLineSize: 1
property real chatViewMaximumWidth: 900 property real chatViewMaximumWidth: 900

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020-2023 Savoir-faire Linux Inc. * Copyright (C) 2020-2022 Savoir-faire Linux Inc.
* Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com> * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
* Author: Trevor Tabah <trevor.tabah@savoirfairelinux.com> * Author: Trevor Tabah <trevor.tabah@savoirfairelinux.com>
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
@ -102,30 +102,53 @@ Rectangle {
onBackClicked: root.dismiss() onBackClicked: root.dismiss()
onShowDetailsClicked: { signal panelsVisibilityChange()
addMemberPanel.visible = false
if (swarmDetailsPanel.visible) { onPanelsVisibilityChange: {
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.chatViewHeaderMinimumWidth)
chatContents.visible = false chatContents.visible = false
} }
}
onShowDetailsClicked: {
addMemberPanel.visible = false
messagesResearchPanel.visible = false
swarmDetailsPanel.visible = !swarmDetailsPanel.visible 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: { onWidthChanged: {
const isExpanding = previousWidth < width const isExpanding = previousWidth < width
if (!swarmDetailsPanel.visible && !addMemberPanel.visible)
if (!swarmDetailsPanel.visible && !addMemberPanel.visible && !messagesResearchPanel.visible)
return return
if (chatViewHeader.width < JamiTheme.detailsPageMinWidth + JamiTheme.chatViewHeaderMinimumWidth if (chatViewHeader.width < JamiTheme.detailsPageMinWidth + JamiTheme.chatViewHeaderMinimumWidth
&& !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
swarmDetailsPanel.width : ? swarmDetailsPanel.width
addMemberPanel.width)) : addMemberPanel.visible
? addMemberPanel.width
: messagesResearchPanel.width))
chatContents.visible = false chatContents.visible = false
} else if (chatViewHeader.width >= JamiTheme.chatViewHeaderMinimumWidth + lastDetailsSplitSize } else if (chatViewHeader.width >= JamiTheme.chatViewHeaderMinimumWidth + lastDetailsSplitSize
&& isExpanding && !layoutManager.isFullScreen && !chatContents.visible) { && isExpanding && !layoutManager.isFullScreen && !chatContents.visible) {
chatContents.visible = true chatContents.visible = true
} }
previousWidth = width previousWidth = width
@ -244,10 +267,7 @@ Rectangle {
id: chatContents id: chatContents
SplitView.maximumWidth: viewCoordinator.splitView.width SplitView.maximumWidth: viewCoordinator.splitView.width
SplitView.minimumWidth: JamiTheme.chatViewHeaderMinimumWidth SplitView.minimumWidth: JamiTheme.chatViewHeaderMinimumWidth
SplitView.fillWidth: true
SplitView.preferredWidth: chatViewHeader.width -
(swarmDetailsPanel.visible ? swarmDetailsPanel.width :
( addMemberPanel.visible ? addMemberPanel.width : 0))
StackLayout { StackLayout {
id: chatViewStack id: chatViewStack
@ -315,6 +335,15 @@ Rectangle {
} }
} }
MessagesResearchPanel {
id: messagesResearchPanel
visible: false
SplitView.maximumWidth: viewCoordinator.splitView.width
SplitView.minimumWidth: JamiTheme.detailsPageMinWidth
SplitView.preferredWidth: JamiTheme.detailsPageMinWidth
}
SwarmDetailsPanel { SwarmDetailsPanel {
id: swarmDetailsPanel id: swarmDetailsPanel
visible: false visible: false

View file

@ -19,6 +19,7 @@
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Controls
import net.jami.Adapters 1.1 import net.jami.Adapters 1.1
import net.jami.Constants 1.1 import net.jami.Constants 1.1
@ -34,6 +35,8 @@ Rectangle {
signal addToConversationClicked signal addToConversationClicked
signal pluginSelector signal pluginSelector
signal showDetailsClicked signal showDetailsClicked
signal searchBarOpened
signal searchBarClosed
Connections { Connections {
target: CurrentConversation target: CurrentConversation
@ -145,98 +148,117 @@ Rectangle {
RowLayout { RowLayout {
id: buttonGroup id: buttonGroup
property int buttonGroupMargin: 8
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
Layout.fillWidth: true Layout.rightMargin: buttonGroupMargin
Layout.rightMargin: 8
spacing: 16 spacing: 16
Layout.fillWidth: true
PushButton { Searchbar {
id: startAAudioCallButton id: rowSearchBar
visible: interactionButtonsVisibility && (!addMemberVisibility || UtilsAdapter.getAppValue(Settings.EnableExperimentalSwarm)) spacing: buttonGroup.spacing
visible: CurrentConversation.isSwarm
source: JamiResources.place_audiocall_24dp_svg
toolTipText: JamiStrings.placeAudioCall
normalColor: JamiTheme.chatviewBgColor
imageColor: JamiTheme.chatviewButtonColor
onClicked: CallAdapter.placeAudioOnlyCall()
} }
PushButton { RowLayout {
id: startAVideoCallButton id: pushbuttons
visible: CurrentAccount.videoEnabled_Video && interactionButtonsVisibility && (!addMemberVisibility || UtilsAdapter.getAppValue(Settings.EnableExperimentalSwarm)) Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
source: JamiResources.videocam_24dp_svg Layout.rightMargin: 8
toolTipText: JamiStrings.placeVideoCall spacing: 16
Layout.fillWidth: true
normalColor: JamiTheme.chatviewBgColor PushButton {
imageColor: JamiTheme.chatviewButtonColor id: startAAudioCallButton
onClicked: { visible: interactionButtonsVisibility && (!addMemberVisibility || UtilsAdapter.getAppValue(Settings.EnableExperimentalSwarm))
CallAdapter.placeCall()
source: JamiResources.place_audiocall_24dp_svg
toolTipText: JamiStrings.placeAudioCall
normalColor: JamiTheme.chatviewBgColor
imageColor: JamiTheme.chatviewButtonColor
onClicked: CallAdapter.placeAudioOnlyCall()
}
PushButton {
id: startAVideoCallButton
visible: CurrentAccount.videoEnabled_Video && interactionButtonsVisibility && (!addMemberVisibility || UtilsAdapter.getAppValue(Settings.EnableExperimentalSwarm))
source: JamiResources.videocam_24dp_svg
toolTipText: JamiStrings.placeVideoCall
normalColor: JamiTheme.chatviewBgColor
imageColor: JamiTheme.chatviewButtonColor
onClicked: {
CallAdapter.placeCall()
}
}
PushButton {
id: addParticipantsButton
source: JamiResources.add_people_24dp_svg
toolTipText: JamiStrings.addParticipants
normalColor: JamiTheme.chatviewBgColor
imageColor: JamiTheme.chatviewButtonColor
visible: CurrentConversation.uris.length < 8 && addMemberVisibility
onClicked: addToConversationClicked()
}
PushButton {
id: selectPluginButton
visible: PluginAdapter.isEnabled && PluginAdapter.chatHandlersListCount &&
interactionButtonsVisibility
source: JamiResources.plugins_24dp_svg
toolTipText: JamiStrings.showPlugins
normalColor: JamiTheme.chatviewBgColor
imageColor: JamiTheme.chatviewButtonColor
onClicked: pluginSelector()
}
PushButton {
id: sendContactRequestButton
visible: CurrentConversation.isTemporary || CurrentConversation.isBanned
source: JamiResources.add_people_24dp_svg
toolTipText: JamiStrings.addToConversations
normalColor: JamiTheme.chatviewBgColor
imageColor: JamiTheme.chatviewButtonColor
onClicked: CurrentConversation.isBanned ?
MessagesAdapter.unbanConversation(CurrentConversation.id)
: MessagesAdapter.sendConversationRequest()
}
PushButton {
id: detailsButton
visible: swarmDetailsVisibility
source: JamiResources.swarm_details_panel_svg
toolTipText: JamiStrings.details
normalColor: JamiTheme.chatviewBgColor
imageColor: JamiTheme.chatviewButtonColor
onClicked: showDetailsClicked()
} }
} }
PushButton {
id: addParticipantsButton
source: JamiResources.add_people_24dp_svg
toolTipText: JamiStrings.addParticipants
normalColor: JamiTheme.chatviewBgColor
imageColor: JamiTheme.chatviewButtonColor
visible: CurrentConversation.uris.length < 8 && addMemberVisibility
onClicked: addToConversationClicked()
}
PushButton {
id: selectPluginButton
visible: PluginAdapter.isEnabled && PluginAdapter.chatHandlersListCount &&
interactionButtonsVisibility
source: JamiResources.plugins_24dp_svg
toolTipText: JamiStrings.showPlugins
normalColor: JamiTheme.chatviewBgColor
imageColor: JamiTheme.chatviewButtonColor
onClicked: pluginSelector()
}
PushButton {
id: sendContactRequestButton
visible: CurrentConversation.isTemporary || CurrentConversation.isBanned
source: JamiResources.add_people_24dp_svg
toolTipText: JamiStrings.addToConversations
normalColor: JamiTheme.chatviewBgColor
imageColor: JamiTheme.chatviewButtonColor
onClicked: CurrentConversation.isBanned ?
MessagesAdapter.unbanConversation(CurrentConversation.id)
: MessagesAdapter.sendConversationRequest()
}
PushButton {
id: detailsButton
visible: swarmDetailsVisibility
source: JamiResources.swarm_details_panel_svg
toolTipText: JamiStrings.details
normalColor: JamiTheme.chatviewBgColor
imageColor: JamiTheme.chatviewButtonColor
onClicked: showDetailsClicked()
}
} }
Component.onCompleted: JamiQmlUtils.messagingHeaderRectRowLayout = messagingHeaderRectRowLayout Component.onCompleted: JamiQmlUtils.messagingHeaderRectRowLayout = messagingHeaderRectRowLayout
} }

View file

@ -128,7 +128,9 @@ Rectangle {
Shortcut { Shortcut {
sequence: "Ctrl+F" sequence: "Ctrl+F"
context: Qt.ApplicationShortcut context: Qt.ApplicationShortcut
onActivated: contactSearchBar.forceActiveFocus() onActivated: {
contactSearchBar.forceActiveFocus()
}
} }
Keys.onPressed: function (keyEvent) { Keys.onPressed: function (keyEvent) {

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (C) 2022-2023 Savoir-faire Linux Inc. * Copyright (C) 2022-2023 Savoir-faire Linux Inc.
* Author: Nicolas Vengeon <nicolas.vengeon@savoirfairelinux.com>
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -35,39 +36,46 @@ Flickable {
contentWidth: width contentWidth: width
property int spacingFlow: JamiTheme.swarmDetailsPageDocumentsMargins property int spacingFlow: JamiTheme.swarmDetailsPageDocumentsMargins
property real flickableWidth: width
property int numberElementsPerRow: { property int numberElementsPerRow: {
var sizeW = flow.width var sizeW = flow.width
var breakSize = JamiTheme.swarmDetailsPageDocumentsMediaSize var breakSize = JamiTheme.swarmDetailsPageDocumentsMediaSize
return Math.floor(sizeW / breakSize) return Math.floor(sizeW / breakSize)
} }
property int spacingLength: spacingFlow * (numberElementsPerRow - 1) property int spacingLength: spacingFlow * (numberElementsPerRow - 1)
property color themeColor: CurrentConversation.color
property string textFilter: ""
onVisibleChanged: { onVisibleChanged: {
if (visible) { if (visible) {
MessagesAdapter.getConvMedias() MessagesAdapter.startSearch(textFilter,true)
} else {
MessagesAdapter.mediaMessageListModel = null
} }
} }
onTextFilterChanged: {
MessagesAdapter.startSearch(textFilter,true)
}
Flow { Flow {
id: flow id: flow
width: parent.width width: parent.width
spacing: spacingFlow spacing: spacingFlow
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
Repeater { Repeater {
model: MessagesAdapter.mediaMessageListModel model: root.visible ? MessagesAdapter.mediaMessageListModel : 0
delegate: Loader { delegate: Loader {
id: loaderRoot id: loaderRoot
sourceComponent: { sourceComponent: {
if(Status === Interaction.Status.TRANSFER_FINISHED || Status === Interaction.Status.SUCCESS ){ if (MessagesAdapter.isDocument(Type)) {
if (Object.keys(MessagesAdapter.getMediaInfo(Body)).length !== 0 && WITH_WEBENGINE) if(Status === Interaction.Status.TRANSFER_FINISHED || Status === Interaction.Status.SUCCESS ){
return localMediaMsgComp if (Object.keys(MessagesAdapter.getMediaInfo(Body)).length !== 0 && WITH_WEBENGINE)
return localMediaMsgComp
return fileMsgComp return fileMsgComp
}
} }
} }

View file

@ -34,7 +34,7 @@ Component {
id: dataTransferRect id: dataTransferRect
clip: true clip: true
width: (documents.width - spacingLength ) / numberElementsPerRow width: (contentWidth - spacingLength ) / numberElementsPerRow
height: width height: width
color: "transparent" color: "transparent"
@ -64,7 +64,7 @@ Component {
anchors.fill: parent anchors.fill: parent
anchors.margins: JamiTheme.swarmDetailsPageDocumentsMargins anchors.margins: JamiTheme.swarmDetailsPageDocumentsMargins
color: "transparent" color: "transparent"
border.color: CurrentConversation.color border.color: themeColor
border.width: 2 border.width: 2
radius: JamiTheme.swarmDetailsPageDocumentsMediaRadius radius: JamiTheme.swarmDetailsPageDocumentsMediaRadius
layer.enabled: true layer.enabled: true

View file

@ -33,7 +33,7 @@ Component {
Rectangle { Rectangle {
id: localMediaRect id: localMediaRect
width: (documents.width - spacingLength) / numberElementsPerRow width: (flickableWidth - spacingLength) / numberElementsPerRow
height: width height: width
color: "transparent" color: "transparent"
@ -62,7 +62,7 @@ Component {
anchors.fill: parent anchors.fill: parent
anchors.margins: JamiTheme.swarmDetailsPageDocumentsMargins anchors.margins: JamiTheme.swarmDetailsPageDocumentsMargins
color: CurrentConversation.color color: themeColor
layer.enabled: true layer.enabled: true
layer.effect: OpacityMask { layer.effect: OpacityMask {
maskSource: Item { maskSource: Item {

View file

@ -0,0 +1,109 @@
/*
* Copyright (C) 2023 Savoir-faire Linux Inc.
* Author: Nicolas Vengeon <nicolas.vengeon@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt.labs.platform
import Qt5Compat.GraphicalEffects
import net.jami.Models 1.1
import net.jami.Adapters 1.1
import net.jami.Constants 1.1
import "../../commoncomponents"
import "../../settingsview/components"
Rectangle {
id: root
color: JamiTheme.chatviewBgColor
ColumnLayout {
anchors.fill: parent
TabBar {
id: researchTabBar
currentIndex: 0
Layout.preferredHeight: contentHeight + 10
Layout.preferredWidth: root.width
background.visible: false
signal filterTabChange()
onCurrentIndexChanged: {
filterTabChange()
}
onVisibleChanged: {
researchTabBar.currentIndex = 0
}
FilterTabButton {
id: messagesResearchTabButton
backgroundColor: "transparent"
hoverColor: "transparent"
borderWidth: 4
bottomMargin: JamiTheme.settingsMarginSize
fontSize: JamiTheme.menuFontSize
underlineContentOnly: true
down: researchTabBar.currentIndex === 0
labelText: JamiStrings.messages
Layout.fillWidth: true
}
FilterTabButton {
id: fileResearchTabButton
backgroundColor: "transparent"
hoverColor: "transparent"
borderWidth: 4
bottomMargin: JamiTheme.settingsMarginSize
fontSize: JamiTheme.menuFontSize
underlineContentOnly: true
down: researchTabBar.currentIndex === 1
labelText: JamiStrings.files
Layout.fillWidth: true
}
}
Rectangle {
id: view
color: JamiTheme.chatviewBgColor
Layout.fillWidth: true
Layout.fillHeight: true
MessagesResearchView {
anchors.fill: parent
visible: researchTabBar.currentIndex === 0
clip: true
}
DocumentsScrollview {
anchors.fill: parent
visible: researchTabBar.currentIndex === 1
clip: true
themeColor: JamiTheme.chatviewTextColor
textFilter: MessagesAdapter.searchbarPrompt
}
}
}
}

View file

@ -0,0 +1,163 @@
/*
* Copyright (C) 2023 Savoir-faire Linux Inc.
* Author: Nicolas Vengeon <nicolas.vengeon@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt.labs.platform
import Qt5Compat.GraphicalEffects
import SortFilterProxyModel
import net.jami.Models 1.1
import net.jami.Adapters 1.1
import net.jami.Constants 1.1
import "../../commoncomponents"
import "../../settingsview/components"
ListView {
id: root
spacing: 10
model: SortFilterProxyModel {
id: proxyModel
property var messageListModel: MessagesAdapter.mediaMessageListModel
readonly property int textType: Interaction.Type.TEXT
onMessageListModelChanged: sourceModel = root.visible ? messageListModel : null
filters: ExpressionFilter {
expression: Type === proxyModel.textType
}
}
property var prompt: MessagesAdapter.searchbarPrompt
onPromptChanged: {
MessagesAdapter.startSearch(prompt)
}
Connections {
target: researchTabBar
function onFilterTabChange() {
MessagesAdapter.startSearch(prompt)
}
}
delegate: Item {
width: root.width
height: msgLayout.height
HoverHandler {
id: msgHover
target: parent
}
ColumnLayout {
id: msgLayout
width: root.width
TimestampInfo {
id: timestampItem
showDay: true
showTime: true
formattedTime: MessagesAdapter.getFormattedTime(Timestamp)
formattedDay: MessagesAdapter.getFormattedDay(Timestamp)
}
RowLayout {
id: contentRow
property bool isMe: Author === CurrentAccount.uri
Avatar {
id: avatar
width: 30
height: 30
imageId: contentRow.isMe ? CurrentAccount.id : Author
showPresenceIndicator: false
mode: contentRow.isMe ? Avatar.Mode.Account : Avatar.Mode.Contact
Layout.leftMargin: 10
}
ColumnLayout {
Text {
text: contentRow.isMe
? CurrentAccount.bestName
: UtilsAdapter.getBestNameForUri(CurrentAccount.id, Author) + " :"
Layout.preferredWidth: myText.width
Layout.rightMargin: 10
Layout.leftMargin: 10
font.pixelSize: 0
color: JamiTheme.chatviewUsernameColor
font.bold: true
}
Text {
id: myText
text: Body
color: JamiTheme.textColor
Layout.preferredWidth: msgLayout.width - avatar.width - 30 - 10
elide: Text.ElideRight
Layout.rightMargin: 10
Layout.leftMargin: 10
font.pixelSize: IsEmojiOnly? JamiTheme.chatviewEmojiSize : JamiTheme.chatviewFontSize
Layout.alignment:Qt.AlignHCenter
}
}
}
}
Button {
id: buttonJumpTo
visible: msgHover.hovered || hovered
anchors.top: msgLayout.top
anchors.right: msgLayout.right
anchors.rightMargin: 20
anchors.topMargin: timestampItem.height - 20
width: buttonJumpText.width + 10
height: buttonJumpText.height + 10
background.visible: false
onClicked: {
CurrentConversation.scrollToMsg(Id)
}
Text {
id: buttonJumpText
text: JamiStrings.jumpTo
color: buttonJumpTo.hovered ? JamiTheme.blueLinkColor : JamiTheme.chatviewUsernameColor
font.underline: buttonJumpTo.hovered
anchors.centerIn: parent
font.pointSize: JamiTheme.jumpToFontSize
}
}
}
}

View file

@ -0,0 +1,182 @@
/*
* Copyright (C) 2023 Savoir-faire Linux Inc.
* Author: Nicolas Vengeon <nicolas.vengeon@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import net.jami.Adapters 1.1
import net.jami.Constants 1.1
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() {
searchBarOpened()
rectTextArea.isSearch = true
anim.start()
textArea.forceActiveFocus()
isOpened = true
}
function closeSearchbar() {
searchBarClosed()
rectTextArea.isSearch = false
anim.start()
isOpened = false
}
Connections {
target: chatViewHeader
function onShowDetailsClicked() {
if (rectTextArea.isSearch)
closeSearchbar()
}
}
onCurrentConversationIdChanged: {
if (isOpened)
closeSearchbar()
}
PushButton {
id: startSearchMessages
source: JamiResources.search_svg
normalColor: JamiTheme.chatviewBgColor
imageColor: JamiTheme.chatviewButtonColor
onClicked: {
if (rectTextArea.isSearch)
closeSearchbar()
else
openSearchBar()
}
}
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
onSearchBarWidthChanged: {
Layout.preferredWidth = root.searchBarWidth
}
TextField {
id: textArea
background.visible: false
anchors.right: clearTextButton.left
anchors.left: rectTextArea.left
color: JamiTheme.chatviewTextColor
placeholderText: JamiStrings.search
placeholderTextColor: JamiTheme.chatviewTextColor
onTextChanged: {
MessagesAdapter.searchbarPrompt = text
}
}
PushButton {
id: clearTextButton
anchors.verticalCenter: rectTextArea.verticalCenter
anchors.right: rectTextArea.right
anchors.margins: 5
preferredSize: 21
radius: rectTextArea.radius
visible: textArea.text.length
opacity: visible ? 1 : 0
normalColor: "transparent"
imageColor: JamiTheme.chatviewButtonColor
source: JamiResources.ic_clear_24dp_svg
toolTipText: JamiStrings.clearText
property string convId: CurrentConversation.id
onConvIdChanged: {
textArea.clear()
}
onClicked: {
textArea.clear()
textArea.forceActiveFocus()
}
Behavior on opacity {
NumberAnimation { duration: 500; easing.type: Easing.OutCubic }
}
}
}
}

View file

@ -67,6 +67,8 @@ MessagesAdapter::MessagesAdapter(AppSettingsManager* settingsManager,
filteredMsgListModel_->setSourceModel(conversation.interactions.get()); filteredMsgListModel_->setSourceModel(conversation.interactions.get());
set_currentConvComposingList(conversationTypersUrlToName(conversation.typers)); set_currentConvComposingList(conversationTypersUrlToName(conversation.typers));
mediaInteractions_.reset(new MessageListModel(this));
set_mediaMessageListModel(QVariant::fromValue(mediaInteractions_.get()));
}); });
connect(previewEngine_, &PreviewEngine::infoReady, this, &MessagesAdapter::onPreviewInfoReady); connect(previewEngine_, &PreviewEngine::infoReady, this, &MessagesAdapter::onPreviewInfoReady);
@ -82,6 +84,12 @@ MessagesAdapter::MessagesAdapter(AppSettingsManager* settingsManager,
connectConversationModel(); connectConversationModel();
} }
bool
MessagesAdapter::isDocument(const interaction::Type& type)
{
return interaction::Type::DATA_TRANSFER == type;
}
void void
MessagesAdapter::loadMoreMessages() MessagesAdapter::loadMoreMessages()
{ {
@ -575,25 +583,16 @@ MessagesAdapter::onComposingStatusChanged(const QString& convId,
void void
MessagesAdapter::onMessagesFoundProcessed(const QString& accountId, MessagesAdapter::onMessagesFoundProcessed(const QString& accountId,
const VectorMapStringString& messageIds, const QMap<QString, interaction::Info>& messageInformation)
const QVector<interaction::Info>& messageInformations)
{ {
if (lrcInstance_->get_currentAccountId() != accountId) { if (lrcInstance_->get_currentAccountId() != accountId) {
return; return;
} }
bool isSearchInProgress = messageIds.length();
bool isSearchInProgress = messageInformation.size();
if (isSearchInProgress) { if (isSearchInProgress) {
int index = -1; for (auto it = messageInformation.begin(); it != messageInformation.end(); it++) {
Q_FOREACH (const MapStringString& msg, messageIds) { mediaInteractions_->insert(qMakePair(it.key(), it.value()));
index++;
try {
std::pair<QString, interaction::Info> message(msg["id"],
messageInformations.at(index));
mediaInteractions_->insert(message);
} catch (...) {
qWarning() << "error in onMessagesFoundProcessed, message insertion on index: "
<< index;
}
} }
} else { } else {
set_mediaMessageListModel(QVariant::fromValue(mediaInteractions_.get())); set_mediaMessageListModel(QVariant::fromValue(mediaInteractions_.get()));
@ -729,17 +728,24 @@ MessagesAdapter::getFormattedDay(const quint64 timestamp)
} }
void void
MessagesAdapter::getConvMedias() MessagesAdapter::startSearch(QString& text, bool isMedia)
{ {
mediaInteractions_.reset(new MessageListModel(this));
set_mediaMessageListModel(QVariant::fromValue(mediaInteractions_.get()));
if (text.isEmpty() && !isMedia)
return;
auto accountId = lrcInstance_->get_currentAccountId(); auto accountId = lrcInstance_->get_currentAccountId();
auto convId = lrcInstance_->get_selectedConvUid(); auto convId = lrcInstance_->get_selectedConvUid();
mediaInteractions_.reset(new MessageListModel(this));
try { try {
lrcInstance_->getCurrentConversationModel()->getConvMediasInfos(accountId, convId); lrcInstance_->getCurrentConversationModel()->getConvMediasInfos(accountId,
convId,
text,
isMedia);
} catch (...) { } catch (...) {
qDebug() << "Exception during getConvMedia:"; qDebug() << "Exception during startSearch()";
} }
} }

View file

@ -60,6 +60,7 @@ class MessagesAdapter final : public QmlAdapterBase
QML_PROPERTY(QString, editId) QML_PROPERTY(QString, editId)
QML_RO_PROPERTY(QList<QString>, currentConvComposingList) QML_RO_PROPERTY(QList<QString>, currentConvComposingList)
QML_PROPERTY(QVariant, mediaMessageListModel) QML_PROPERTY(QVariant, mediaMessageListModel)
QML_PROPERTY(QString, searchbarPrompt)
public: public:
explicit MessagesAdapter(AppSettingsManager* settingsManager, explicit MessagesAdapter(AppSettingsManager* settingsManager,
@ -70,6 +71,7 @@ public:
Q_SIGNALS: Q_SIGNALS:
void newInteraction(const QString& id, int type); void newInteraction(const QString& id, int type);
void newMessageBarPlaceholderText(QString& placeholderText);
void newFilePasted(QString filePath); void newFilePasted(QString filePath);
void newTextPasted(); void newTextPasted();
void previewInformationToQML(QString messageId, QStringList previewInformation); void previewInformationToQML(QString messageId, QStringList previewInformation);
@ -77,6 +79,7 @@ Q_SIGNALS:
void timestampUpdated(); void timestampUpdated();
protected: protected:
Q_INVOKABLE bool isDocument(const interaction::Type& type);
Q_INVOKABLE void loadMoreMessages(); Q_INVOKABLE void loadMoreMessages();
Q_INVOKABLE void loadConversationUntil(const QString& to); Q_INVOKABLE void loadConversationUntil(const QString& to);
Q_INVOKABLE void connectConversationModel(); Q_INVOKABLE void connectConversationModel();
@ -129,7 +132,7 @@ protected:
Q_INVOKABLE QVariantMap getTransferStats(const QString& messageId, int); Q_INVOKABLE QVariantMap getTransferStats(const QString& messageId, int);
Q_INVOKABLE QVariant dataForInteraction(const QString& interactionId, Q_INVOKABLE QVariant dataForInteraction(const QString& interactionId,
int role = Qt::DisplayRole) const; int role = Qt::DisplayRole) const;
Q_INVOKABLE void getConvMedias(); Q_INVOKABLE void startSearch(QString& text, bool isMedia = false);
Q_INVOKABLE int getMessageIndexFromId(QString& id); Q_INVOKABLE int getMessageIndexFromId(QString& id);
// Run corrsponding js functions, c++ to qml. // Run corrsponding js functions, c++ to qml.
@ -150,8 +153,7 @@ private Q_SLOTS:
const QString& contactUri, const QString& contactUri,
bool isComposing); bool isComposing);
void onMessagesFoundProcessed(const QString& accountId, void onMessagesFoundProcessed(const QString& accountId,
const VectorMapStringString& messageIds, const QMap<QString, interaction::Info>& messageInformation);
const QVector<interaction::Info>& messageInformations);
private: private:
QList<QString> conversationTypersUrlToName(const QSet<QString>& typersSet); QList<QString> conversationTypersUrlToName(const QSet<QString>& typersSet);

View file

@ -316,9 +316,12 @@ public:
api::datatransfer::Info& info) const; api::datatransfer::Info& info) const;
/** /**
* Starts a search of all medias and files in a conversation * Starts a search of all medias in a conversation
*/ */
void getConvMediasInfos(const QString& accountId, const QString& conversationId); void getConvMediasInfos(const QString& accountId,
const QString& conversationId,
const QString& text,
bool isMedia);
/** /**
* @param convUid, uid of the conversation * @param convUid, uid of the conversation
* @return the number of unread messages for the conversation * @return the number of unread messages for the conversation
@ -612,12 +615,10 @@ Q_SIGNALS:
/** /**
* Emitted once a message search has been done and processed * Emitted once a message search has been done and processed
* @param accountId * @param accountId
* @param messageIds ids of all the messages found by the search * @param messageInformation message datas
* @param messageInformations message datas
*/ */
void messagesFoundProcessed(const QString& accountId, void messagesFoundProcessed(const QString& accountId,
const VectorMapStringString& messageIds, const QMap<QString, interaction::Info>& messageInformation) const;
const QVector<interaction::Info>& messageInformations) const;
/** /**
* Emitted once a conversation needs somebody to host the call * Emitted once a conversation needs somebody to host the call
* @param callId * @param callId

View file

@ -243,7 +243,8 @@ public:
std::map<QString, std::mutex> interactionsLocks; ///< {convId, mutex} std::map<QString, std::mutex> interactionsLocks; ///< {convId, mutex}
MapStringString transfIdToDbIntId; MapStringString transfIdToDbIntId;
uint32_t currentMsgRequestId; uint32_t mediaResearchRequestId;
uint32_t msgResearchRequestId;
public Q_SLOTS: public Q_SLOTS:
/** /**
@ -2540,29 +2541,34 @@ ConversationModelPimpl::slotMessagesFound(uint32_t requestId,
const QString& conversationId, const QString& conversationId,
const VectorMapStringString& messageIds) const VectorMapStringString& messageIds)
{ {
if (requestId != currentMsgRequestId) { QMap<QString, interaction::Info> messageDetailedInformation;
return; if (requestId == mediaResearchRequestId) {
} Q_FOREACH (const MapStringString& msg, messageIds) {
QVector<interaction::Info> messageDetailedinformations; auto intInfo = interaction::Info(msg, "");
Q_FOREACH (const MapStringString& msg, messageIds) { if (intInfo.type == interaction::Type::DATA_TRANSFER) {
auto intInfo = interaction::Info(msg, ""); auto fileId = msg["fileId"];
if (intInfo.type == interaction::Type::DATA_TRANSFER) {
auto fileId = msg["fileId"];
QString path; QString path;
qlonglong bytesProgress, totalSize; qlonglong bytesProgress, totalSize;
linked.owner.dataTransferModel->fileTransferInfo(accountId, linked.owner.dataTransferModel->fileTransferInfo(accountId,
conversationId, conversationId,
fileId, fileId,
path, path,
totalSize, totalSize,
bytesProgress); bytesProgress);
intInfo.body = path; intInfo.body = path;
}
messageDetailedInformation[msg["id"]] = intInfo;
}
} else if (requestId == msgResearchRequestId) {
Q_FOREACH (const MapStringString& msg, messageIds) {
auto intInfo = interaction::Info(msg, "");
if (intInfo.type == interaction::Type::TEXT) {
messageDetailedInformation[msg["id"]] = intInfo;
}
} }
messageDetailedinformations.append(intInfo);
} }
Q_EMIT linked.messagesFoundProcessed(accountId, messageDetailedInformation);
Q_EMIT linked.messagesFoundProcessed(accountId, messageIds, messageDetailedinformations);
} }
void void
@ -4000,10 +4006,17 @@ ConversationModel::sendFile(const QString& convUid, const QString& path, const Q
} }
void void
ConversationModel::getConvMediasInfos(const QString& accountId, const QString& conversationId) ConversationModel::getConvMediasInfos(const QString& accountId,
const QString& conversationId,
const QString& text,
bool isMedia)
{ {
pimpl_->currentMsgRequestId = ConfigurationManager::instance().searchConversation( if (isMedia)
accountId, conversationId, "", "", "", "application/data-transfer+json", 0, 0, 0, 0); pimpl_->mediaResearchRequestId = ConfigurationManager::instance().searchConversation(
accountId, conversationId, "", "", text, "application/data-transfer+json", 0, 0, 0, 0);
else
pimpl_->msgResearchRequestId = ConfigurationManager::instance().searchConversation(
accountId, conversationId, "", "", text, "text/plain", 0, 0, 0, 0);
} }
void void