mirror of
https://git.jami.net/savoirfairelinux/jami-client-qt.git
synced 2025-04-21 21:52:03 +02:00
chatview: replace web chat view with qml listview
Introduces a primitive QML ListView based chat view lacking features present in the previous web chat view, that will be added in subsequent commits(styling, preview/media/link/file-transfer message type support, etc.). Gitlab: #467 Change-Id: Iedc40f6172a6cdacc48cda6f4187053fbf226713
This commit is contained in:
parent
e0b28eed7b
commit
2e67dc1bd8
32 changed files with 930 additions and 935 deletions
|
@ -45,7 +45,6 @@ set(COMMON_SOURCES
|
||||||
${SRC_DIR}/networkmanager.cpp
|
${SRC_DIR}/networkmanager.cpp
|
||||||
${SRC_DIR}/runguard.cpp
|
${SRC_DIR}/runguard.cpp
|
||||||
${SRC_DIR}/updatemanager.cpp
|
${SRC_DIR}/updatemanager.cpp
|
||||||
${SRC_DIR}/webchathelpers.cpp
|
|
||||||
${SRC_DIR}/main.cpp
|
${SRC_DIR}/main.cpp
|
||||||
${SRC_DIR}/smartlistmodel.cpp
|
${SRC_DIR}/smartlistmodel.cpp
|
||||||
${SRC_DIR}/utils.cpp
|
${SRC_DIR}/utils.cpp
|
||||||
|
@ -87,7 +86,8 @@ set(COMMON_SOURCES
|
||||||
${SRC_DIR}/avatarregistry.cpp
|
${SRC_DIR}/avatarregistry.cpp
|
||||||
${SRC_DIR}/currentconversation.cpp
|
${SRC_DIR}/currentconversation.cpp
|
||||||
${SRC_DIR}/currentaccount.cpp
|
${SRC_DIR}/currentaccount.cpp
|
||||||
${SRC_DIR}/videodevices.cpp)
|
${SRC_DIR}/videodevices.cpp
|
||||||
|
${SRC_DIR}/previewengine.cpp)
|
||||||
|
|
||||||
set(COMMON_HEADERS
|
set(COMMON_HEADERS
|
||||||
${SRC_DIR}/avatarimageprovider.h
|
${SRC_DIR}/avatarimageprovider.h
|
||||||
|
@ -99,7 +99,6 @@ set(COMMON_HEADERS
|
||||||
${SRC_DIR}/version.h
|
${SRC_DIR}/version.h
|
||||||
${SRC_DIR}/accountlistmodel.h
|
${SRC_DIR}/accountlistmodel.h
|
||||||
${SRC_DIR}/runguard.h
|
${SRC_DIR}/runguard.h
|
||||||
${SRC_DIR}/webchathelpers.h
|
|
||||||
${SRC_DIR}/rendermanager.h
|
${SRC_DIR}/rendermanager.h
|
||||||
${SRC_DIR}/connectivitymonitor.h
|
${SRC_DIR}/connectivitymonitor.h
|
||||||
${SRC_DIR}/jamiavatartheme.h
|
${SRC_DIR}/jamiavatartheme.h
|
||||||
|
@ -144,7 +143,8 @@ set(COMMON_HEADERS
|
||||||
${SRC_DIR}/avatarregistry.h
|
${SRC_DIR}/avatarregistry.h
|
||||||
${SRC_DIR}/currentconversation.h
|
${SRC_DIR}/currentconversation.h
|
||||||
${SRC_DIR}/currentaccount.h
|
${SRC_DIR}/currentaccount.h
|
||||||
${SRC_DIR}/videodevices.h)
|
${SRC_DIR}/videodevices.h
|
||||||
|
${SRC_DIR}/previewengine.h)
|
||||||
|
|
||||||
set(QML_LIBS
|
set(QML_LIBS
|
||||||
Qt5::Quick
|
Qt5::Quick
|
||||||
|
@ -155,7 +155,8 @@ set(QML_LIBS
|
||||||
Qt5::Concurrent
|
Qt5::Concurrent
|
||||||
Qt5::QuickControls2
|
Qt5::QuickControls2
|
||||||
Qt5::WebEngine
|
Qt5::WebEngine
|
||||||
Qt5::Core)
|
Qt5::Core
|
||||||
|
Qt5::WebEngineWidgets)
|
||||||
|
|
||||||
set(QML_LIBS_LIST
|
set(QML_LIBS_LIST
|
||||||
Core
|
Core
|
||||||
|
@ -166,7 +167,8 @@ set(QML_LIBS_LIST
|
||||||
Svg
|
Svg
|
||||||
Sql
|
Sql
|
||||||
QuickControls2
|
QuickControls2
|
||||||
WebEngine)
|
WebEngine
|
||||||
|
WebEngineWidgets)
|
||||||
|
|
||||||
set(WINDOWS_SYS_LIBS Shell32.lib
|
set(WINDOWS_SYS_LIBS Shell32.lib
|
||||||
Ole32.lib
|
Ole32.lib
|
||||||
|
|
6
qml.qrc
6
qml.qrc
|
@ -94,7 +94,7 @@
|
||||||
<file>src/mainview/components/AboutPopUp.qml</file>
|
<file>src/mainview/components/AboutPopUp.qml</file>
|
||||||
<file>src/mainview/components/SidePanel.qml</file>
|
<file>src/mainview/components/SidePanel.qml</file>
|
||||||
<file>src/mainview/components/WelcomePage.qml</file>
|
<file>src/mainview/components/WelcomePage.qml</file>
|
||||||
<file>src/mainview/components/MessageWebView.qml</file>
|
<file>src/mainview/components/ChatView.qml</file>
|
||||||
<file>src/mainview/components/MessageWebViewHeader.qml</file>
|
<file>src/mainview/components/MessageWebViewHeader.qml</file>
|
||||||
<file>src/mainview/components/AccountComboBox.qml</file>
|
<file>src/mainview/components/AccountComboBox.qml</file>
|
||||||
<file>src/mainview/components/CallStackView.qml</file>
|
<file>src/mainview/components/CallStackView.qml</file>
|
||||||
|
@ -143,7 +143,7 @@
|
||||||
<file>src/commoncomponents/contextmenu/GeneralMenuSeparator.qml</file>
|
<file>src/commoncomponents/contextmenu/GeneralMenuSeparator.qml</file>
|
||||||
<file>src/mainview/components/ParticipantOverlayButton.qml</file>
|
<file>src/mainview/components/ParticipantOverlayButton.qml</file>
|
||||||
<file>src/mainview/components/ParticipantControlLayout.qml</file>
|
<file>src/mainview/components/ParticipantControlLayout.qml</file>
|
||||||
<file>src/mainview/components/MessageWebViewFooter.qml</file>
|
<file>src/mainview/components/ChatViewFooter.qml</file>
|
||||||
<file>src/commoncomponents/emojipicker/EmojiPicker.qml</file>
|
<file>src/commoncomponents/emojipicker/EmojiPicker.qml</file>
|
||||||
<file>src/commoncomponents/emojipicker/emojiPickerLoader.js</file>
|
<file>src/commoncomponents/emojipicker/emojiPickerLoader.js</file>
|
||||||
<file>src/commoncomponents/emojipicker/emojiPickerLoader.html</file>
|
<file>src/commoncomponents/emojipicker/emojiPickerLoader.html</file>
|
||||||
|
@ -161,5 +161,7 @@
|
||||||
<file>src/commoncomponents/BackButton.qml</file>
|
<file>src/commoncomponents/BackButton.qml</file>
|
||||||
<file>src/commoncomponents/JamiSwitch.qml</file>
|
<file>src/commoncomponents/JamiSwitch.qml</file>
|
||||||
<file>src/mainview/components/ReadOnlyFooter.qml</file>
|
<file>src/mainview/components/ReadOnlyFooter.qml</file>
|
||||||
|
<file>src/commoncomponents/MessageDelegate.qml</file>
|
||||||
|
<file>src/mainview/components/MessageListView.qml</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|
54
resources/misc/previewInterop.js
Normal file
54
resources/misc/previewInterop.js
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
_ = new QWebChannel(qt.webChannelTransport, function (channel) {
|
||||||
|
window.jsbridge = channel.objects.jsbridge
|
||||||
|
})
|
||||||
|
|
||||||
|
function log(msg) {
|
||||||
|
window.jsbridge.log(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPreviewInfo(messageId, url) {
|
||||||
|
var title = null
|
||||||
|
var description = null
|
||||||
|
var image = null
|
||||||
|
if (!url.includes("http://") && !url.includes("https://")) {
|
||||||
|
url = "http://".concat(url)
|
||||||
|
}
|
||||||
|
fetch(url, {
|
||||||
|
mode: 'no-cors',
|
||||||
|
headers: {'Set-Cookie': 'SameSite=None; Secure'}
|
||||||
|
}).then(function (response) {
|
||||||
|
return response.text()
|
||||||
|
}).then(function (html) {
|
||||||
|
// create DOM from html string
|
||||||
|
var parser = new DOMParser()
|
||||||
|
var doc = parser.parseFromString(html, "text/html")
|
||||||
|
if (!url.includes("twitter.com")){
|
||||||
|
title = getTitle(doc)
|
||||||
|
image = getImage(doc, url)
|
||||||
|
description = getDescription(doc)
|
||||||
|
var domain = (new URL(url))
|
||||||
|
domain = (domain.hostname).replace("www.", "")
|
||||||
|
} else {
|
||||||
|
title = "Twitter. It's what's happening."
|
||||||
|
}
|
||||||
|
|
||||||
|
window.jsbridge.infoReady(messageId, {
|
||||||
|
'title': title,
|
||||||
|
'image': image,
|
||||||
|
'description': description,
|
||||||
|
'url': url,
|
||||||
|
'domain': domain,
|
||||||
|
})
|
||||||
|
}).catch(function (err) {
|
||||||
|
log("Error occured while fetching document: " + err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseMessage(messageId, message) {
|
||||||
|
var links = linkify.find(message)
|
||||||
|
if (links.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
getPreviewInfo(messageId, links[0].href)
|
||||||
|
window.jsbridge.linkifyReady(messageId, linkifyStr(message))
|
||||||
|
}
|
191
src/commoncomponents/MessageDelegate.qml
Normal file
191
src/commoncomponents/MessageDelegate.qml
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
import QtWebEngine 1.10
|
||||||
|
|
||||||
|
import net.jami.Models 1.1
|
||||||
|
import net.jami.Adapters 1.1
|
||||||
|
import net.jami.Constants 1.1
|
||||||
|
|
||||||
|
Control {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
readonly property bool isGenerated: Type === Interaction.Type.CALL ||
|
||||||
|
Type === Interaction.Type.CONTACT
|
||||||
|
readonly property string author: Author
|
||||||
|
readonly property var timestamp: Timestamp
|
||||||
|
readonly property bool isOutgoing: model.Author === ""
|
||||||
|
readonly property var formattedTime: MessagesAdapter.getFormattedTime(Timestamp)
|
||||||
|
readonly property bool isImage: MessagesAdapter.isImage(Body)
|
||||||
|
readonly property bool isAnimatedImage: MessagesAdapter.isAnimatedImage(Body)
|
||||||
|
readonly property var linkPreviewInfo: LinkPreviewInfo
|
||||||
|
|
||||||
|
readonly property var body: Body
|
||||||
|
readonly property real msgMargin: 64
|
||||||
|
|
||||||
|
width: parent ? parent.width : 0
|
||||||
|
height: loader.height
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: loader
|
||||||
|
|
||||||
|
property alias isOutgoing: root.isOutgoing
|
||||||
|
property alias isGenerated: root.isGenerated
|
||||||
|
readonly property var author: Author
|
||||||
|
readonly property var body: Body
|
||||||
|
|
||||||
|
sourceComponent: isGenerated ?
|
||||||
|
generatedMsgComp :
|
||||||
|
userMsgComp
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: generatedMsgComp
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: root.width
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
TextArea {
|
||||||
|
width: parent.width
|
||||||
|
text: body
|
||||||
|
horizontalAlignment: Qt.AlignHCenter
|
||||||
|
readOnly: true
|
||||||
|
font.pointSize: 11
|
||||||
|
color: JamiTheme.chatviewTextColor
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: infoCell
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
height: childrenRect.height
|
||||||
|
|
||||||
|
Component.onCompleted: children = timestampLabel
|
||||||
|
}
|
||||||
|
|
||||||
|
bottomPadding: 12
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: userMsgComp
|
||||||
|
|
||||||
|
GridLayout {
|
||||||
|
id: gridLayout
|
||||||
|
|
||||||
|
width: root.width
|
||||||
|
|
||||||
|
columns: 2
|
||||||
|
rows: 2
|
||||||
|
|
||||||
|
columnSpacing: 2
|
||||||
|
rowSpacing: 2
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: msgCell
|
||||||
|
|
||||||
|
Layout.column: isOutgoing ? 0 : 1
|
||||||
|
Layout.row: 0
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.maximumWidth: 640
|
||||||
|
Layout.preferredHeight: childrenRect.height
|
||||||
|
Layout.alignment: isOutgoing ? Qt.AlignRight : Qt.AlignLeft
|
||||||
|
Layout.leftMargin: isOutgoing ? msgMargin : 0
|
||||||
|
Layout.rightMargin: isOutgoing ? 0 : msgMargin
|
||||||
|
|
||||||
|
Control {
|
||||||
|
id: msgBlock
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
|
||||||
|
contentItem: Column {
|
||||||
|
id: msgContent
|
||||||
|
|
||||||
|
property real txtWidth: ta.contentWidth + 3 * ta.padding
|
||||||
|
|
||||||
|
TextArea {
|
||||||
|
id: ta
|
||||||
|
width: parent.width
|
||||||
|
text: body
|
||||||
|
padding: 10
|
||||||
|
font.pointSize: 11
|
||||||
|
font.hintingPreference: Font.PreferNoHinting
|
||||||
|
renderType: Text.NativeRendering
|
||||||
|
textFormat: TextEdit.RichText
|
||||||
|
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||||
|
transform: Translate { x: bg.x }
|
||||||
|
rightPadding: isOutgoing ? padding * 1.5 : 0
|
||||||
|
color: isOutgoing ?
|
||||||
|
JamiTheme.messageOutTxtColor :
|
||||||
|
JamiTheme.messageInTxtColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
background: Rectangle {
|
||||||
|
id: bg
|
||||||
|
|
||||||
|
anchors.right: isOutgoing ? msgContent.right : undefined
|
||||||
|
width: msgContent.txtWidth
|
||||||
|
radius: 18
|
||||||
|
color: isOutgoing ?
|
||||||
|
JamiTheme.messageOutBgColor :
|
||||||
|
JamiTheme.messageInBgColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Item {
|
||||||
|
id: infoCell
|
||||||
|
|
||||||
|
Layout.column: isOutgoing ? 0 : 1
|
||||||
|
Layout.row: 1
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: childrenRect.height
|
||||||
|
|
||||||
|
Component.onCompleted: children = timestampLabel
|
||||||
|
}
|
||||||
|
Item {
|
||||||
|
id: avatarCell
|
||||||
|
|
||||||
|
Layout.column: isOutgoing ? 1 : 0
|
||||||
|
Layout.row: 0
|
||||||
|
Layout.preferredWidth: isOutgoing ? 16 : avatar.width
|
||||||
|
Layout.preferredHeight: msgCell.height
|
||||||
|
Layout.leftMargin: isOutgoing ? 0 : 6
|
||||||
|
Layout.rightMargin: Layout.leftMargin
|
||||||
|
Avatar {
|
||||||
|
id: avatar
|
||||||
|
visible: !isOutgoing
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
width: 32
|
||||||
|
height: 32
|
||||||
|
imageId: author
|
||||||
|
showPresenceIndicator: false
|
||||||
|
mode: Avatar.Mode.Contact
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
id: timestampLabel
|
||||||
|
|
||||||
|
text: formattedTime
|
||||||
|
color: JamiTheme.timestampColor
|
||||||
|
|
||||||
|
anchors.right: isGenerated || !isOutgoing ? undefined : parent.right
|
||||||
|
anchors.rightMargin: 6
|
||||||
|
anchors.left: isGenerated || isOutgoing ? undefined : parent.left
|
||||||
|
anchors.leftMargin: 6
|
||||||
|
anchors.horizontalCenter: isGenerated ? parent.horizontalCenter : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
opacity: 0
|
||||||
|
Behavior on opacity { NumberAnimation { duration: 40 } }
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
opacity = 1
|
||||||
|
if (!Linkified && !isImage && !isAnimatedImage) {
|
||||||
|
MessagesAdapter.parseMessageUrls(Id, Body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -147,12 +147,12 @@ Item {
|
||||||
// Chatview
|
// Chatview
|
||||||
property color jamiLightBlue: darkTheme ? "#003b4e" : Qt.rgba(59, 193, 211, 0.3)
|
property color jamiLightBlue: darkTheme ? "#003b4e" : Qt.rgba(59, 193, 211, 0.3)
|
||||||
property color jamiDarkBlue: darkTheme ? "#28b1ed" : "#003b4e"
|
property color jamiDarkBlue: darkTheme ? "#28b1ed" : "#003b4e"
|
||||||
property color chatviewTextColor: textColor
|
property color chatviewTextColor: darkTheme ? "#f0f0f0" : "#353637"
|
||||||
property color timestampColor: darkTheme ? "#bbb" : "#333"
|
property color timestampColor: darkTheme ? "#bbb" : "#333"
|
||||||
property color messageOutBgColor: darkTheme ? "#28b1ed" : "#cfd8dc"
|
property color messageOutBgColor: darkTheme ? "#28b1ed" : "#cfd8dc"
|
||||||
property color messageOutTxtColor: textColor
|
property color messageOutTxtColor: chatviewTextColor
|
||||||
property color messageInBgColor: darkTheme? "#616161" : "#cfebf5"
|
property color messageInBgColor: darkTheme? "#616161" : "#cfebf5"
|
||||||
property color messageInTxtColor: textColor
|
property color messageInTxtColor: chatviewTextColor
|
||||||
property color fileOutTimestampColor: darkTheme ? "#eee" : "#555"
|
property color fileOutTimestampColor: darkTheme ? "#eee" : "#555"
|
||||||
property color fileInTimestampColor: darkTheme ? "#999" : "#555"
|
property color fileInTimestampColor: darkTheme ? "#999" : "#555"
|
||||||
property color chatviewBgColor: darkTheme ? bgDarkMode_ : whiteColor
|
property color chatviewBgColor: darkTheme ? bgDarkMode_ : whiteColor
|
||||||
|
@ -271,17 +271,17 @@ Item {
|
||||||
property real modalPopupDropShadowSamples: 16
|
property real modalPopupDropShadowSamples: 16
|
||||||
|
|
||||||
// MessageWebView
|
// MessageWebView
|
||||||
property real messageWebViewHairLineSize: 1
|
property real chatViewHairLineSize: 1
|
||||||
property real messageWebViewHeaderPreferredHeight: 64
|
property real messageWebViewHeaderPreferredHeight: 64
|
||||||
property real messageWebViewFooterContentMaximumWidth: 1000
|
property real chatViewMaximumWidth: 900
|
||||||
property real messageWebViewFooterPreferredHeight: 50
|
property real chatViewFooterPreferredHeight: 50
|
||||||
property real messageWebViewFooterMaximumHeight: 280
|
property real chatViewFooterMaximumHeight: 280
|
||||||
property real messageWebViewFooterRowSpacing: 1
|
property real chatViewFooterRowSpacing: 1
|
||||||
property real messageWebViewFooterButtonSize: 36
|
property real chatViewFooterButtonSize: 36
|
||||||
property real messageWebViewFooterButtonIconSize: 48
|
property real chatViewFooterButtonIconSize: 48
|
||||||
property real messageWebViewFooterButtonRadius: 5
|
property real chatViewFooterButtonRadius: 5
|
||||||
property real messageWebViewFooterFileContainerPreferredHeight: 150
|
property real chatViewFooterFileContainerPreferredHeight: 150
|
||||||
property real messageWebViewFooterTextAreaMaximumHeight: 130
|
property real chatViewFooterTextAreaMaximumHeight: 130
|
||||||
|
|
||||||
// MessageWebView File Transfer Container
|
// MessageWebView File Transfer Container
|
||||||
property real filesToSendContainerSpacing: 5
|
property real filesToSendContainerSpacing: 5
|
||||||
|
|
|
@ -108,22 +108,22 @@ ConversationListModelBase::dataForItem(item_t item, int role) const
|
||||||
case Role::UnreadMessagesCount:
|
case Role::UnreadMessagesCount:
|
||||||
return QVariant(item.unreadMessages);
|
return QVariant(item.unreadMessages);
|
||||||
case Role::LastInteractionTimeStamp: {
|
case Role::LastInteractionTimeStamp: {
|
||||||
if (!item.interactions.empty()) {
|
if (!item.interactions->empty()) {
|
||||||
auto ts = static_cast<qint32>(item.interactions.at(item.lastMessageUid).timestamp);
|
auto ts = static_cast<qint32>(item.interactions->at(item.lastMessageUid).timestamp);
|
||||||
return QVariant(ts);
|
return QVariant(ts);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Role::LastInteractionDate: {
|
case Role::LastInteractionDate: {
|
||||||
if (!item.interactions.empty()) {
|
if (!item.interactions->empty()) {
|
||||||
return QVariant(
|
return QVariant(
|
||||||
Utils::formatTimeString(item.interactions.at(item.lastMessageUid).timestamp));
|
Utils::formatTimeString(item.interactions->at(item.lastMessageUid).timestamp));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Role::LastInteraction: {
|
case Role::LastInteraction: {
|
||||||
if (!item.interactions.empty()) {
|
if (!item.interactions->empty()) {
|
||||||
return QVariant(item.interactions.at(item.lastMessageUid).body);
|
return QVariant(item.interactions->at(item.lastMessageUid).body);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,6 +60,7 @@ CurrentConversation::updateData()
|
||||||
set_needsSyncing(convInfo.needsSyncing);
|
set_needsSyncing(convInfo.needsSyncing);
|
||||||
set_isSip(accInfo.profileInfo.type == profile::Type::SIP);
|
set_isSip(accInfo.profileInfo.type == profile::Type::SIP);
|
||||||
set_callId(convInfo.getCallId());
|
set_callId(convInfo.getCallId());
|
||||||
|
set_allMessagesLoaded(convInfo.allMessagesLoaded);
|
||||||
if (accInfo.callModel->hasCall(callId_)) {
|
if (accInfo.callModel->hasCall(callId_)) {
|
||||||
auto call = accInfo.callModel->getCall(callId_);
|
auto call = accInfo.callModel->getCall(callId_);
|
||||||
set_callState(call.status);
|
set_callState(call.status);
|
||||||
|
|
|
@ -44,6 +44,7 @@ class CurrentConversation final : public QObject
|
||||||
QML_PROPERTY(bool, inCall)
|
QML_PROPERTY(bool, inCall)
|
||||||
QML_PROPERTY(bool, isTemporary)
|
QML_PROPERTY(bool, isTemporary)
|
||||||
QML_PROPERTY(bool, isContact)
|
QML_PROPERTY(bool, isContact)
|
||||||
|
QML_PROPERTY(bool, allMessagesLoaded)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit CurrentConversation(LRCInstance* lrcInstance, QObject* parent = nullptr);
|
explicit CurrentConversation(LRCInstance* lrcInstance, QObject* parent = nullptr);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/*!
|
/*
|
||||||
* Copyright (C) 2015-2020 by Savoir-faire Linux
|
* Copyright (C) 2015-2020 by Savoir-faire Linux
|
||||||
* Author: Edric Ladent Milaret <edric.ladent-milaret@savoirfairelinux.com>
|
* Author: Edric Ladent Milaret <edric.ladent-milaret@savoirfairelinux.com>
|
||||||
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
|
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
|
||||||
|
@ -25,6 +25,7 @@
|
||||||
#include "appsettingsmanager.h"
|
#include "appsettingsmanager.h"
|
||||||
#include "connectivitymonitor.h"
|
#include "connectivitymonitor.h"
|
||||||
#include "systemtray.h"
|
#include "systemtray.h"
|
||||||
|
#include "previewengine.h"
|
||||||
|
|
||||||
#include <QAction>
|
#include <QAction>
|
||||||
#include <QCommandLineParser>
|
#include <QCommandLineParser>
|
||||||
|
@ -149,18 +150,12 @@ MainApplication::MainApplication(int& argc, char** argv)
|
||||||
, connectivityMonitor_(new ConnectivityMonitor(this))
|
, connectivityMonitor_(new ConnectivityMonitor(this))
|
||||||
, settingsManager_(new AppSettingsManager(this))
|
, settingsManager_(new AppSettingsManager(this))
|
||||||
, systemTray_(new SystemTray(settingsManager_.get(), this))
|
, systemTray_(new SystemTray(settingsManager_.get(), this))
|
||||||
|
, previewEngine_(new PreviewEngine(this))
|
||||||
{
|
{
|
||||||
QObject::connect(this, &QApplication::aboutToQuit, [this] { cleanup(); });
|
QObject::connect(this, &QApplication::aboutToQuit, [this] { cleanup(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
MainApplication::~MainApplication()
|
MainApplication::~MainApplication() {}
|
||||||
{
|
|
||||||
engine_.reset();
|
|
||||||
systemTray_.reset();
|
|
||||||
settingsManager_.reset();
|
|
||||||
lrcInstance_.reset();
|
|
||||||
connectivityMonitor_.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
bool
|
||||||
MainApplication::init()
|
MainApplication::init()
|
||||||
|
@ -414,6 +409,7 @@ MainApplication::initQmlLayer()
|
||||||
systemTray_.get(),
|
systemTray_.get(),
|
||||||
lrcInstance_.get(),
|
lrcInstance_.get(),
|
||||||
settingsManager_.get(),
|
settingsManager_.get(),
|
||||||
|
previewEngine_.get(),
|
||||||
&screenInfo_,
|
&screenInfo_,
|
||||||
this);
|
this);
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/*!
|
/*
|
||||||
* Copyright (C) 2020 by Savoir-faire Linux
|
* Copyright (C) 2020 by Savoir-faire Linux
|
||||||
* Author: Edric Ladent Milaret <edric.ladent-milaret@savoirfairelinux.com>
|
* Author: Edric Ladent Milaret <edric.ladent-milaret@savoirfairelinux.com>
|
||||||
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
|
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
|
||||||
|
@ -35,6 +35,7 @@ class ConnectivityMonitor;
|
||||||
class AppSettingsManager;
|
class AppSettingsManager;
|
||||||
class SystemTray;
|
class SystemTray;
|
||||||
class CallAdapter;
|
class CallAdapter;
|
||||||
|
class PreviewEngine;
|
||||||
|
|
||||||
// Provides information about the screen the app is displayed on
|
// Provides information about the screen the app is displayed on
|
||||||
class ScreenInfo : public QObject
|
class ScreenInfo : public QObject
|
||||||
|
@ -97,8 +98,7 @@ private:
|
||||||
QScopedPointer<ConnectivityMonitor> connectivityMonitor_;
|
QScopedPointer<ConnectivityMonitor> connectivityMonitor_;
|
||||||
QScopedPointer<AppSettingsManager> settingsManager_;
|
QScopedPointer<AppSettingsManager> settingsManager_;
|
||||||
QScopedPointer<SystemTray> systemTray_;
|
QScopedPointer<SystemTray> systemTray_;
|
||||||
|
QScopedPointer<PreviewEngine> previewEngine_;
|
||||||
|
|
||||||
ScreenInfo screenInfo_;
|
ScreenInfo screenInfo_;
|
||||||
|
|
||||||
CallAdapter* callAdapter_;
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -76,8 +76,8 @@ Rectangle {
|
||||||
callStackView.needToCloseInCallConversationAndPotentialWindow()
|
callStackView.needToCloseInCallConversationAndPotentialWindow()
|
||||||
LRCInstance.deselectConversation()
|
LRCInstance.deselectConversation()
|
||||||
if (isPageInStack("callStackViewObject", sidePanelViewStack) ||
|
if (isPageInStack("callStackViewObject", sidePanelViewStack) ||
|
||||||
isPageInStack("communicationPageMessageWebView", sidePanelViewStack) ||
|
isPageInStack("chatView", sidePanelViewStack) ||
|
||||||
isPageInStack("communicationPageMessageWebView", mainViewStack) ||
|
isPageInStack("chatView", mainViewStack) ||
|
||||||
isPageInStack("callStackViewObject", mainViewStack)) {
|
isPageInStack("callStackViewObject", mainViewStack)) {
|
||||||
sidePanelViewStack.pop(StackView.Immediate)
|
sidePanelViewStack.pop(StackView.Immediate)
|
||||||
mainViewStack.pop(welcomePage, StackView.Immediate)
|
mainViewStack.pop(welcomePage, StackView.Immediate)
|
||||||
|
@ -98,10 +98,10 @@ Rectangle {
|
||||||
function pushCommunicationMessageWebView() {
|
function pushCommunicationMessageWebView() {
|
||||||
if (sidePanelOnly) {
|
if (sidePanelOnly) {
|
||||||
sidePanelViewStack.pop(StackView.Immediate)
|
sidePanelViewStack.pop(StackView.Immediate)
|
||||||
sidePanelViewStack.push(communicationPageMessageWebView, StackView.Immediate)
|
sidePanelViewStack.push(chatView, StackView.Immediate)
|
||||||
} else {
|
} else {
|
||||||
mainViewStack.pop(welcomePage, StackView.Immediate)
|
mainViewStack.pop(welcomePage, StackView.Immediate)
|
||||||
mainViewStack.push(communicationPageMessageWebView, StackView.Immediate)
|
mainViewStack.push(chatView, StackView.Immediate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,24 +164,17 @@ Rectangle {
|
||||||
}
|
}
|
||||||
|
|
||||||
function setMainView(convId) {
|
function setMainView(convId) {
|
||||||
if (!(communicationPageMessageWebView.jsLoaded)) {
|
|
||||||
communicationPageMessageWebView.jsLoadedChanged.connect(
|
|
||||||
function(convId) {
|
|
||||||
return function() { setMainView(convId) }
|
|
||||||
}(convId))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var item = ConversationsAdapter.getConvInfoMap(convId)
|
var item = ConversationsAdapter.getConvInfoMap(convId)
|
||||||
if (item.convId === undefined)
|
if (item.convId === undefined)
|
||||||
return
|
return
|
||||||
communicationPageMessageWebView.headerUserAliasLabelText = item.title
|
chatView.headerUserAliasLabelText = item.title
|
||||||
communicationPageMessageWebView.headerUserUserNameLabelText = item.bestId
|
chatView.headerUserUserNameLabelText = item.bestId
|
||||||
if (item.callStackViewShouldShow) {
|
if (item.callStackViewShouldShow) {
|
||||||
if (inSettingsView) {
|
if (inSettingsView) {
|
||||||
toggleSettingsView()
|
toggleSettingsView()
|
||||||
}
|
}
|
||||||
MessagesAdapter.setupChatView(item)
|
MessagesAdapter.setupChatView(item)
|
||||||
callStackView.setLinkedWebview(communicationPageMessageWebView)
|
callStackView.setLinkedWebview(chatView)
|
||||||
callStackView.responsibleAccountId = LRCInstance.currentAccountId
|
callStackView.responsibleAccountId = LRCInstance.currentAccountId
|
||||||
callStackView.responsibleConvUid = convId
|
callStackView.responsibleConvUid = convId
|
||||||
callStackView.isAudioOnly = item.isAudioOnly
|
callStackView.isAudioOnly = item.isAudioOnly
|
||||||
|
@ -201,13 +194,13 @@ Rectangle {
|
||||||
callStackView.needToCloseInCallConversationAndPotentialWindow()
|
callStackView.needToCloseInCallConversationAndPotentialWindow()
|
||||||
MessagesAdapter.setupChatView(item)
|
MessagesAdapter.setupChatView(item)
|
||||||
pushCommunicationMessageWebView()
|
pushCommunicationMessageWebView()
|
||||||
communicationPageMessageWebView.focusMessageWebView()
|
chatView.focusChatView()
|
||||||
currentConvUID = convId
|
currentConvUID = convId
|
||||||
} else if (isPageInStack("callStackViewObject", sidePanelViewStack)
|
} else if (isPageInStack("callStackViewObject", sidePanelViewStack)
|
||||||
|| isPageInStack("callStackViewObject", mainViewStack)) {
|
|| isPageInStack("callStackViewObject", mainViewStack)) {
|
||||||
callStackView.needToCloseInCallConversationAndPotentialWindow()
|
callStackView.needToCloseInCallConversationAndPotentialWindow()
|
||||||
pushCommunicationMessageWebView()
|
pushCommunicationMessageWebView()
|
||||||
communicationPageMessageWebView.focusMessageWebView()
|
chatView.focusChatView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -396,21 +389,12 @@ Rectangle {
|
||||||
onSettingsBackArrowClicked: sidePanelViewStack.pop(StackView.Immediate)
|
onSettingsBackArrowClicked: sidePanelViewStack.pop(StackView.Immediate)
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageWebView {
|
ChatView {
|
||||||
id: communicationPageMessageWebView
|
id: chatView
|
||||||
|
|
||||||
objectName: "communicationPageMessageWebView"
|
|
||||||
|
|
||||||
signal toSendMessageContentSaved(string arg)
|
|
||||||
signal toMessagesCleared
|
|
||||||
signal toMessagesLoaded
|
|
||||||
|
|
||||||
|
objectName: "chatView"
|
||||||
visible: false
|
visible: false
|
||||||
|
Component.onCompleted: MessagesAdapter.setQmlObject(this)
|
||||||
Component.onCompleted: {
|
|
||||||
// Set qml MessageWebView object pointer to c++.
|
|
||||||
MessagesAdapter.setQmlObject(this)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onWidthChanged: {
|
onWidthChanged: {
|
||||||
|
|
147
src/mainview/components/ChatView.qml
Normal file
147
src/mainview/components/ChatView.qml
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2020-2021 by Savoir-faire Linux
|
||||||
|
* Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
|
||||||
|
* Author: Trevor Tabah <trevor.tabah@savoirfairelinux.com>
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
|
import net.jami.Models 1.1
|
||||||
|
import net.jami.Adapters 1.1
|
||||||
|
import net.jami.Constants 1.1
|
||||||
|
|
||||||
|
import "../../commoncomponents"
|
||||||
|
import "../js/pluginhandlerpickercreation.js" as PluginHandlerPickerCreation
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property string headerUserAliasLabelText: ""
|
||||||
|
property string headerUserUserNameLabelText: ""
|
||||||
|
|
||||||
|
property bool allMessagesLoaded
|
||||||
|
|
||||||
|
signal needToHideConversationInCall
|
||||||
|
signal messagesCleared
|
||||||
|
signal messagesLoaded
|
||||||
|
|
||||||
|
function focusChatView() {
|
||||||
|
chatViewFooter.textInput.forceActiveFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
color: JamiTheme.chatviewBgColor
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: root
|
||||||
|
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
MessageWebViewHeader {
|
||||||
|
id: messageWebViewHeader
|
||||||
|
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: JamiTheme.messageWebViewHeaderPreferredHeight
|
||||||
|
Layout.maximumHeight: JamiTheme.messageWebViewHeaderPreferredHeight
|
||||||
|
|
||||||
|
userAliasLabelText: headerUserAliasLabelText
|
||||||
|
userUserNameLabelText: headerUserUserNameLabelText
|
||||||
|
|
||||||
|
DropArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onDropped: chatViewFooter.setFilePathsToSend(drop.urls)
|
||||||
|
}
|
||||||
|
|
||||||
|
onBackClicked: {
|
||||||
|
mainView.showWelcomeView()
|
||||||
|
}
|
||||||
|
|
||||||
|
onNeedToHideConversationInCall: {
|
||||||
|
root.needToHideConversationInCall()
|
||||||
|
}
|
||||||
|
|
||||||
|
onPluginSelector: {
|
||||||
|
// Create plugin handler picker - PLUGINS
|
||||||
|
PluginHandlerPickerCreation.createPluginHandlerPickerObjects(
|
||||||
|
root, false)
|
||||||
|
PluginHandlerPickerCreation.calculateCurrentGeo(root.width / 2,
|
||||||
|
root.height / 2)
|
||||||
|
PluginHandlerPickerCreation.openPluginHandlerPicker()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StackLayout {
|
||||||
|
id: chatViewStack
|
||||||
|
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.maximumWidth: JamiTheme.chatViewMaximumWidth
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.topMargin: JamiTheme.chatViewHairLineSize
|
||||||
|
Layout.bottomMargin: JamiTheme.chatViewHairLineSize
|
||||||
|
|
||||||
|
currentIndex: CurrentConversation.isRequest ||
|
||||||
|
CurrentConversation.needsSyncing
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
active: CurrentConversation.id !== ""
|
||||||
|
sourceComponent: MessageListView {
|
||||||
|
DropArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onDropped: chatViewFooter.setFilePathsToSend(drop.urls)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InvitationView {
|
||||||
|
id: invitationView
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ReadOnlyFooter {
|
||||||
|
visible: CurrentConversation.readOnly
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatViewFooter {
|
||||||
|
id: chatViewFooter
|
||||||
|
|
||||||
|
visible: {
|
||||||
|
if (CurrentConversation.needsSyncing || CurrentConversation.readOnly)
|
||||||
|
return false
|
||||||
|
else if (CurrentConversation.isSwarm && CurrentConversation.isRequest)
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: implicitHeight
|
||||||
|
Layout.maximumHeight: JamiTheme.chatViewFooterMaximumHeight
|
||||||
|
|
||||||
|
DropArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onDropped: chatViewFooter.setFilePathsToSend(drop.urls)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -136,7 +136,7 @@ Rectangle {
|
||||||
emojiPicker.y = Qt.binding(function() {
|
emojiPicker.y = Qt.binding(function() {
|
||||||
var buttonY = JamiQmlUtils.audioRecordMessageButtonInMainViewPoint.y
|
var buttonY = JamiQmlUtils.audioRecordMessageButtonInMainViewPoint.y
|
||||||
return buttonY - emojiPicker.height - messageBar.marginSize
|
return buttonY - emojiPicker.height - messageBar.marginSize
|
||||||
- JamiTheme.messageWebViewHairLineSize
|
- JamiTheme.chatViewHairLineSize
|
||||||
})
|
})
|
||||||
|
|
||||||
emojiPicker.openEmojiPicker()
|
emojiPicker.openEmojiPicker()
|
||||||
|
@ -201,9 +201,9 @@ Rectangle {
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
Layout.preferredWidth: footerColumnLayout.width
|
Layout.preferredWidth: footerColumnLayout.width
|
||||||
Layout.maximumWidth: JamiTheme.messageWebViewFooterContentMaximumWidth
|
Layout.maximumWidth: JamiTheme.chatViewMaximumWidth
|
||||||
Layout.preferredHeight: filesToSendCount ?
|
Layout.preferredHeight: filesToSendCount ?
|
||||||
JamiTheme.messageWebViewFooterFileContainerPreferredHeight : 0
|
JamiTheme.chatViewFooterFileContainerPreferredHeight : 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -48,9 +48,9 @@ ColumnLayout {
|
||||||
id: messageBarHairLine
|
id: messageBarHairLine
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
|
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
|
||||||
Layout.preferredHeight: JamiTheme.messageWebViewHairLineSize
|
Layout.preferredHeight: JamiTheme.chatViewHairLineSize
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.maximumWidth: JamiTheme.messageWebViewFooterContentMaximumWidth
|
Layout.maximumWidth: JamiTheme.chatViewMaximumWidth
|
||||||
|
|
||||||
color: JamiTheme.tabbarBorderColor
|
color: JamiTheme.tabbarBorderColor
|
||||||
}
|
}
|
||||||
|
@ -60,20 +60,20 @@ ColumnLayout {
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignCenter
|
Layout.alignment: Qt.AlignCenter
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.maximumWidth: JamiTheme.messageWebViewFooterContentMaximumWidth
|
Layout.maximumWidth: JamiTheme.chatViewMaximumWidth
|
||||||
|
|
||||||
spacing: JamiTheme.messageWebViewFooterRowSpacing
|
spacing: JamiTheme.chatViewFooterRowSpacing
|
||||||
|
|
||||||
PushButton {
|
PushButton {
|
||||||
id: sendFileButton
|
id: sendFileButton
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
Layout.leftMargin: marginSize
|
Layout.leftMargin: marginSize
|
||||||
Layout.preferredWidth: JamiTheme.messageWebViewFooterButtonSize
|
Layout.preferredWidth: JamiTheme.chatViewFooterButtonSize
|
||||||
Layout.preferredHeight: JamiTheme.messageWebViewFooterButtonSize
|
Layout.preferredHeight: JamiTheme.chatViewFooterButtonSize
|
||||||
|
|
||||||
radius: JamiTheme.messageWebViewFooterButtonRadius
|
radius: JamiTheme.chatViewFooterButtonRadius
|
||||||
preferredSize: JamiTheme.messageWebViewFooterButtonIconSize - 6
|
preferredSize: JamiTheme.chatViewFooterButtonIconSize - 6
|
||||||
|
|
||||||
toolTipText: JamiStrings.sendFile
|
toolTipText: JamiStrings.sendFile
|
||||||
|
|
||||||
|
@ -89,11 +89,11 @@ ColumnLayout {
|
||||||
id: audioRecordMessageButton
|
id: audioRecordMessageButton
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
Layout.preferredWidth: JamiTheme.messageWebViewFooterButtonSize
|
Layout.preferredWidth: JamiTheme.chatViewFooterButtonSize
|
||||||
Layout.preferredHeight: JamiTheme.messageWebViewFooterButtonSize
|
Layout.preferredHeight: JamiTheme.chatViewFooterButtonSize
|
||||||
|
|
||||||
radius: JamiTheme.messageWebViewFooterButtonRadius
|
radius: JamiTheme.chatViewFooterButtonRadius
|
||||||
preferredSize: JamiTheme.messageWebViewFooterButtonIconSize
|
preferredSize: JamiTheme.chatViewFooterButtonIconSize
|
||||||
|
|
||||||
toolTipText: JamiStrings.leaveAudioMessage
|
toolTipText: JamiStrings.leaveAudioMessage
|
||||||
|
|
||||||
|
@ -111,11 +111,11 @@ ColumnLayout {
|
||||||
id: videoRecordMessageButton
|
id: videoRecordMessageButton
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
Layout.preferredWidth: JamiTheme.messageWebViewFooterButtonSize
|
Layout.preferredWidth: JamiTheme.chatViewFooterButtonSize
|
||||||
Layout.preferredHeight: JamiTheme.messageWebViewFooterButtonSize
|
Layout.preferredHeight: JamiTheme.chatViewFooterButtonSize
|
||||||
|
|
||||||
radius: JamiTheme.messageWebViewFooterButtonRadius
|
radius: JamiTheme.chatViewFooterButtonRadius
|
||||||
preferredSize: JamiTheme.messageWebViewFooterButtonIconSize
|
preferredSize: JamiTheme.chatViewFooterButtonIconSize
|
||||||
|
|
||||||
toolTipText: JamiStrings.leaveVideoMessage
|
toolTipText: JamiStrings.leaveVideoMessage
|
||||||
|
|
||||||
|
@ -144,10 +144,10 @@ ColumnLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.margins: marginSize / 2
|
Layout.margins: marginSize / 2
|
||||||
Layout.preferredHeight: {
|
Layout.preferredHeight: {
|
||||||
return JamiTheme.messageWebViewFooterPreferredHeight
|
return JamiTheme.chatViewFooterPreferredHeight
|
||||||
> contentHeight ? JamiTheme.messageWebViewFooterPreferredHeight : contentHeight
|
> contentHeight ? JamiTheme.chatViewFooterPreferredHeight : contentHeight
|
||||||
}
|
}
|
||||||
Layout.maximumHeight: JamiTheme.messageWebViewFooterTextAreaMaximumHeight
|
Layout.maximumHeight: JamiTheme.chatViewFooterTextAreaMaximumHeight
|
||||||
- marginSize / 2
|
- marginSize / 2
|
||||||
|
|
||||||
onSendMessagesRequired: root.sendMessageButtonClicked()
|
onSendMessagesRequired: root.sendMessageButtonClicked()
|
||||||
|
@ -158,11 +158,11 @@ ColumnLayout {
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
Layout.rightMargin: sendMessageButton.visible ? 0 : marginSize
|
Layout.rightMargin: sendMessageButton.visible ? 0 : marginSize
|
||||||
Layout.preferredWidth: JamiTheme.messageWebViewFooterButtonSize
|
Layout.preferredWidth: JamiTheme.chatViewFooterButtonSize
|
||||||
Layout.preferredHeight: JamiTheme.messageWebViewFooterButtonSize
|
Layout.preferredHeight: JamiTheme.chatViewFooterButtonSize
|
||||||
|
|
||||||
radius: JamiTheme.messageWebViewFooterButtonRadius
|
radius: JamiTheme.chatViewFooterButtonRadius
|
||||||
preferredSize: JamiTheme.messageWebViewFooterButtonIconSize
|
preferredSize: JamiTheme.chatViewFooterButtonIconSize
|
||||||
|
|
||||||
toolTipText: JamiStrings.addEmoji
|
toolTipText: JamiStrings.addEmoji
|
||||||
|
|
||||||
|
@ -183,11 +183,11 @@ ColumnLayout {
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
Layout.rightMargin: visible ? marginSize : 0
|
Layout.rightMargin: visible ? marginSize : 0
|
||||||
Layout.preferredWidth: scale * JamiTheme.messageWebViewFooterButtonSize
|
Layout.preferredWidth: scale * JamiTheme.chatViewFooterButtonSize
|
||||||
Layout.preferredHeight: JamiTheme.messageWebViewFooterButtonSize
|
Layout.preferredHeight: JamiTheme.chatViewFooterButtonSize
|
||||||
|
|
||||||
radius: JamiTheme.messageWebViewFooterButtonRadius
|
radius: JamiTheme.chatViewFooterButtonRadius
|
||||||
preferredSize: JamiTheme.messageWebViewFooterButtonIconSize - 6
|
preferredSize: JamiTheme.chatViewFooterButtonIconSize - 6
|
||||||
|
|
||||||
toolTipText: JamiStrings.send
|
toolTipText: JamiStrings.send
|
||||||
|
|
||||||
|
|
|
@ -136,7 +136,7 @@ Flickable {
|
||||||
// Shift + Enter -> Next Line
|
// Shift + Enter -> Next Line
|
||||||
Keys.onPressed: function (keyEvent) {
|
Keys.onPressed: function (keyEvent) {
|
||||||
if (keyEvent.matches(StandardKey.Paste)) {
|
if (keyEvent.matches(StandardKey.Paste)) {
|
||||||
MessagesAdapter.pasteKeyDetected()
|
MessagesAdapter.onPaste()
|
||||||
keyEvent.accepted = true
|
keyEvent.accepted = true
|
||||||
} else if (keyEvent.key === Qt.Key_Enter ||
|
} else if (keyEvent.key === Qt.Key_Enter ||
|
||||||
keyEvent.key === Qt.Key_Return) {
|
keyEvent.key === Qt.Key_Return) {
|
||||||
|
|
106
src/mainview/components/MessageListView.qml
Normal file
106
src/mainview/components/MessageListView.qml
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 by Savoir-faire Linux
|
||||||
|
* Author: Trevor Tabah <trevor.tabah@savoirfairelinux.com>
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
|
import net.jami.Models 1.1
|
||||||
|
import net.jami.Adapters 1.1
|
||||||
|
import net.jami.Constants 1.1
|
||||||
|
|
||||||
|
import "../../commoncomponents"
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
// fade-in mechanism
|
||||||
|
Component.onCompleted: fadeAnimation.start()
|
||||||
|
Rectangle {
|
||||||
|
id: overlay
|
||||||
|
anchors.fill: parent
|
||||||
|
color: JamiTheme.chatviewBgColor
|
||||||
|
visible: opacity !== 0
|
||||||
|
SequentialAnimation {
|
||||||
|
id: fadeAnimation
|
||||||
|
NumberAnimation {
|
||||||
|
target: overlay; property: "opacity"
|
||||||
|
to: 1; duration: 0
|
||||||
|
}
|
||||||
|
NumberAnimation {
|
||||||
|
target: overlay; property: "opacity"
|
||||||
|
to: 0; duration: 240
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Connections {
|
||||||
|
target: CurrentConversation
|
||||||
|
function onIdChanged() { fadeAnimation.start() }
|
||||||
|
}
|
||||||
|
|
||||||
|
topMargin: 12
|
||||||
|
bottomMargin: 6
|
||||||
|
spacing: 2
|
||||||
|
anchors.centerIn: parent
|
||||||
|
height: parent.height
|
||||||
|
width: parent.width
|
||||||
|
displayMarginBeginning: 2048
|
||||||
|
displayMarginEnd: 2048
|
||||||
|
maximumFlickVelocity: 2048
|
||||||
|
verticalLayoutDirection: ListView.BottomToTop
|
||||||
|
clip: true
|
||||||
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
|
currentIndex: -1
|
||||||
|
|
||||||
|
ScrollBar.vertical: ScrollBar {}
|
||||||
|
|
||||||
|
model: MessagesAdapter.messageListModel
|
||||||
|
|
||||||
|
delegate: MessageDelegate {}
|
||||||
|
|
||||||
|
function getDistanceToBottom() {
|
||||||
|
const scrollDiff = ScrollBar.vertical.position -
|
||||||
|
(1.0 - ScrollBar.vertical.size)
|
||||||
|
return Math.abs(scrollDiff) * contentHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
onAtYBeginningChanged: loadMoreMsgsIfNeeded()
|
||||||
|
|
||||||
|
function loadMoreMsgsIfNeeded() {
|
||||||
|
if (atYBeginning && !CurrentConversation.allMessagesLoaded)
|
||||||
|
MessagesAdapter.loadMoreMessages()
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: MessagesAdapter
|
||||||
|
|
||||||
|
function onNewInteraction() {
|
||||||
|
if (root.getDistanceToBottom() < 80 &&
|
||||||
|
!root.atYEnd) {
|
||||||
|
Qt.callLater(root.positionViewAtBeginning)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMoreMessagesLoaded() {
|
||||||
|
if (root.contentHeight < root.height) {
|
||||||
|
root.loadMoreMsgsIfNeeded()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,287 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2020 by Savoir-faire Linux
|
|
||||||
* Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import QtQuick 2.15
|
|
||||||
import QtQuick.Controls 2.15
|
|
||||||
import QtQuick.Layouts 1.15
|
|
||||||
import QtWebEngine 1.10
|
|
||||||
import QtWebChannel 1.15
|
|
||||||
|
|
||||||
import net.jami.Models 1.1
|
|
||||||
import net.jami.Adapters 1.1
|
|
||||||
import net.jami.Constants 1.1
|
|
||||||
|
|
||||||
import "../../commoncomponents"
|
|
||||||
import "../js/pluginhandlerpickercreation.js" as PluginHandlerPickerCreation
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
property string headerUserAliasLabelText: ""
|
|
||||||
property string headerUserUserNameLabelText: ""
|
|
||||||
property bool jsLoaded: false
|
|
||||||
|
|
||||||
signal needToHideConversationInCall
|
|
||||||
signal messagesCleared
|
|
||||||
signal messagesLoaded
|
|
||||||
|
|
||||||
function setSendMessageContent(content) {
|
|
||||||
jsBridgeObject.setSendMessageContentRequest(content)
|
|
||||||
}
|
|
||||||
|
|
||||||
function focusMessageWebView() {
|
|
||||||
messageWebViewFooter.textInput.forceActiveFocus()
|
|
||||||
}
|
|
||||||
|
|
||||||
function webViewRunJavaScript(arg) {
|
|
||||||
messageWebView.runJavaScript(arg)
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateChatviewTheme() {
|
|
||||||
var theme = 'setTheme("\
|
|
||||||
--svg-invert-percentage:' + JamiTheme.invertPercentageInDecimal + ';\
|
|
||||||
--jami-light-blue:' + JamiTheme.jamiLightBlue + ';\
|
|
||||||
--jami-dark-blue: ' + JamiTheme.jamiDarkBlue + ';\
|
|
||||||
--text-color: ' + JamiTheme.chatviewTextColor + ';\
|
|
||||||
--timestamp-color:' + JamiTheme.timestampColor + ';\
|
|
||||||
--message-out-bg:' + JamiTheme.messageOutBgColor + ';\
|
|
||||||
--message-out-txt:' + JamiTheme.messageOutTxtColor + ';\
|
|
||||||
--message-in-bg:' + JamiTheme.messageInBgColor + ';\
|
|
||||||
--message-in-txt:' + JamiTheme.messageInTxtColor + ';\
|
|
||||||
--file-in-timestamp-color:' + JamiTheme.fileOutTimestampColor + ';\
|
|
||||||
--file-out-timestamp-color:' + JamiTheme.fileInTimestampColor + ';\
|
|
||||||
--bg-color:' + JamiTheme.chatviewBgColor + ';\
|
|
||||||
--action-icon-color:' + JamiTheme.chatviewButtonColor + ';\
|
|
||||||
--action-icon-hover-color:' + JamiTheme.hoveredButtonColor + ';\
|
|
||||||
--action-icon-press-color:' + JamiTheme.pressedButtonColor + ';\
|
|
||||||
--placeholder-text-color:' + JamiTheme.placeholderTextColor + ';\
|
|
||||||
--invite-hover-color:' + JamiTheme.inviteHoverColor + ';\
|
|
||||||
--bg-text-input:' + JamiTheme.bgTextInput + ';\
|
|
||||||
--bg-invitation-rect:' + JamiTheme.bgInvitationRectColor + ';\
|
|
||||||
--preview-text-container-color:' + JamiTheme.previewTextContainerColor + ';\
|
|
||||||
--preview-title-color:' + JamiTheme.previewTitleColor + ';\
|
|
||||||
--preview-subtitle-color:' + JamiTheme.previewSubtitleColor + ';\
|
|
||||||
--preview-image-background-color:' + JamiTheme.previewImageBackgroundColor + ';\
|
|
||||||
--preview-card-container-color:' + JamiTheme.previewCardContainerColor + ';\
|
|
||||||
--preview-url-color:' + JamiTheme.previewUrlColor + ';")'
|
|
||||||
messageWebView.runJavaScript("init_picker(" + JamiTheme.darkTheme + ");")
|
|
||||||
messageWebView.runJavaScript(theme);
|
|
||||||
}
|
|
||||||
|
|
||||||
color: JamiTheme.primaryBackgroundColor
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: JamiTheme
|
|
||||||
|
|
||||||
function onDarkThemeChanged() {
|
|
||||||
updateChatviewTheme()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QtObject {
|
|
||||||
id: jsBridgeObject
|
|
||||||
|
|
||||||
// ID, under which this object will be known at chatview.js side.
|
|
||||||
WebChannel.id: "jsbridge"
|
|
||||||
|
|
||||||
// signals to trigger functions in chatview.js
|
|
||||||
// mainly used to avoid input arg string escape
|
|
||||||
signal setSendMessageContentRequest(string content)
|
|
||||||
|
|
||||||
// Functions that are exposed, return code can be derived from js side
|
|
||||||
// by setting callback function.
|
|
||||||
function deleteInteraction(arg) {
|
|
||||||
MessagesAdapter.deleteInteraction(arg)
|
|
||||||
}
|
|
||||||
|
|
||||||
function retryInteraction(arg) {
|
|
||||||
MessagesAdapter.retryInteraction(arg)
|
|
||||||
}
|
|
||||||
|
|
||||||
function openFile(arg) {
|
|
||||||
MessagesAdapter.openFile(arg)
|
|
||||||
}
|
|
||||||
|
|
||||||
function acceptFile(arg) {
|
|
||||||
MessagesAdapter.acceptFile(arg)
|
|
||||||
}
|
|
||||||
|
|
||||||
function refuseFile(arg) {
|
|
||||||
MessagesAdapter.refuseFile(arg)
|
|
||||||
}
|
|
||||||
|
|
||||||
function emitMessagesCleared() {
|
|
||||||
root.messagesCleared()
|
|
||||||
}
|
|
||||||
|
|
||||||
function emitMessagesLoaded() {
|
|
||||||
root.messagesLoaded()
|
|
||||||
}
|
|
||||||
|
|
||||||
function copyToDownloads(interactionId, displayName) {
|
|
||||||
MessagesAdapter.copyToDownloads(interactionId, displayName)
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseI18nData() {
|
|
||||||
return MessagesAdapter.chatviewTranslatedStrings
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadMessages(n) {
|
|
||||||
return MessagesAdapter.loadMessages(n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
anchors.fill: root
|
|
||||||
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
MessageWebViewHeader {
|
|
||||||
id: messageWebViewHeader
|
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: JamiTheme.messageWebViewHeaderPreferredHeight
|
|
||||||
Layout.maximumHeight: JamiTheme.messageWebViewHeaderPreferredHeight
|
|
||||||
|
|
||||||
userAliasLabelText: headerUserAliasLabelText
|
|
||||||
userUserNameLabelText: headerUserUserNameLabelText
|
|
||||||
|
|
||||||
DropArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
onDropped: messageWebViewFooter.setFilePathsToSend(drop.urls)
|
|
||||||
}
|
|
||||||
|
|
||||||
onBackClicked: {
|
|
||||||
mainView.showWelcomeView()
|
|
||||||
}
|
|
||||||
|
|
||||||
onNeedToHideConversationInCall: {
|
|
||||||
root.needToHideConversationInCall()
|
|
||||||
}
|
|
||||||
|
|
||||||
onPluginSelector: {
|
|
||||||
// Create plugin handler picker - PLUGINS
|
|
||||||
PluginHandlerPickerCreation.createPluginHandlerPickerObjects(
|
|
||||||
root, false)
|
|
||||||
PluginHandlerPickerCreation.calculateCurrentGeo(root.width / 2,
|
|
||||||
root.height / 2)
|
|
||||||
PluginHandlerPickerCreation.openPluginHandlerPicker()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StackLayout {
|
|
||||||
id: messageWebViewStack
|
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
Layout.topMargin: JamiTheme.messageWebViewHairLineSize
|
|
||||||
Layout.bottomMargin: JamiTheme.messageWebViewHairLineSize
|
|
||||||
|
|
||||||
currentIndex: CurrentConversation.isRequest || CurrentConversation.needsSyncing
|
|
||||||
|
|
||||||
GeneralWebEngineView {
|
|
||||||
id: messageWebView
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
onCompletedLoadHtml: ":/chatview.html"
|
|
||||||
|
|
||||||
webChannel.registeredObjects: [jsBridgeObject]
|
|
||||||
|
|
||||||
DropArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
onDropped: messageWebViewFooter.setFilePathsToSend(drop.urls)
|
|
||||||
}
|
|
||||||
|
|
||||||
onLoadingChanged: {
|
|
||||||
if (loadRequest.status == WebEngineView.LoadSucceededStatus) {
|
|
||||||
messageWebView.runJavaScript(UtilsAdapter.getStyleSheet(
|
|
||||||
"chatcss",
|
|
||||||
UtilsAdapter.qStringFromFile(
|
|
||||||
":/chatview.css")))
|
|
||||||
messageWebView.runJavaScript(UtilsAdapter.getStyleSheet(
|
|
||||||
"chatwin",
|
|
||||||
UtilsAdapter.qStringFromFile(
|
|
||||||
":/chatview-qt.css")))
|
|
||||||
messageWebView.runJavaScript(UtilsAdapter.qStringFromFile(
|
|
||||||
":/linkify.js"))
|
|
||||||
messageWebView.runJavaScript(UtilsAdapter.qStringFromFile(
|
|
||||||
":/linkify-html.js"))
|
|
||||||
messageWebView.runJavaScript(UtilsAdapter.qStringFromFile(
|
|
||||||
":/linkify-string.js"))
|
|
||||||
messageWebView.runJavaScript(UtilsAdapter.qStringFromFile(
|
|
||||||
":/qwebchannel.js"))
|
|
||||||
messageWebView.runJavaScript(UtilsAdapter.qStringFromFile(
|
|
||||||
":/jed.js"))
|
|
||||||
messageWebView.runJavaScript(UtilsAdapter.qStringFromFile(
|
|
||||||
":/emoji.js"))
|
|
||||||
messageWebView.runJavaScript(UtilsAdapter.qStringFromFile(
|
|
||||||
":/previewInfo.js"))
|
|
||||||
messageWebView.runJavaScript(
|
|
||||||
UtilsAdapter.qStringFromFile(":/chatview.js"),
|
|
||||||
function() {
|
|
||||||
messageWebView.runJavaScript("init_i18n();")
|
|
||||||
MessagesAdapter.setDisplayLinks()
|
|
||||||
updateChatviewTheme()
|
|
||||||
messageWebView.runJavaScript("displayNavbar(false);")
|
|
||||||
messageWebView.runJavaScript("hideMessageBar(true);")
|
|
||||||
jsLoaded = true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
InvitationView {
|
|
||||||
id: invitationView
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ReadOnlyFooter {
|
|
||||||
visible: CurrentConversation.readOnly
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
MessageWebViewFooter {
|
|
||||||
id: messageWebViewFooter
|
|
||||||
|
|
||||||
visible: {
|
|
||||||
if (CurrentConversation.needsSyncing || CurrentConversation.readOnly)
|
|
||||||
return false
|
|
||||||
else if (CurrentConversation.isSwarm && CurrentConversation.isRequest)
|
|
||||||
return false
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: implicitHeight
|
|
||||||
Layout.maximumHeight: JamiTheme.messageWebViewFooterMaximumHeight
|
|
||||||
|
|
||||||
DropArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
onDropped: messageWebViewFooter.setFilePathsToSend(drop.urls)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -223,7 +223,7 @@ Rectangle {
|
||||||
lBorderwidth: 0
|
lBorderwidth: 0
|
||||||
rBorderwidth: 0
|
rBorderwidth: 0
|
||||||
tBorderwidth: 0
|
tBorderwidth: 0
|
||||||
bBorderwidth: JamiTheme.messageWebViewHairLineSize
|
bBorderwidth: JamiTheme.chatViewHairLineSize
|
||||||
borderColor: JamiTheme.tabbarBorderColor
|
borderColor: JamiTheme.tabbarBorderColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ Control {
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
height: JamiTheme.messageWebViewHairLineSize
|
height: JamiTheme.chatViewHairLineSize
|
||||||
width: parent.width
|
width: parent.width
|
||||||
color: JamiTheme.tabbarBorderColor
|
color: JamiTheme.tabbarBorderColor
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/*!
|
/*
|
||||||
* Copyright (C) 2020 by Savoir-faire Linux
|
* Copyright (C) 2020 by Savoir-faire Linux
|
||||||
* Author: Edric Ladent Milaret <edric.ladent-milaret@savoirfairelinux.com>
|
* Author: Edric Ladent Milaret <edric.ladent-milaret@savoirfairelinux.com>
|
||||||
* Author: Anthony Léonard <anthony.leonard@savoirfairelinux.com>
|
* Author: Anthony Léonard <anthony.leonard@savoirfairelinux.com>
|
||||||
|
@ -26,7 +26,6 @@
|
||||||
#include "appsettingsmanager.h"
|
#include "appsettingsmanager.h"
|
||||||
#include "qtutils.h"
|
#include "qtutils.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "webchathelpers.h"
|
|
||||||
|
|
||||||
#include <api/datatransfermodel.h>
|
#include <api/datatransfermodel.h>
|
||||||
|
|
||||||
|
@ -39,13 +38,30 @@
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <QMimeData>
|
#include <QMimeData>
|
||||||
#include <QBuffer>
|
#include <QBuffer>
|
||||||
|
#include <QtMath>
|
||||||
|
|
||||||
MessagesAdapter::MessagesAdapter(AppSettingsManager* settingsManager,
|
MessagesAdapter::MessagesAdapter(AppSettingsManager* settingsManager,
|
||||||
|
PreviewEngine* previewEngine,
|
||||||
LRCInstance* instance,
|
LRCInstance* instance,
|
||||||
QObject* parent)
|
QObject* parent)
|
||||||
: QmlAdapterBase(instance, parent)
|
: QmlAdapterBase(instance, parent)
|
||||||
, settingsManager_(settingsManager)
|
, settingsManager_(settingsManager)
|
||||||
{}
|
, previewEngine_(previewEngine)
|
||||||
|
, filteredMsgListModel_(new FilteredMsgListModel(this))
|
||||||
|
{
|
||||||
|
connect(lrcInstance_, &LRCInstance::selectedConvUidChanged, [this]() {
|
||||||
|
const QString& convId = lrcInstance_->get_selectedConvUid();
|
||||||
|
const auto& conversation = lrcInstance_->getConversationFromConvUid(convId);
|
||||||
|
filteredMsgListModel_->setSourceModel(conversation.interactions.get());
|
||||||
|
set_messageListModel(QVariant::fromValue(filteredMsgListModel_));
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(previewEngine_, &PreviewEngine::infoReady, this, &MessagesAdapter::onPreviewInfoReady);
|
||||||
|
connect(previewEngine_,
|
||||||
|
&PreviewEngine::linkifyReady,
|
||||||
|
this,
|
||||||
|
&MessagesAdapter::onMessageLinkified);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MessagesAdapter::safeInit()
|
MessagesAdapter::safeInit()
|
||||||
|
@ -59,71 +75,26 @@ MessagesAdapter::safeInit()
|
||||||
void
|
void
|
||||||
MessagesAdapter::setupChatView(const QVariantMap& convInfo)
|
MessagesAdapter::setupChatView(const QVariantMap& convInfo)
|
||||||
{
|
{
|
||||||
Utils::oneShotConnect(qmlObj_, SIGNAL(messagesCleared()), this, SLOT(slotMessagesCleared()));
|
auto* convModel = lrcInstance_->getCurrentConversationModel();
|
||||||
setMessagesVisibility(false);
|
auto convId = convInfo["convId"].toString();
|
||||||
clearChatView();
|
if (convInfo["isSwarm"].toBool()) {
|
||||||
setIsSwarm(convInfo["isSwarm"].toBool());
|
convModel->loadConversationMessages(convId, loadChunkSize_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: current conv observe
|
||||||
Q_EMIT newMessageBarPlaceholderText(convInfo["title"].toString());
|
Q_EMIT newMessageBarPlaceholderText(convInfo["title"].toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MessagesAdapter::onNewInteraction(const QString& convUid,
|
MessagesAdapter::loadMoreMessages()
|
||||||
const QString& interactionId,
|
|
||||||
const lrc::api::interaction::Info& interaction)
|
|
||||||
{
|
{
|
||||||
auto accountId = lrcInstance_->get_currentAccountId();
|
auto accountId = lrcInstance_->get_currentAccountId();
|
||||||
newInteraction(accountId, convUid, interactionId, interaction);
|
auto convId = lrcInstance_->get_selectedConvUid();
|
||||||
}
|
const auto& convInfo = lrcInstance_->getConversationFromConvUid(convId, accountId);
|
||||||
|
if (convInfo.isSwarm()) {
|
||||||
void
|
auto* convModel = lrcInstance_->getCurrentConversationModel();
|
||||||
MessagesAdapter::onInteractionStatusUpdated(const QString& convUid,
|
convModel->loadConversationMessages(convId, loadChunkSize_);
|
||||||
const QString& interactionId,
|
|
||||||
const lrc::api::interaction::Info& interaction)
|
|
||||||
{
|
|
||||||
auto currentConversationModel = lrcInstance_->getCurrentConversationModel();
|
|
||||||
updateInteraction(*currentConversationModel, interactionId, interaction);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
MessagesAdapter::onInteractionRemoved(const QString& convUid, const QString& interactionId)
|
|
||||||
{
|
|
||||||
Q_UNUSED(convUid);
|
|
||||||
removeInteraction(interactionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
MessagesAdapter::onNewMessagesAvailable(const QString& accountId, const QString& conversationId)
|
|
||||||
{
|
|
||||||
auto* convModel = lrcInstance_->accountModel().getAccountInfo(accountId).conversationModel.get();
|
|
||||||
auto optConv = convModel->getConversationForUid(conversationId);
|
|
||||||
if (!optConv)
|
|
||||||
return;
|
|
||||||
updateHistory(*convModel, optConv->get().interactions, optConv->get().allMessagesLoaded);
|
|
||||||
Utils::oneShotConnect(qmlObj_, SIGNAL(messagesLoaded()), this, SLOT(slotMessagesLoaded()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
MessagesAdapter::updateConversation(const QString& conversationId)
|
|
||||||
{
|
|
||||||
if (conversationId != lrcInstance_->get_selectedConvUid())
|
|
||||||
return;
|
|
||||||
auto* convModel = lrcInstance_->getCurrentConversationModel();
|
|
||||||
if (auto optConv = convModel->getConversationForUid(conversationId))
|
|
||||||
setConversationProfileData(optConv->get());
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
MessagesAdapter::onComposingStatusChanged(const QString& convId,
|
|
||||||
const QString& contactUri,
|
|
||||||
bool isComposing)
|
|
||||||
{
|
|
||||||
if (convId != lrcInstance_->get_selectedConvUid())
|
|
||||||
return;
|
|
||||||
if (!settingsManager_->getValue(Settings::Key::EnableTypingIndicator).toBool()) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
contactIsComposing(contactUri, isComposing);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -138,33 +109,9 @@ MessagesAdapter::connectConversationModel()
|
||||||
Qt::UniqueConnection);
|
Qt::UniqueConnection);
|
||||||
|
|
||||||
QObject::connect(currentConversationModel,
|
QObject::connect(currentConversationModel,
|
||||||
&ConversationModel::interactionStatusUpdated,
|
&ConversationModel::conversationMessagesLoaded,
|
||||||
this,
|
this,
|
||||||
&MessagesAdapter::onInteractionStatusUpdated,
|
&MessagesAdapter::onConversationMessagesLoaded,
|
||||||
Qt::UniqueConnection);
|
|
||||||
|
|
||||||
QObject::connect(currentConversationModel,
|
|
||||||
&ConversationModel::interactionRemoved,
|
|
||||||
this,
|
|
||||||
&MessagesAdapter::onInteractionRemoved,
|
|
||||||
Qt::UniqueConnection);
|
|
||||||
|
|
||||||
QObject::connect(currentConversationModel,
|
|
||||||
&ConversationModel::newMessagesAvailable,
|
|
||||||
this,
|
|
||||||
&MessagesAdapter::onNewMessagesAvailable,
|
|
||||||
Qt::UniqueConnection);
|
|
||||||
|
|
||||||
QObject::connect(currentConversationModel,
|
|
||||||
&ConversationModel::conversationReady,
|
|
||||||
this,
|
|
||||||
&MessagesAdapter::updateConversation,
|
|
||||||
Qt::UniqueConnection);
|
|
||||||
|
|
||||||
QObject::connect(currentConversationModel,
|
|
||||||
&ConversationModel::composingStatusChanged,
|
|
||||||
this,
|
|
||||||
&MessagesAdapter::onComposingStatusChanged,
|
|
||||||
Qt::UniqueConnection);
|
Qt::UniqueConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,29 +121,6 @@ MessagesAdapter::sendConversationRequest()
|
||||||
lrcInstance_->makeConversationPermanent();
|
lrcInstance_->makeConversationPermanent();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
|
||||||
MessagesAdapter::slotMessagesCleared()
|
|
||||||
{
|
|
||||||
auto* convModel = lrcInstance_->getCurrentConversationModel();
|
|
||||||
|
|
||||||
auto optConv = convModel->getConversationForUid(lrcInstance_->get_selectedConvUid());
|
|
||||||
if (!optConv)
|
|
||||||
return;
|
|
||||||
if (optConv->get().isSwarm() && !optConv->get().allMessagesLoaded) {
|
|
||||||
convModel->loadConversationMessages(optConv->get().uid, 20);
|
|
||||||
} else {
|
|
||||||
updateHistory(*convModel, optConv->get().interactions, optConv->get().allMessagesLoaded);
|
|
||||||
Utils::oneShotConnect(qmlObj_, SIGNAL(messagesLoaded()), this, SLOT(slotMessagesLoaded()));
|
|
||||||
}
|
|
||||||
setConversationProfileData(optConv->get());
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
MessagesAdapter::slotMessagesLoaded()
|
|
||||||
{
|
|
||||||
setMessagesVisibility(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
MessagesAdapter::sendMessage(const QString& message)
|
MessagesAdapter::sendMessage(const QString& message)
|
||||||
{
|
{
|
||||||
|
@ -279,7 +203,7 @@ MessagesAdapter::refuseFile(const QString& interactionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MessagesAdapter::pasteKeyDetected()
|
MessagesAdapter::onPaste()
|
||||||
{
|
{
|
||||||
const QMimeData* mimeData = QApplication::clipboard()->mimeData();
|
const QMimeData* mimeData = QApplication::clipboard()->mimeData();
|
||||||
|
|
||||||
|
@ -328,186 +252,24 @@ MessagesAdapter::userIsComposing(bool isComposing)
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MessagesAdapter::setConversationProfileData(const conversation::Info& convInfo)
|
MessagesAdapter::onNewInteraction(const QString& convUid,
|
||||||
{
|
const QString& interactionId,
|
||||||
// make the all the participant avatars available within the web view
|
const interaction::Info& interaction)
|
||||||
for (const auto& participant : convInfo.participants) {
|
|
||||||
QByteArray ba;
|
|
||||||
QBuffer bu(&ba);
|
|
||||||
Utils::conversationAvatar(lrcInstance_, convInfo.uid).save(&bu, "PNG");
|
|
||||||
setSenderImage(participant, QString::fromLocal8Bit(ba.toBase64()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
MessagesAdapter::newInteraction(const QString& accountId,
|
|
||||||
const QString& convUid,
|
|
||||||
const QString& interactionId,
|
|
||||||
const interaction::Info& interaction)
|
|
||||||
{
|
{
|
||||||
Q_UNUSED(interactionId);
|
Q_UNUSED(interactionId);
|
||||||
try {
|
try {
|
||||||
if (convUid.isEmpty() || convUid != lrcInstance_->get_selectedConvUid()) {
|
if (convUid.isEmpty() || convUid != lrcInstance_->get_selectedConvUid()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
auto accountId = lrcInstance_->get_currentAccountId();
|
||||||
auto& accountInfo = lrcInstance_->getAccountInfo(accountId);
|
auto& accountInfo = lrcInstance_->getAccountInfo(accountId);
|
||||||
auto& convModel = accountInfo.conversationModel;
|
auto& convModel = accountInfo.conversationModel;
|
||||||
convModel->clearUnreadInteractions(convUid);
|
convModel->clearUnreadInteractions(convUid);
|
||||||
printNewInteraction(*convModel, interactionId, interaction);
|
|
||||||
Q_EMIT newInteraction(static_cast<int>(interaction.type));
|
Q_EMIT newInteraction(static_cast<int>(interaction.type));
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* JS invoke.
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
MessagesAdapter::setMessagesVisibility(bool visible)
|
|
||||||
{
|
|
||||||
QString s = QString::fromLatin1(visible ? "showMessagesDiv();" : "hideMessagesDiv();");
|
|
||||||
QMetaObject::invokeMethod(qmlObj_, "webViewRunJavaScript", Q_ARG(QVariant, s));
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
MessagesAdapter::setIsSwarm(bool isSwarm)
|
|
||||||
{
|
|
||||||
QString s = QString::fromLatin1("set_is_swarm(%1)").arg(isSwarm);
|
|
||||||
QMetaObject::invokeMethod(qmlObj_, "webViewRunJavaScript", Q_ARG(QVariant, s));
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
MessagesAdapter::clearChatView()
|
|
||||||
{
|
|
||||||
QString s = QString::fromLatin1("clearMessages();");
|
|
||||||
QMetaObject::invokeMethod(qmlObj_, "webViewRunJavaScript", Q_ARG(QVariant, s));
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
MessagesAdapter::setDisplayLinks()
|
|
||||||
{
|
|
||||||
QString s
|
|
||||||
= QString::fromLatin1("setDisplayLinks(%1);")
|
|
||||||
.arg(settingsManager_->getValue(Settings::Key::DisplayHyperlinkPreviews).toBool());
|
|
||||||
QMetaObject::invokeMethod(qmlObj_, "webViewRunJavaScript", Q_ARG(QVariant, s));
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
MessagesAdapter::updateHistory(lrc::api::ConversationModel& conversationModel,
|
|
||||||
MessagesList interactions,
|
|
||||||
bool allLoaded)
|
|
||||||
{
|
|
||||||
auto conversationId = lrcInstance_->get_selectedConvUid();
|
|
||||||
auto interactionsStr
|
|
||||||
= interactionsToJsonArrayObject(conversationModel, conversationId, interactions).toUtf8();
|
|
||||||
QString s;
|
|
||||||
QTextStream out(&s);
|
|
||||||
out << "updateHistory(" << interactionsStr << ", " << (allLoaded? "true" : "false") << ");";
|
|
||||||
QMetaObject::invokeMethod(qmlObj_, "webViewRunJavaScript", Q_ARG(QVariant, s));
|
|
||||||
conversationModel.clearUnreadInteractions(conversationId);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
MessagesAdapter::setSenderImage(const QString& sender, const QString& senderImage)
|
|
||||||
{
|
|
||||||
QJsonObject setSenderImageObject = QJsonObject();
|
|
||||||
setSenderImageObject.insert("sender_contact_method", QJsonValue(sender));
|
|
||||||
setSenderImageObject.insert("sender_image", QJsonValue(senderImage));
|
|
||||||
|
|
||||||
auto setSenderImageObjectString = QString(
|
|
||||||
QJsonDocument(setSenderImageObject).toJson(QJsonDocument::Compact));
|
|
||||||
QString s = QString::fromLatin1("setSenderImage(%1);")
|
|
||||||
.arg(setSenderImageObjectString.toUtf8().constData());
|
|
||||||
QMetaObject::invokeMethod(qmlObj_, "webViewRunJavaScript", Q_ARG(QVariant, s));
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
MessagesAdapter::printNewInteraction(lrc::api::ConversationModel& conversationModel,
|
|
||||||
const QString& msgId,
|
|
||||||
const lrc::api::interaction::Info& interaction)
|
|
||||||
{
|
|
||||||
auto interactionObject = interactionToJsonInteractionObject(conversationModel,
|
|
||||||
lrcInstance_->get_selectedConvUid(),
|
|
||||||
msgId,
|
|
||||||
interaction)
|
|
||||||
.toUtf8();
|
|
||||||
if (interactionObject.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
QString s = QString::fromLatin1("addMessage(%1);").arg(interactionObject.constData());
|
|
||||||
QMetaObject::invokeMethod(qmlObj_, "webViewRunJavaScript", Q_ARG(QVariant, s));
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
MessagesAdapter::updateInteraction(lrc::api::ConversationModel& conversationModel,
|
|
||||||
const QString& msgId,
|
|
||||||
const lrc::api::interaction::Info& interaction)
|
|
||||||
{
|
|
||||||
auto interactionObject = interactionToJsonInteractionObject(conversationModel,
|
|
||||||
lrcInstance_->get_selectedConvUid(),
|
|
||||||
msgId,
|
|
||||||
interaction)
|
|
||||||
.toUtf8();
|
|
||||||
if (interactionObject.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
QString s = QString::fromLatin1("updateMessage(%1);").arg(interactionObject.constData());
|
|
||||||
QMetaObject::invokeMethod(qmlObj_, "webViewRunJavaScript", Q_ARG(QVariant, s));
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
MessagesAdapter::setMessagesImageContent(const QString& path, bool isBased64)
|
|
||||||
{
|
|
||||||
if (isBased64) {
|
|
||||||
QString param = QString("addImage_base64('%1')").arg(path);
|
|
||||||
QMetaObject::invokeMethod(qmlObj_, "webViewRunJavaScript", Q_ARG(QVariant, param));
|
|
||||||
} else {
|
|
||||||
QString param = QString("addImage_path('file://%1')").arg(path);
|
|
||||||
QMetaObject::invokeMethod(qmlObj_, "webViewRunJavaScript", Q_ARG(QVariant, param));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
MessagesAdapter::setMessagesFileContent(const QString& path)
|
|
||||||
{
|
|
||||||
qint64 fileSize = QFileInfo(path).size();
|
|
||||||
QString fileName = QFileInfo(path).fileName();
|
|
||||||
|
|
||||||
QString param = QString("addFile_path('%1','%2','%3')")
|
|
||||||
.arg(path, fileName, Utils::humanFileSize(fileSize));
|
|
||||||
|
|
||||||
QMetaObject::invokeMethod(qmlObj_, "webViewRunJavaScript", Q_ARG(QVariant, param));
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
MessagesAdapter::removeInteraction(const QString& interactionId)
|
|
||||||
{
|
|
||||||
QString s = QString::fromLatin1("removeInteraction(%1);").arg(interactionId);
|
|
||||||
QMetaObject::invokeMethod(qmlObj_, "webViewRunJavaScript", Q_ARG(QVariant, s));
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
MessagesAdapter::setSendMessageContent(const QString& content)
|
|
||||||
{
|
|
||||||
QMetaObject::invokeMethod(qmlObj_, "setSendMessageContent", Q_ARG(QVariant, content));
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
MessagesAdapter::contactIsComposing(const QString& contactUri, bool isComposing)
|
|
||||||
{
|
|
||||||
auto* convModel = lrcInstance_->getCurrentConversationModel();
|
|
||||||
auto convInfo = convModel->getConversationForUid(lrcInstance_->get_selectedConvUid());
|
|
||||||
if (!convInfo)
|
|
||||||
return;
|
|
||||||
auto& conv = convInfo->get();
|
|
||||||
bool showIsComposing = conv.participants.first() == contactUri;
|
|
||||||
if (showIsComposing) {
|
|
||||||
QString s
|
|
||||||
= QString::fromLatin1("showTypingIndicator(`%1`, %2);").arg(contactUri).arg(isComposing);
|
|
||||||
QMetaObject::invokeMethod(qmlObj_, "webViewRunJavaScript", Q_ARG(QVariant, s));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
MessagesAdapter::acceptInvitation(const QString& convId)
|
MessagesAdapter::acceptInvitation(const QString& convId)
|
||||||
{
|
{
|
||||||
|
@ -577,12 +339,75 @@ MessagesAdapter::removeContact(const QString& convUid, bool banContact)
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MessagesAdapter::loadMessages(int n)
|
MessagesAdapter::onPreviewInfoReady(QString messageId, QVariantMap info)
|
||||||
{
|
{
|
||||||
auto* convModel = lrcInstance_->getCurrentConversationModel();
|
const QString& convId = lrcInstance_->get_selectedConvUid();
|
||||||
auto convOpt = convModel->getConversationForUid(lrcInstance_->get_selectedConvUid());
|
const QString& accId = lrcInstance_->get_currentAccountId();
|
||||||
if (!convOpt)
|
auto& conversation = lrcInstance_->getConversationFromConvUid(convId, accId);
|
||||||
return;
|
conversation.interactions->addHyperlinkInfo(messageId, info);
|
||||||
if (convOpt->get().isSwarm() && !convOpt->get().allMessagesLoaded)
|
}
|
||||||
convModel->loadConversationMessages(convOpt->get().uid, n);
|
|
||||||
|
void
|
||||||
|
MessagesAdapter::onConversationMessagesLoaded(uint32_t, const QString& convId)
|
||||||
|
{
|
||||||
|
if (convId != lrcInstance_->get_selectedConvUid())
|
||||||
|
return;
|
||||||
|
Q_EMIT moreMessagesLoaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
MessagesAdapter::parseMessageUrls(const QString& messageId, const QString& msg)
|
||||||
|
{
|
||||||
|
previewEngine_->parseMessage(messageId, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
MessagesAdapter::onMessageLinkified(const QString& messageId, const QString& linkified)
|
||||||
|
{
|
||||||
|
const QString& convId = lrcInstance_->get_selectedConvUid();
|
||||||
|
const QString& accId = lrcInstance_->get_currentAccountId();
|
||||||
|
auto& conversation = lrcInstance_->getConversationFromConvUid(convId, accId);
|
||||||
|
conversation.interactions->linkifyMessage(messageId, linkified);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
MessagesAdapter::isImage(const QString& message)
|
||||||
|
{
|
||||||
|
QRegularExpression pattern("[^\\s]+(.*?)\\.(jpg|jpeg|png)$",
|
||||||
|
QRegularExpression::CaseInsensitiveOption);
|
||||||
|
QRegularExpressionMatch match = pattern.match(message);
|
||||||
|
return match.hasMatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
MessagesAdapter::isAnimatedImage(const QString& msg)
|
||||||
|
{
|
||||||
|
QRegularExpression pattern("[^\\s]+(.*?)\\.(gif|apng|webp|avif|flif)$",
|
||||||
|
QRegularExpression::CaseInsensitiveOption);
|
||||||
|
QRegularExpressionMatch match = pattern.match(msg);
|
||||||
|
return match.hasMatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString
|
||||||
|
MessagesAdapter::getFormattedTime(const quint64 timestamp)
|
||||||
|
{
|
||||||
|
const auto now = QDateTime::currentDateTime();
|
||||||
|
const auto seconds = now.toSecsSinceEpoch() - timestamp;
|
||||||
|
auto interval = qFloor(seconds / (3600 * 24));
|
||||||
|
if (interval > 5)
|
||||||
|
return QLocale::system().toString(QDateTime::fromSecsSinceEpoch(timestamp),
|
||||||
|
QLocale::ShortFormat);
|
||||||
|
if (interval > 1)
|
||||||
|
return QObject::tr("%1 days ago").arg(interval);
|
||||||
|
if (interval == 1)
|
||||||
|
return QObject::tr("one day ago");
|
||||||
|
interval = qFloor(seconds / 3600);
|
||||||
|
if (interval > 1)
|
||||||
|
return QObject::tr("%1 hours ago").arg(interval);
|
||||||
|
if (interval == 1)
|
||||||
|
return QObject::tr("one hour ago");
|
||||||
|
interval = qFloor(seconds / 60);
|
||||||
|
if (interval > 1)
|
||||||
|
return QObject::tr("%1 minutes ago").arg(interval);
|
||||||
|
return QObject::tr("just now");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/*!
|
/*
|
||||||
* Copyright (C) 2020 by Savoir-faire Linux
|
* Copyright (C) 2020 by Savoir-faire Linux
|
||||||
* Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
|
* Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
|
||||||
*
|
*
|
||||||
|
@ -20,28 +20,66 @@
|
||||||
|
|
||||||
#include "lrcinstance.h"
|
#include "lrcinstance.h"
|
||||||
#include "qmladapterbase.h"
|
#include "qmladapterbase.h"
|
||||||
|
#include "previewengine.h"
|
||||||
|
|
||||||
#include "api/chatview.h"
|
#include "api/chatview.h"
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
|
#include <QSortFilterProxyModel>
|
||||||
|
|
||||||
|
class FilteredMsgListModel final : public QSortFilterProxyModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit FilteredMsgListModel(QObject* parent = nullptr)
|
||||||
|
: QSortFilterProxyModel(parent)
|
||||||
|
{
|
||||||
|
sort(0, Qt::AscendingOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override
|
||||||
|
{
|
||||||
|
auto index = sourceModel()->index(sourceRow, 0, sourceParent);
|
||||||
|
auto type = sourceModel()->data(index, MessageList::Role::Type).toInt();
|
||||||
|
return static_cast<interaction::Type>(type) != interaction::Type::MERGE;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool lessThan(const QModelIndex& left, const QModelIndex& right) const override
|
||||||
|
{
|
||||||
|
return left.row() > right.row();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
class AppSettingsManager;
|
class AppSettingsManager;
|
||||||
|
|
||||||
class MessagesAdapter final : public QmlAdapterBase
|
class MessagesAdapter final : public QmlAdapterBase
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(QVariantMap chatviewTranslatedStrings MEMBER chatviewTranslatedStrings_ CONSTANT)
|
QML_RO_PROPERTY(QVariant, messageListModel)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit MessagesAdapter(AppSettingsManager* settingsManager,
|
explicit MessagesAdapter(AppSettingsManager* settingsManager,
|
||||||
|
PreviewEngine* previewEngine,
|
||||||
LRCInstance* instance,
|
LRCInstance* instance,
|
||||||
QObject* parent = nullptr);
|
QObject* parent = nullptr);
|
||||||
~MessagesAdapter() = default;
|
~MessagesAdapter() = default;
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void newInteraction(int type);
|
||||||
|
void newMessageBarPlaceholderText(QString placeholderText);
|
||||||
|
void newFilePasted(QString filePath);
|
||||||
|
void newTextPasted();
|
||||||
|
void previewInformationToQML(QString messageId, QStringList previewInformation);
|
||||||
|
void moreMessagesLoaded();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void safeInit() override;
|
void safeInit() override;
|
||||||
|
|
||||||
Q_INVOKABLE void setupChatView(const QVariantMap& convInfo);
|
Q_INVOKABLE void setupChatView(const QVariantMap& convInfo);
|
||||||
|
Q_INVOKABLE void loadMoreMessages();
|
||||||
Q_INVOKABLE void connectConversationModel();
|
Q_INVOKABLE void connectConversationModel();
|
||||||
Q_INVOKABLE void sendConversationRequest();
|
Q_INVOKABLE void sendConversationRequest();
|
||||||
Q_INVOKABLE void removeConversation(const QString& convUid);
|
Q_INVOKABLE void removeConversation(const QString& convUid);
|
||||||
|
@ -51,70 +89,39 @@ protected:
|
||||||
Q_INVOKABLE void refuseInvitation(const QString& convUid = "");
|
Q_INVOKABLE void refuseInvitation(const QString& convUid = "");
|
||||||
Q_INVOKABLE void blockConversation(const QString& convUid = "");
|
Q_INVOKABLE void blockConversation(const QString& convUid = "");
|
||||||
Q_INVOKABLE void unbanContact(int index);
|
Q_INVOKABLE void unbanContact(int index);
|
||||||
|
|
||||||
// JS Q_INVOKABLE.
|
|
||||||
Q_INVOKABLE void setDisplayLinks();
|
|
||||||
Q_INVOKABLE void sendMessage(const QString& message);
|
Q_INVOKABLE void sendMessage(const QString& message);
|
||||||
Q_INVOKABLE void sendFile(const QString& message);
|
Q_INVOKABLE void sendFile(const QString& message);
|
||||||
Q_INVOKABLE void retryInteraction(const QString& interactionId);
|
|
||||||
Q_INVOKABLE void deleteInteraction(const QString& interactionId);
|
|
||||||
Q_INVOKABLE void openUrl(const QString& url);
|
|
||||||
Q_INVOKABLE void openFile(const QString& arg);
|
|
||||||
Q_INVOKABLE void acceptFile(const QString& arg);
|
Q_INVOKABLE void acceptFile(const QString& arg);
|
||||||
Q_INVOKABLE void refuseFile(const QString& arg);
|
Q_INVOKABLE void refuseFile(const QString& arg);
|
||||||
Q_INVOKABLE void pasteKeyDetected();
|
Q_INVOKABLE void openUrl(const QString& url);
|
||||||
Q_INVOKABLE void userIsComposing(bool isComposing);
|
Q_INVOKABLE void openFile(const QString& arg);
|
||||||
Q_INVOKABLE void loadMessages(int n);
|
Q_INVOKABLE void retryInteraction(const QString& interactionId);
|
||||||
|
Q_INVOKABLE void deleteInteraction(const QString& interactionId);
|
||||||
Q_INVOKABLE void copyToDownloads(const QString& interactionId, const QString& displayName);
|
Q_INVOKABLE void copyToDownloads(const QString& interactionId, const QString& displayName);
|
||||||
|
Q_INVOKABLE void userIsComposing(bool isComposing);
|
||||||
|
Q_INVOKABLE bool isImage(const QString& msg);
|
||||||
|
Q_INVOKABLE bool isAnimatedImage(const QString& msg);
|
||||||
|
Q_INVOKABLE QString getFormattedTime(const quint64 timestamp);
|
||||||
|
Q_INVOKABLE void parseMessageUrls(const QString& messageId, const QString& msg);
|
||||||
|
Q_INVOKABLE void onPaste();
|
||||||
|
|
||||||
// Run corrsponding js functions, c++ to qml.
|
// Run corrsponding js functions, c++ to qml.
|
||||||
void setMessagesVisibility(bool visible);
|
|
||||||
void setIsSwarm(bool isSwarm);
|
|
||||||
void clearChatView();
|
|
||||||
void updateHistory(ConversationModel& conversationModel,
|
|
||||||
MessagesList interactions,
|
|
||||||
bool allLoaded);
|
|
||||||
void setSenderImage(const QString& sender, const QString& senderImage);
|
|
||||||
void printNewInteraction(lrc::api::ConversationModel& conversationModel,
|
|
||||||
const QString& msgId,
|
|
||||||
const lrc::api::interaction::Info& interaction);
|
|
||||||
void updateInteraction(lrc::api::ConversationModel& conversationModel,
|
|
||||||
const QString& msgId,
|
|
||||||
const lrc::api::interaction::Info& interaction);
|
|
||||||
void setMessagesImageContent(const QString& path, bool isBased64 = false);
|
void setMessagesImageContent(const QString& path, bool isBased64 = false);
|
||||||
void setMessagesFileContent(const QString& path);
|
void setMessagesFileContent(const QString& path);
|
||||||
void removeInteraction(const QString& interactionId);
|
|
||||||
void setSendMessageContent(const QString& content);
|
void setSendMessageContent(const QString& content);
|
||||||
void contactIsComposing(const QString& contactUri, bool isComposing);
|
|
||||||
|
|
||||||
Q_SIGNALS:
|
|
||||||
void newInteraction(int type);
|
|
||||||
void newMessageBarPlaceholderText(QString placeholderText);
|
|
||||||
void newFilePasted(QString filePath);
|
|
||||||
void newTextPasted();
|
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void slotMessagesCleared();
|
|
||||||
void slotMessagesLoaded();
|
|
||||||
void onNewInteraction(const QString& convUid,
|
void onNewInteraction(const QString& convUid,
|
||||||
const QString& interactionId,
|
const QString& interactionId,
|
||||||
const interaction::Info& interaction);
|
const interaction::Info& interaction);
|
||||||
void onInteractionStatusUpdated(const QString& convUid,
|
void onPreviewInfoReady(QString messageIndex, QVariantMap urlInMessage);
|
||||||
const QString& interactionId,
|
void onConversationMessagesLoaded(uint32_t requestId, const QString& convId);
|
||||||
const interaction::Info& interaction);
|
void onMessageLinkified(const QString& messageId, const QString& linkified);
|
||||||
void onInteractionRemoved(const QString& convUid, const QString& interactionId);
|
|
||||||
void onNewMessagesAvailable(const QString& accountId, const QString& conversationId);
|
|
||||||
void updateConversation(const QString& conversationId);
|
|
||||||
void onComposingStatusChanged(const QString& uid, const QString& contactUri, bool isComposing);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setConversationProfileData(const lrc::api::conversation::Info& convInfo);
|
|
||||||
void newInteraction(const QString& accountId,
|
|
||||||
const QString& convUid,
|
|
||||||
const QString& interactionId,
|
|
||||||
const interaction::Info& interaction);
|
|
||||||
|
|
||||||
const QVariantMap chatviewTranslatedStrings_ {lrc::api::chatview::getTranslatedStrings()};
|
|
||||||
|
|
||||||
AppSettingsManager* settingsManager_;
|
AppSettingsManager* settingsManager_;
|
||||||
|
PreviewEngine* previewEngine_;
|
||||||
|
FilteredMsgListModel* filteredMsgListModel_;
|
||||||
|
|
||||||
|
static constexpr const int loadChunkSize_ {20};
|
||||||
};
|
};
|
||||||
|
|
94
src/previewengine.cpp
Normal file
94
src/previewengine.cpp
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 by Savoir-faire Linux
|
||||||
|
* Author: Trevor Tabah <trevor.tabah@savoirfairelinux.com>
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "previewengine.h"
|
||||||
|
|
||||||
|
#include <QtWebEngine>
|
||||||
|
#include <QWebEngineScript>
|
||||||
|
#include <QWebEngineProfile>
|
||||||
|
#include <QWebEngineSettings>
|
||||||
|
|
||||||
|
PreviewEngine::PreviewEngine(QObject* parent)
|
||||||
|
: QWebEngineView(qobject_cast<QWidget*>(parent))
|
||||||
|
, pimpl_(new PreviewEnginePrivate(this))
|
||||||
|
{
|
||||||
|
QWebEngineProfile* profile = QWebEngineProfile::defaultProfile();
|
||||||
|
|
||||||
|
QDir dataDir(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation));
|
||||||
|
dataDir.cdUp();
|
||||||
|
auto cachePath = dataDir.absolutePath() + "/jami";
|
||||||
|
profile->setCachePath(cachePath);
|
||||||
|
profile->setPersistentStoragePath(cachePath);
|
||||||
|
profile->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies);
|
||||||
|
profile->setHttpCacheType(QWebEngineProfile::NoCache);
|
||||||
|
|
||||||
|
setPage(new QWebEnginePage(profile, this));
|
||||||
|
|
||||||
|
settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
|
||||||
|
settings()->setAttribute(QWebEngineSettings::ScrollAnimatorEnabled, false);
|
||||||
|
settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false);
|
||||||
|
settings()->setAttribute(QWebEngineSettings::PluginsEnabled, false);
|
||||||
|
settings()->setAttribute(QWebEngineSettings::ScreenCaptureEnabled, false);
|
||||||
|
settings()->setAttribute(QWebEngineSettings::LinksIncludedInFocusChain, false);
|
||||||
|
settings()->setAttribute(QWebEngineSettings::LocalStorageEnabled, false);
|
||||||
|
settings()->setAttribute(QWebEngineSettings::AllowRunningInsecureContent, true);
|
||||||
|
settings()->setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, true);
|
||||||
|
settings()->setAttribute(QWebEngineSettings::XSSAuditingEnabled, false);
|
||||||
|
settings()->setAttribute(QWebEngineSettings::LocalContentCanAccessFileUrls, true);
|
||||||
|
|
||||||
|
setContextMenuPolicy(Qt::ContextMenuPolicy::NoContextMenu);
|
||||||
|
|
||||||
|
channel_ = new QWebChannel(this);
|
||||||
|
channel_->registerObject(QStringLiteral("jsbridge"), pimpl_);
|
||||||
|
|
||||||
|
page()->setWebChannel(channel_);
|
||||||
|
page()->runJavaScript(Utils::QByteArrayFromFile(":/linkify.js"), QWebEngineScript::MainWorld);
|
||||||
|
page()->runJavaScript(Utils::QByteArrayFromFile(":/linkify-string.js"),
|
||||||
|
QWebEngineScript::MainWorld);
|
||||||
|
page()->runJavaScript(Utils::QByteArrayFromFile(":/qwebchannel.js"),
|
||||||
|
QWebEngineScript::MainWorld);
|
||||||
|
page()->runJavaScript(Utils::QByteArrayFromFile(":/previewInfo.js"),
|
||||||
|
QWebEngineScript::MainWorld);
|
||||||
|
page()->runJavaScript(Utils::QByteArrayFromFile(":/misc/previewInterop.js"),
|
||||||
|
QWebEngineScript::MainWorld);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PreviewEngine::parseMessage(const QString& messageId, const QString& msg)
|
||||||
|
{
|
||||||
|
page()->runJavaScript(QString("parseMessage(`%1`, `%2`)").arg(messageId, msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PreviewEnginePrivate::log(const QString& str)
|
||||||
|
{
|
||||||
|
qDebug() << str;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PreviewEnginePrivate::infoReady(const QString& messageId, const QVariantMap& info)
|
||||||
|
{
|
||||||
|
Q_EMIT parent_->infoReady(messageId, info);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PreviewEnginePrivate::linkifyReady(const QString& messageId, const QString& linkified)
|
||||||
|
{
|
||||||
|
Q_EMIT parent_->linkifyReady(messageId, linkified);
|
||||||
|
}
|
64
src/previewengine.h
Normal file
64
src/previewengine.h
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 by Savoir-faire Linux
|
||||||
|
* Author: Trevor Tabah <trevor.tabah@savoirfairelinux.com>
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
#include <QtWebChannel>
|
||||||
|
#include <QtWebEngine>
|
||||||
|
#include <QtWebEngineCore>
|
||||||
|
#include <QtWebEngine>
|
||||||
|
#include <QWebEngineView>
|
||||||
|
|
||||||
|
class PreviewEngine;
|
||||||
|
|
||||||
|
class PreviewEnginePrivate : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit PreviewEnginePrivate(PreviewEngine* parent)
|
||||||
|
: parent_(parent)
|
||||||
|
{}
|
||||||
|
|
||||||
|
Q_INVOKABLE void infoReady(const QString& messageId, const QVariantMap& info);
|
||||||
|
Q_INVOKABLE void linkifyReady(const QString& messageId, const QString& linkified);
|
||||||
|
Q_INVOKABLE void log(const QString& str);
|
||||||
|
|
||||||
|
private:
|
||||||
|
PreviewEngine* parent_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PreviewEngine : public QWebEngineView
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit PreviewEngine(QObject* parent = nullptr);
|
||||||
|
~PreviewEngine() = default;
|
||||||
|
|
||||||
|
void parseMessage(const QString& messageId, const QString& msg);
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void infoReady(const QString& messageId, const QVariantMap& info);
|
||||||
|
void linkifyReady(const QString& messageId, const QString& linkified);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QWebChannel* channel_;
|
||||||
|
PreviewEnginePrivate* pimpl_;
|
||||||
|
};
|
|
@ -24,6 +24,7 @@
|
||||||
#include "contactadapter.h"
|
#include "contactadapter.h"
|
||||||
#include "pluginadapter.h"
|
#include "pluginadapter.h"
|
||||||
#include "messagesadapter.h"
|
#include "messagesadapter.h"
|
||||||
|
#include "previewengine.h"
|
||||||
#include "utilsadapter.h"
|
#include "utilsadapter.h"
|
||||||
#include "conversationsadapter.h"
|
#include "conversationsadapter.h"
|
||||||
#include "currentconversation.h"
|
#include "currentconversation.h"
|
||||||
|
@ -99,21 +100,22 @@ void
|
||||||
registerTypes(QQmlEngine* engine,
|
registerTypes(QQmlEngine* engine,
|
||||||
SystemTray* systemTray,
|
SystemTray* systemTray,
|
||||||
LRCInstance* lrcInstance,
|
LRCInstance* lrcInstance,
|
||||||
AppSettingsManager* appSettingsManager,
|
AppSettingsManager* settingsManager,
|
||||||
|
PreviewEngine* previewEngine,
|
||||||
ScreenInfo* screenInfo,
|
ScreenInfo* screenInfo,
|
||||||
QObject* parent)
|
QObject* parent)
|
||||||
{
|
{
|
||||||
// setup the adapters (their lifetimes are that of MainApplication)
|
// setup the adapters (their lifetimes are that of MainApplication)
|
||||||
auto callAdapter = new CallAdapter(systemTray, lrcInstance, parent);
|
auto callAdapter = new CallAdapter(systemTray, lrcInstance, parent);
|
||||||
auto messagesAdapter = new MessagesAdapter(appSettingsManager, lrcInstance, parent);
|
auto messagesAdapter = new MessagesAdapter(settingsManager, previewEngine, lrcInstance, parent);
|
||||||
auto conversationsAdapter = new ConversationsAdapter(systemTray, lrcInstance, parent);
|
auto conversationsAdapter = new ConversationsAdapter(systemTray, lrcInstance, parent);
|
||||||
auto avAdapter = new AvAdapter(lrcInstance, parent);
|
auto avAdapter = new AvAdapter(lrcInstance, parent);
|
||||||
auto contactAdapter = new ContactAdapter(lrcInstance, parent);
|
auto contactAdapter = new ContactAdapter(lrcInstance, parent);
|
||||||
auto accountAdapter = new AccountAdapter(appSettingsManager, lrcInstance, parent);
|
auto accountAdapter = new AccountAdapter(settingsManager, lrcInstance, parent);
|
||||||
auto utilsAdapter = new UtilsAdapter(appSettingsManager, systemTray, lrcInstance, parent);
|
auto utilsAdapter = new UtilsAdapter(settingsManager, systemTray, lrcInstance, parent);
|
||||||
auto pluginAdapter = new PluginAdapter(lrcInstance, parent);
|
auto pluginAdapter = new PluginAdapter(lrcInstance, parent);
|
||||||
auto currentConversation = new CurrentConversation(lrcInstance, parent);
|
auto currentConversation = new CurrentConversation(lrcInstance, parent);
|
||||||
auto currentAccount = new CurrentAccount(lrcInstance, appSettingsManager, parent);
|
auto currentAccount = new CurrentAccount(lrcInstance, settingsManager, parent);
|
||||||
auto videoDevices = new VideoDevices(lrcInstance, parent);
|
auto videoDevices = new VideoDevices(lrcInstance, parent);
|
||||||
|
|
||||||
// qml adapter registration
|
// qml adapter registration
|
||||||
|
@ -155,12 +157,14 @@ registerTypes(QQmlEngine* engine,
|
||||||
QML_REGISTERTYPE(NS_MODELS, PluginListPreferenceModel);
|
QML_REGISTERTYPE(NS_MODELS, PluginListPreferenceModel);
|
||||||
QML_REGISTERTYPE(NS_MODELS, FilesToSendListModel);
|
QML_REGISTERTYPE(NS_MODELS, FilesToSendListModel);
|
||||||
QML_REGISTERTYPE(NS_MODELS, SmartListModel);
|
QML_REGISTERTYPE(NS_MODELS, SmartListModel);
|
||||||
|
QML_REGISTERTYPE(NS_MODELS, MessageListModel);
|
||||||
|
|
||||||
// Roles & type enums for models
|
// Roles & type enums for models
|
||||||
QML_REGISTERNAMESPACE(NS_MODELS, AccountList::staticMetaObject, "AccountList");
|
QML_REGISTERNAMESPACE(NS_MODELS, AccountList::staticMetaObject, "AccountList");
|
||||||
QML_REGISTERNAMESPACE(NS_MODELS, ConversationList::staticMetaObject, "ConversationList");
|
QML_REGISTERNAMESPACE(NS_MODELS, ConversationList::staticMetaObject, "ConversationList");
|
||||||
QML_REGISTERNAMESPACE(NS_MODELS, ContactList::staticMetaObject, "ContactList");
|
QML_REGISTERNAMESPACE(NS_MODELS, ContactList::staticMetaObject, "ContactList");
|
||||||
QML_REGISTERNAMESPACE(NS_MODELS, FilesToSend::staticMetaObject, "FilesToSend");
|
QML_REGISTERNAMESPACE(NS_MODELS, FilesToSend::staticMetaObject, "FilesToSend");
|
||||||
|
QML_REGISTERNAMESPACE(NS_MODELS, MessageList::staticMetaObject, "MessageList");
|
||||||
|
|
||||||
// QQuickItems
|
// QQuickItems
|
||||||
QML_REGISTERTYPE(NS_MODELS, PreviewRenderer);
|
QML_REGISTERTYPE(NS_MODELS, PreviewRenderer);
|
||||||
|
@ -176,10 +180,10 @@ registerTypes(QQmlEngine* engine,
|
||||||
|
|
||||||
QML_REGISTERSINGLETONTYPE_POBJECT(NS_CONSTANTS, screenInfo, "ScreenInfo")
|
QML_REGISTERSINGLETONTYPE_POBJECT(NS_CONSTANTS, screenInfo, "ScreenInfo")
|
||||||
QML_REGISTERSINGLETONTYPE_POBJECT(NS_CONSTANTS, lrcInstance, "LRCInstance")
|
QML_REGISTERSINGLETONTYPE_POBJECT(NS_CONSTANTS, lrcInstance, "LRCInstance")
|
||||||
QML_REGISTERSINGLETONTYPE_POBJECT(NS_CONSTANTS, appSettingsManager, "AppSettingsManager")
|
QML_REGISTERSINGLETONTYPE_POBJECT(NS_CONSTANTS, settingsManager, "AppSettingsManager")
|
||||||
|
|
||||||
auto avatarRegistry = new AvatarRegistry(lrcInstance, parent);
|
auto avatarRegistry = new AvatarRegistry(lrcInstance, parent);
|
||||||
auto wizardViewStepModel = new WizardViewStepModel(lrcInstance, accountAdapter, appSettingsManager, parent);
|
auto wizardViewStepModel = new WizardViewStepModel(lrcInstance, accountAdapter, settingsManager, parent);
|
||||||
QML_REGISTERSINGLETONTYPE_POBJECT(NS_HELPERS, avatarRegistry, "AvatarRegistry");
|
QML_REGISTERSINGLETONTYPE_POBJECT(NS_HELPERS, avatarRegistry, "AvatarRegistry");
|
||||||
QML_REGISTERSINGLETONTYPE_POBJECT(NS_MODELS, wizardViewStepModel, "WizardViewStepModel")
|
QML_REGISTERSINGLETONTYPE_POBJECT(NS_MODELS, wizardViewStepModel, "WizardViewStepModel")
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
class SystemTray;
|
class SystemTray;
|
||||||
class LRCInstance;
|
class LRCInstance;
|
||||||
class AppSettingsManager;
|
class AppSettingsManager;
|
||||||
|
class PreviewEngine;
|
||||||
class ScreenInfo;
|
class ScreenInfo;
|
||||||
|
|
||||||
// Hack for QtCreator autocomplete (part 1)
|
// Hack for QtCreator autocomplete (part 1)
|
||||||
|
@ -63,6 +64,7 @@ void registerTypes(QQmlEngine* engine,
|
||||||
SystemTray* systemTray,
|
SystemTray* systemTray,
|
||||||
LRCInstance* lrcInstance,
|
LRCInstance* lrcInstance,
|
||||||
AppSettingsManager* appSettingsManager,
|
AppSettingsManager* appSettingsManager,
|
||||||
|
PreviewEngine* previewEngine,
|
||||||
ScreenInfo* screenInfo,
|
ScreenInfo* screenInfo,
|
||||||
QObject* parent);
|
QObject* parent);
|
||||||
}
|
}
|
||||||
|
|
|
@ -789,7 +789,7 @@ Utils::QByteArrayFromFile(const QString& filename)
|
||||||
if (file.open(QIODevice::ReadOnly)) {
|
if (file.open(QIODevice::ReadOnly)) {
|
||||||
return file.readAll();
|
return file.readAll();
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "can't open file";
|
qDebug() << "QByteArrayFromFile: can't open file";
|
||||||
return QByteArray();
|
return QByteArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,159 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2017-2020 by Savoir-faire Linux
|
|
||||||
* Author: Alexandre Viau <alexandre.viau@savoirfairelinux.com>
|
|
||||||
* Author: S�bastien Blin <sebastien.blin@savoirfairelinux.com>
|
|
||||||
* Author: Hugo Lefeuvre <hugo.lefeuvre@savoirfairelinux.com>
|
|
||||||
* 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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "webchathelpers.h"
|
|
||||||
|
|
||||||
QJsonObject
|
|
||||||
buildInteractionJson(lrc::api::ConversationModel& conversationModel,
|
|
||||||
const QString& convId,
|
|
||||||
const QString msgId,
|
|
||||||
const lrc::api::interaction::Info& inter)
|
|
||||||
{
|
|
||||||
QRegExp reg(".(jpeg|jpg|gif|png)$");
|
|
||||||
auto interaction = inter;
|
|
||||||
if (interaction.type == lrc::api::interaction::Type::DATA_TRANSFER) {
|
|
||||||
if (interaction.body.isEmpty())
|
|
||||||
return {};
|
|
||||||
else if (interaction.body.toLower().contains(reg))
|
|
||||||
interaction.body = "file://" + interaction.body;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (interaction.type == lrc::api::interaction::Type::MERGE)
|
|
||||||
return {};
|
|
||||||
|
|
||||||
auto sender = interaction.authorUri;
|
|
||||||
auto timestamp = QString::number(interaction.timestamp);
|
|
||||||
auto direction = lrc::api::interaction::isOutgoing(interaction) ? QString("out")
|
|
||||||
: QString("in");
|
|
||||||
|
|
||||||
QJsonObject interactionObject = QJsonObject();
|
|
||||||
interactionObject.insert("text", QJsonValue(interaction.body));
|
|
||||||
interactionObject.insert("id", QJsonValue(msgId));
|
|
||||||
interactionObject.insert("sender", QJsonValue(sender));
|
|
||||||
interactionObject.insert("sender_contact_method", QJsonValue(sender));
|
|
||||||
interactionObject.insert("timestamp", QJsonValue(timestamp));
|
|
||||||
interactionObject.insert("direction", QJsonValue(direction));
|
|
||||||
interactionObject.insert("duration", QJsonValue(static_cast<int>(interaction.duration)));
|
|
||||||
|
|
||||||
switch (interaction.type) {
|
|
||||||
case lrc::api::interaction::Type::TEXT:
|
|
||||||
interactionObject.insert("type", QJsonValue("text"));
|
|
||||||
break;
|
|
||||||
case lrc::api::interaction::Type::CALL:
|
|
||||||
interactionObject.insert("type", QJsonValue("call"));
|
|
||||||
break;
|
|
||||||
case lrc::api::interaction::Type::CONTACT:
|
|
||||||
interactionObject.insert("type", QJsonValue("contact"));
|
|
||||||
break;
|
|
||||||
case lrc::api::interaction::Type::DATA_TRANSFER: {
|
|
||||||
interactionObject.insert("type", QJsonValue("data_transfer"));
|
|
||||||
lrc::api::datatransfer::Info info = {};
|
|
||||||
conversationModel.getTransferInfo(convId, msgId, info);
|
|
||||||
if (info.status != lrc::api::datatransfer::Status::INVALID) {
|
|
||||||
interactionObject.insert("totalSize", QJsonValue(qint64(info.totalSize)));
|
|
||||||
interactionObject.insert("progress", QJsonValue(qint64(info.progress)));
|
|
||||||
}
|
|
||||||
interactionObject.insert("displayName", QJsonValue(inter.commit["displayName"]));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case lrc::api::interaction::Type::INVALID:
|
|
||||||
default:
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (interaction.isRead) {
|
|
||||||
interactionObject.insert("delivery_status", QJsonValue("read"));
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (interaction.status) {
|
|
||||||
case lrc::api::interaction::Status::SUCCESS:
|
|
||||||
interactionObject.insert("delivery_status", QJsonValue("sent"));
|
|
||||||
break;
|
|
||||||
case lrc::api::interaction::Status::FAILURE:
|
|
||||||
case lrc::api::interaction::Status::TRANSFER_ERROR:
|
|
||||||
interactionObject.insert("delivery_status", QJsonValue("failure"));
|
|
||||||
break;
|
|
||||||
case lrc::api::interaction::Status::TRANSFER_UNJOINABLE_PEER:
|
|
||||||
interactionObject.insert("delivery_status", QJsonValue("unjoinable peer"));
|
|
||||||
break;
|
|
||||||
case lrc::api::interaction::Status::SENDING:
|
|
||||||
interactionObject.insert("delivery_status", QJsonValue("sending"));
|
|
||||||
break;
|
|
||||||
case lrc::api::interaction::Status::TRANSFER_CREATED:
|
|
||||||
interactionObject.insert("delivery_status", QJsonValue("connecting"));
|
|
||||||
break;
|
|
||||||
case lrc::api::interaction::Status::TRANSFER_ACCEPTED:
|
|
||||||
interactionObject.insert("delivery_status", QJsonValue("accepted"));
|
|
||||||
break;
|
|
||||||
case lrc::api::interaction::Status::TRANSFER_CANCELED:
|
|
||||||
interactionObject.insert("delivery_status", QJsonValue("canceled"));
|
|
||||||
break;
|
|
||||||
case lrc::api::interaction::Status::TRANSFER_ONGOING:
|
|
||||||
interactionObject.insert("delivery_status", QJsonValue("ongoing"));
|
|
||||||
break;
|
|
||||||
case lrc::api::interaction::Status::TRANSFER_AWAITING_PEER:
|
|
||||||
interactionObject.insert("delivery_status", QJsonValue("awaiting peer"));
|
|
||||||
break;
|
|
||||||
case lrc::api::interaction::Status::TRANSFER_AWAITING_HOST:
|
|
||||||
interactionObject.insert("delivery_status", QJsonValue("awaiting host"));
|
|
||||||
break;
|
|
||||||
case lrc::api::interaction::Status::TRANSFER_TIMEOUT_EXPIRED:
|
|
||||||
interactionObject.insert("delivery_status", QJsonValue("awaiting peer timeout"));
|
|
||||||
break;
|
|
||||||
case lrc::api::interaction::Status::TRANSFER_FINISHED:
|
|
||||||
interactionObject.insert("delivery_status", QJsonValue("finished"));
|
|
||||||
break;
|
|
||||||
case lrc::api::interaction::Status::INVALID:
|
|
||||||
case lrc::api::interaction::Status::UNKNOWN:
|
|
||||||
default:
|
|
||||||
interactionObject.insert("delivery_status", QJsonValue("unknown"));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return interactionObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString
|
|
||||||
interactionToJsonInteractionObject(lrc::api::ConversationModel& conversationModel,
|
|
||||||
const QString& convId,
|
|
||||||
const QString& msgId,
|
|
||||||
const lrc::api::interaction::Info& interaction)
|
|
||||||
{
|
|
||||||
auto interactionObject = buildInteractionJson(conversationModel, convId, msgId, interaction);
|
|
||||||
return QString(QJsonDocument(interactionObject).toJson(QJsonDocument::Compact));
|
|
||||||
}
|
|
||||||
|
|
||||||
QString
|
|
||||||
interactionsToJsonArrayObject(lrc::api::ConversationModel& conversationModel,
|
|
||||||
const QString& convId,
|
|
||||||
MessagesList interactions)
|
|
||||||
{
|
|
||||||
QJsonArray array;
|
|
||||||
for (const auto& interaction : interactions) {
|
|
||||||
auto interactionObject = buildInteractionJson(conversationModel,
|
|
||||||
convId,
|
|
||||||
interaction.first,
|
|
||||||
interaction.second);
|
|
||||||
if (!interactionObject.isEmpty()) {
|
|
||||||
array.append(interactionObject);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return QString(QJsonDocument(array).toJson(QJsonDocument::Compact));
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2017-2020 by Savoir-faire Linux
|
|
||||||
* Author: Alexandre Viau <alexandre.viau@savoirfairelinux.com>
|
|
||||||
* Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
|
|
||||||
* Author: Hugo Lefeuvre <hugo.lefeuvre@savoirfairelinux.com>
|
|
||||||
* 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QFile>
|
|
||||||
#include <QJsonArray>
|
|
||||||
#include <QJsonDocument>
|
|
||||||
#include <QJsonObject>
|
|
||||||
|
|
||||||
#include "lrcinstance.h"
|
|
||||||
#include "api/conversationmodel.h"
|
|
||||||
|
|
||||||
QJsonObject buildInteractionJson(lrc::api::ConversationModel& conversationModel,
|
|
||||||
const QString& convId,
|
|
||||||
const QString& msgId,
|
|
||||||
lrc::api::interaction::Info& interaction);
|
|
||||||
QString interactionToJsonInteractionObject(lrc::api::ConversationModel& conversationModel,
|
|
||||||
const QString& convId,
|
|
||||||
const QString& msgId,
|
|
||||||
const lrc::api::interaction::Info& interaction);
|
|
||||||
QString interactionsToJsonArrayObject(lrc::api::ConversationModel& conversationModel,
|
|
||||||
const QString& convId,
|
|
||||||
MessagesList interactions);
|
|
|
@ -22,6 +22,7 @@
|
||||||
#include "appsettingsmanager.h"
|
#include "appsettingsmanager.h"
|
||||||
#include "connectivitymonitor.h"
|
#include "connectivitymonitor.h"
|
||||||
#include "systemtray.h"
|
#include "systemtray.h"
|
||||||
|
#include "previewengine.h"
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
|
||||||
|
@ -75,6 +76,7 @@ public:
|
||||||
systemTray_.get(),
|
systemTray_.get(),
|
||||||
lrcInstance_.get(),
|
lrcInstance_.get(),
|
||||||
settingsManager_.get(),
|
settingsManager_.get(),
|
||||||
|
previewEngine_.get(),
|
||||||
&screenInfo_,
|
&screenInfo_,
|
||||||
this);
|
this);
|
||||||
}
|
}
|
||||||
|
@ -116,6 +118,7 @@ private:
|
||||||
QScopedPointer<ConnectivityMonitor> connectivityMonitor_;
|
QScopedPointer<ConnectivityMonitor> connectivityMonitor_;
|
||||||
QScopedPointer<AppSettingsManager> settingsManager_;
|
QScopedPointer<AppSettingsManager> settingsManager_;
|
||||||
QScopedPointer<SystemTray> systemTray_;
|
QScopedPointer<SystemTray> systemTray_;
|
||||||
|
QScopedPointer<PreviewEngine> previewEngine_;
|
||||||
ScreenInfo screenInfo_;
|
ScreenInfo screenInfo_;
|
||||||
|
|
||||||
bool muteDring_ {false};
|
bool muteDring_ {false};
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<qresource prefix="/">
|
<qresource prefix="/">
|
||||||
<file>src/tst_LocalAccount.qml</file>
|
<file>src/tst_LocalAccount.qml</file>
|
||||||
<file>src/tst_PresenceIndicator.qml</file>
|
<file>src/tst_PresenceIndicator.qml</file>
|
||||||
<file>src/tst_MessageWebViewFooter.qml</file>
|
<file>src/tst_ChatViewFooter.qml</file>
|
||||||
<file>src/resources/gif_test.gif</file>
|
<file>src/resources/gif_test.gif</file>
|
||||||
<file>src/resources/gz_test.gz</file>
|
<file>src/resources/gz_test.gz</file>
|
||||||
<file>src/resources/png_test.png</file>
|
<file>src/resources/png_test.png</file>
|
||||||
|
|
|
@ -35,13 +35,13 @@ ColumnLayout {
|
||||||
width: 300
|
width: 300
|
||||||
height: uut.implicitHeight
|
height: uut.implicitHeight
|
||||||
|
|
||||||
MessageWebViewFooter {
|
ChatViewFooter {
|
||||||
id: uut
|
id: uut
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: implicitHeight
|
Layout.preferredHeight: implicitHeight
|
||||||
Layout.maximumHeight: JamiTheme.messageWebViewFooterMaximumHeight
|
Layout.maximumHeight: JamiTheme.chatViewMaximumWidth
|
||||||
|
|
||||||
TestCase {
|
TestCase {
|
||||||
name: "MessageWebViewFooter Send Message Button Visibility Test"
|
name: "MessageWebViewFooter Send Message Button Visibility Test"
|
|
@ -40,9 +40,9 @@ ColumnLayout {
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
Layout.preferredWidth: root.width
|
Layout.preferredWidth: root.width
|
||||||
Layout.maximumWidth: JamiTheme.messageWebViewFooterContentMaximumWidth
|
Layout.maximumWidth: JamiTheme.chatViewMaximumWidth
|
||||||
Layout.preferredHeight: filesToSendCount ?
|
Layout.preferredHeight: filesToSendCount ?
|
||||||
JamiTheme.messageWebViewFooterFileContainerPreferredHeight : 0
|
JamiTheme.chatViewFooterFileContainerPreferredHeight : 0
|
||||||
|
|
||||||
TestCase {
|
TestCase {
|
||||||
name: "FilesToSendContainer add/remove file test"
|
name: "FilesToSendContainer add/remove file test"
|
||||||
|
|
Loading…
Add table
Reference in a new issue