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}/runguard.cpp
|
||||
${SRC_DIR}/updatemanager.cpp
|
||||
${SRC_DIR}/webchathelpers.cpp
|
||||
${SRC_DIR}/main.cpp
|
||||
${SRC_DIR}/smartlistmodel.cpp
|
||||
${SRC_DIR}/utils.cpp
|
||||
|
@ -87,7 +86,8 @@ set(COMMON_SOURCES
|
|||
${SRC_DIR}/avatarregistry.cpp
|
||||
${SRC_DIR}/currentconversation.cpp
|
||||
${SRC_DIR}/currentaccount.cpp
|
||||
${SRC_DIR}/videodevices.cpp)
|
||||
${SRC_DIR}/videodevices.cpp
|
||||
${SRC_DIR}/previewengine.cpp)
|
||||
|
||||
set(COMMON_HEADERS
|
||||
${SRC_DIR}/avatarimageprovider.h
|
||||
|
@ -99,7 +99,6 @@ set(COMMON_HEADERS
|
|||
${SRC_DIR}/version.h
|
||||
${SRC_DIR}/accountlistmodel.h
|
||||
${SRC_DIR}/runguard.h
|
||||
${SRC_DIR}/webchathelpers.h
|
||||
${SRC_DIR}/rendermanager.h
|
||||
${SRC_DIR}/connectivitymonitor.h
|
||||
${SRC_DIR}/jamiavatartheme.h
|
||||
|
@ -144,7 +143,8 @@ set(COMMON_HEADERS
|
|||
${SRC_DIR}/avatarregistry.h
|
||||
${SRC_DIR}/currentconversation.h
|
||||
${SRC_DIR}/currentaccount.h
|
||||
${SRC_DIR}/videodevices.h)
|
||||
${SRC_DIR}/videodevices.h
|
||||
${SRC_DIR}/previewengine.h)
|
||||
|
||||
set(QML_LIBS
|
||||
Qt5::Quick
|
||||
|
@ -155,7 +155,8 @@ set(QML_LIBS
|
|||
Qt5::Concurrent
|
||||
Qt5::QuickControls2
|
||||
Qt5::WebEngine
|
||||
Qt5::Core)
|
||||
Qt5::Core
|
||||
Qt5::WebEngineWidgets)
|
||||
|
||||
set(QML_LIBS_LIST
|
||||
Core
|
||||
|
@ -166,7 +167,8 @@ set(QML_LIBS_LIST
|
|||
Svg
|
||||
Sql
|
||||
QuickControls2
|
||||
WebEngine)
|
||||
WebEngine
|
||||
WebEngineWidgets)
|
||||
|
||||
set(WINDOWS_SYS_LIBS Shell32.lib
|
||||
Ole32.lib
|
||||
|
|
6
qml.qrc
6
qml.qrc
|
@ -94,7 +94,7 @@
|
|||
<file>src/mainview/components/AboutPopUp.qml</file>
|
||||
<file>src/mainview/components/SidePanel.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/AccountComboBox.qml</file>
|
||||
<file>src/mainview/components/CallStackView.qml</file>
|
||||
|
@ -143,7 +143,7 @@
|
|||
<file>src/commoncomponents/contextmenu/GeneralMenuSeparator.qml</file>
|
||||
<file>src/mainview/components/ParticipantOverlayButton.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/emojiPickerLoader.js</file>
|
||||
<file>src/commoncomponents/emojipicker/emojiPickerLoader.html</file>
|
||||
|
@ -161,5 +161,7 @@
|
|||
<file>src/commoncomponents/BackButton.qml</file>
|
||||
<file>src/commoncomponents/JamiSwitch.qml</file>
|
||||
<file>src/mainview/components/ReadOnlyFooter.qml</file>
|
||||
<file>src/commoncomponents/MessageDelegate.qml</file>
|
||||
<file>src/mainview/components/MessageListView.qml</file>
|
||||
</qresource>
|
||||
</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
|
||||
property color jamiLightBlue: darkTheme ? "#003b4e" : Qt.rgba(59, 193, 211, 0.3)
|
||||
property color jamiDarkBlue: darkTheme ? "#28b1ed" : "#003b4e"
|
||||
property color chatviewTextColor: textColor
|
||||
property color chatviewTextColor: darkTheme ? "#f0f0f0" : "#353637"
|
||||
property color timestampColor: darkTheme ? "#bbb" : "#333"
|
||||
property color messageOutBgColor: darkTheme ? "#28b1ed" : "#cfd8dc"
|
||||
property color messageOutTxtColor: textColor
|
||||
property color messageOutTxtColor: chatviewTextColor
|
||||
property color messageInBgColor: darkTheme? "#616161" : "#cfebf5"
|
||||
property color messageInTxtColor: textColor
|
||||
property color messageInTxtColor: chatviewTextColor
|
||||
property color fileOutTimestampColor: darkTheme ? "#eee" : "#555"
|
||||
property color fileInTimestampColor: darkTheme ? "#999" : "#555"
|
||||
property color chatviewBgColor: darkTheme ? bgDarkMode_ : whiteColor
|
||||
|
@ -271,17 +271,17 @@ Item {
|
|||
property real modalPopupDropShadowSamples: 16
|
||||
|
||||
// MessageWebView
|
||||
property real messageWebViewHairLineSize: 1
|
||||
property real chatViewHairLineSize: 1
|
||||
property real messageWebViewHeaderPreferredHeight: 64
|
||||
property real messageWebViewFooterContentMaximumWidth: 1000
|
||||
property real messageWebViewFooterPreferredHeight: 50
|
||||
property real messageWebViewFooterMaximumHeight: 280
|
||||
property real messageWebViewFooterRowSpacing: 1
|
||||
property real messageWebViewFooterButtonSize: 36
|
||||
property real messageWebViewFooterButtonIconSize: 48
|
||||
property real messageWebViewFooterButtonRadius: 5
|
||||
property real messageWebViewFooterFileContainerPreferredHeight: 150
|
||||
property real messageWebViewFooterTextAreaMaximumHeight: 130
|
||||
property real chatViewMaximumWidth: 900
|
||||
property real chatViewFooterPreferredHeight: 50
|
||||
property real chatViewFooterMaximumHeight: 280
|
||||
property real chatViewFooterRowSpacing: 1
|
||||
property real chatViewFooterButtonSize: 36
|
||||
property real chatViewFooterButtonIconSize: 48
|
||||
property real chatViewFooterButtonRadius: 5
|
||||
property real chatViewFooterFileContainerPreferredHeight: 150
|
||||
property real chatViewFooterTextAreaMaximumHeight: 130
|
||||
|
||||
// MessageWebView File Transfer Container
|
||||
property real filesToSendContainerSpacing: 5
|
||||
|
|
|
@ -108,22 +108,22 @@ ConversationListModelBase::dataForItem(item_t item, int role) const
|
|||
case Role::UnreadMessagesCount:
|
||||
return QVariant(item.unreadMessages);
|
||||
case Role::LastInteractionTimeStamp: {
|
||||
if (!item.interactions.empty()) {
|
||||
auto ts = static_cast<qint32>(item.interactions.at(item.lastMessageUid).timestamp);
|
||||
if (!item.interactions->empty()) {
|
||||
auto ts = static_cast<qint32>(item.interactions->at(item.lastMessageUid).timestamp);
|
||||
return QVariant(ts);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Role::LastInteractionDate: {
|
||||
if (!item.interactions.empty()) {
|
||||
if (!item.interactions->empty()) {
|
||||
return QVariant(
|
||||
Utils::formatTimeString(item.interactions.at(item.lastMessageUid).timestamp));
|
||||
Utils::formatTimeString(item.interactions->at(item.lastMessageUid).timestamp));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Role::LastInteraction: {
|
||||
if (!item.interactions.empty()) {
|
||||
return QVariant(item.interactions.at(item.lastMessageUid).body);
|
||||
if (!item.interactions->empty()) {
|
||||
return QVariant(item.interactions->at(item.lastMessageUid).body);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -60,6 +60,7 @@ CurrentConversation::updateData()
|
|||
set_needsSyncing(convInfo.needsSyncing);
|
||||
set_isSip(accInfo.profileInfo.type == profile::Type::SIP);
|
||||
set_callId(convInfo.getCallId());
|
||||
set_allMessagesLoaded(convInfo.allMessagesLoaded);
|
||||
if (accInfo.callModel->hasCall(callId_)) {
|
||||
auto call = accInfo.callModel->getCall(callId_);
|
||||
set_callState(call.status);
|
||||
|
|
|
@ -44,6 +44,7 @@ class CurrentConversation final : public QObject
|
|||
QML_PROPERTY(bool, inCall)
|
||||
QML_PROPERTY(bool, isTemporary)
|
||||
QML_PROPERTY(bool, isContact)
|
||||
QML_PROPERTY(bool, allMessagesLoaded)
|
||||
|
||||
public:
|
||||
explicit CurrentConversation(LRCInstance* lrcInstance, QObject* parent = nullptr);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/*!
|
||||
/*
|
||||
* Copyright (C) 2015-2020 by Savoir-faire Linux
|
||||
* Author: Edric Ladent Milaret <edric.ladent-milaret@savoirfairelinux.com>
|
||||
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
|
||||
|
@ -25,6 +25,7 @@
|
|||
#include "appsettingsmanager.h"
|
||||
#include "connectivitymonitor.h"
|
||||
#include "systemtray.h"
|
||||
#include "previewengine.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QCommandLineParser>
|
||||
|
@ -149,18 +150,12 @@ MainApplication::MainApplication(int& argc, char** argv)
|
|||
, connectivityMonitor_(new ConnectivityMonitor(this))
|
||||
, settingsManager_(new AppSettingsManager(this))
|
||||
, systemTray_(new SystemTray(settingsManager_.get(), this))
|
||||
, previewEngine_(new PreviewEngine(this))
|
||||
{
|
||||
QObject::connect(this, &QApplication::aboutToQuit, [this] { cleanup(); });
|
||||
}
|
||||
|
||||
MainApplication::~MainApplication()
|
||||
{
|
||||
engine_.reset();
|
||||
systemTray_.reset();
|
||||
settingsManager_.reset();
|
||||
lrcInstance_.reset();
|
||||
connectivityMonitor_.reset();
|
||||
}
|
||||
MainApplication::~MainApplication() {}
|
||||
|
||||
bool
|
||||
MainApplication::init()
|
||||
|
@ -414,6 +409,7 @@ MainApplication::initQmlLayer()
|
|||
systemTray_.get(),
|
||||
lrcInstance_.get(),
|
||||
settingsManager_.get(),
|
||||
previewEngine_.get(),
|
||||
&screenInfo_,
|
||||
this);
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/*!
|
||||
/*
|
||||
* Copyright (C) 2020 by Savoir-faire Linux
|
||||
* Author: Edric Ladent Milaret <edric.ladent-milaret@savoirfairelinux.com>
|
||||
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
|
||||
|
@ -35,6 +35,7 @@ class ConnectivityMonitor;
|
|||
class AppSettingsManager;
|
||||
class SystemTray;
|
||||
class CallAdapter;
|
||||
class PreviewEngine;
|
||||
|
||||
// Provides information about the screen the app is displayed on
|
||||
class ScreenInfo : public QObject
|
||||
|
@ -97,8 +98,7 @@ private:
|
|||
QScopedPointer<ConnectivityMonitor> connectivityMonitor_;
|
||||
QScopedPointer<AppSettingsManager> settingsManager_;
|
||||
QScopedPointer<SystemTray> systemTray_;
|
||||
QScopedPointer<PreviewEngine> previewEngine_;
|
||||
|
||||
ScreenInfo screenInfo_;
|
||||
|
||||
CallAdapter* callAdapter_;
|
||||
};
|
||||
|
|
|
@ -76,8 +76,8 @@ Rectangle {
|
|||
callStackView.needToCloseInCallConversationAndPotentialWindow()
|
||||
LRCInstance.deselectConversation()
|
||||
if (isPageInStack("callStackViewObject", sidePanelViewStack) ||
|
||||
isPageInStack("communicationPageMessageWebView", sidePanelViewStack) ||
|
||||
isPageInStack("communicationPageMessageWebView", mainViewStack) ||
|
||||
isPageInStack("chatView", sidePanelViewStack) ||
|
||||
isPageInStack("chatView", mainViewStack) ||
|
||||
isPageInStack("callStackViewObject", mainViewStack)) {
|
||||
sidePanelViewStack.pop(StackView.Immediate)
|
||||
mainViewStack.pop(welcomePage, StackView.Immediate)
|
||||
|
@ -98,10 +98,10 @@ Rectangle {
|
|||
function pushCommunicationMessageWebView() {
|
||||
if (sidePanelOnly) {
|
||||
sidePanelViewStack.pop(StackView.Immediate)
|
||||
sidePanelViewStack.push(communicationPageMessageWebView, StackView.Immediate)
|
||||
sidePanelViewStack.push(chatView, StackView.Immediate)
|
||||
} else {
|
||||
mainViewStack.pop(welcomePage, StackView.Immediate)
|
||||
mainViewStack.push(communicationPageMessageWebView, StackView.Immediate)
|
||||
mainViewStack.push(chatView, StackView.Immediate)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -164,24 +164,17 @@ Rectangle {
|
|||
}
|
||||
|
||||
function setMainView(convId) {
|
||||
if (!(communicationPageMessageWebView.jsLoaded)) {
|
||||
communicationPageMessageWebView.jsLoadedChanged.connect(
|
||||
function(convId) {
|
||||
return function() { setMainView(convId) }
|
||||
}(convId))
|
||||
return
|
||||
}
|
||||
var item = ConversationsAdapter.getConvInfoMap(convId)
|
||||
if (item.convId === undefined)
|
||||
return
|
||||
communicationPageMessageWebView.headerUserAliasLabelText = item.title
|
||||
communicationPageMessageWebView.headerUserUserNameLabelText = item.bestId
|
||||
chatView.headerUserAliasLabelText = item.title
|
||||
chatView.headerUserUserNameLabelText = item.bestId
|
||||
if (item.callStackViewShouldShow) {
|
||||
if (inSettingsView) {
|
||||
toggleSettingsView()
|
||||
}
|
||||
MessagesAdapter.setupChatView(item)
|
||||
callStackView.setLinkedWebview(communicationPageMessageWebView)
|
||||
callStackView.setLinkedWebview(chatView)
|
||||
callStackView.responsibleAccountId = LRCInstance.currentAccountId
|
||||
callStackView.responsibleConvUid = convId
|
||||
callStackView.isAudioOnly = item.isAudioOnly
|
||||
|
@ -201,13 +194,13 @@ Rectangle {
|
|||
callStackView.needToCloseInCallConversationAndPotentialWindow()
|
||||
MessagesAdapter.setupChatView(item)
|
||||
pushCommunicationMessageWebView()
|
||||
communicationPageMessageWebView.focusMessageWebView()
|
||||
chatView.focusChatView()
|
||||
currentConvUID = convId
|
||||
} else if (isPageInStack("callStackViewObject", sidePanelViewStack)
|
||||
|| isPageInStack("callStackViewObject", mainViewStack)) {
|
||||
callStackView.needToCloseInCallConversationAndPotentialWindow()
|
||||
pushCommunicationMessageWebView()
|
||||
communicationPageMessageWebView.focusMessageWebView()
|
||||
chatView.focusChatView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -396,21 +389,12 @@ Rectangle {
|
|||
onSettingsBackArrowClicked: sidePanelViewStack.pop(StackView.Immediate)
|
||||
}
|
||||
|
||||
MessageWebView {
|
||||
id: communicationPageMessageWebView
|
||||
|
||||
objectName: "communicationPageMessageWebView"
|
||||
|
||||
signal toSendMessageContentSaved(string arg)
|
||||
signal toMessagesCleared
|
||||
signal toMessagesLoaded
|
||||
ChatView {
|
||||
id: chatView
|
||||
|
||||
objectName: "chatView"
|
||||
visible: false
|
||||
|
||||
Component.onCompleted: {
|
||||
// Set qml MessageWebView object pointer to c++.
|
||||
MessagesAdapter.setQmlObject(this)
|
||||
}
|
||||
Component.onCompleted: MessagesAdapter.setQmlObject(this)
|
||||
}
|
||||
|
||||
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() {
|
||||
var buttonY = JamiQmlUtils.audioRecordMessageButtonInMainViewPoint.y
|
||||
return buttonY - emojiPicker.height - messageBar.marginSize
|
||||
- JamiTheme.messageWebViewHairLineSize
|
||||
- JamiTheme.chatViewHairLineSize
|
||||
})
|
||||
|
||||
emojiPicker.openEmojiPicker()
|
||||
|
@ -201,9 +201,9 @@ Rectangle {
|
|||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: footerColumnLayout.width
|
||||
Layout.maximumWidth: JamiTheme.messageWebViewFooterContentMaximumWidth
|
||||
Layout.maximumWidth: JamiTheme.chatViewMaximumWidth
|
||||
Layout.preferredHeight: filesToSendCount ?
|
||||
JamiTheme.messageWebViewFooterFileContainerPreferredHeight : 0
|
||||
JamiTheme.chatViewFooterFileContainerPreferredHeight : 0
|
||||
}
|
||||
}
|
||||
}
|
|
@ -48,9 +48,9 @@ ColumnLayout {
|
|||
id: messageBarHairLine
|
||||
|
||||
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
|
||||
Layout.preferredHeight: JamiTheme.messageWebViewHairLineSize
|
||||
Layout.preferredHeight: JamiTheme.chatViewHairLineSize
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: JamiTheme.messageWebViewFooterContentMaximumWidth
|
||||
Layout.maximumWidth: JamiTheme.chatViewMaximumWidth
|
||||
|
||||
color: JamiTheme.tabbarBorderColor
|
||||
}
|
||||
|
@ -60,20 +60,20 @@ ColumnLayout {
|
|||
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: JamiTheme.messageWebViewFooterContentMaximumWidth
|
||||
Layout.maximumWidth: JamiTheme.chatViewMaximumWidth
|
||||
|
||||
spacing: JamiTheme.messageWebViewFooterRowSpacing
|
||||
spacing: JamiTheme.chatViewFooterRowSpacing
|
||||
|
||||
PushButton {
|
||||
id: sendFileButton
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.leftMargin: marginSize
|
||||
Layout.preferredWidth: JamiTheme.messageWebViewFooterButtonSize
|
||||
Layout.preferredHeight: JamiTheme.messageWebViewFooterButtonSize
|
||||
Layout.preferredWidth: JamiTheme.chatViewFooterButtonSize
|
||||
Layout.preferredHeight: JamiTheme.chatViewFooterButtonSize
|
||||
|
||||
radius: JamiTheme.messageWebViewFooterButtonRadius
|
||||
preferredSize: JamiTheme.messageWebViewFooterButtonIconSize - 6
|
||||
radius: JamiTheme.chatViewFooterButtonRadius
|
||||
preferredSize: JamiTheme.chatViewFooterButtonIconSize - 6
|
||||
|
||||
toolTipText: JamiStrings.sendFile
|
||||
|
||||
|
@ -89,11 +89,11 @@ ColumnLayout {
|
|||
id: audioRecordMessageButton
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredWidth: JamiTheme.messageWebViewFooterButtonSize
|
||||
Layout.preferredHeight: JamiTheme.messageWebViewFooterButtonSize
|
||||
Layout.preferredWidth: JamiTheme.chatViewFooterButtonSize
|
||||
Layout.preferredHeight: JamiTheme.chatViewFooterButtonSize
|
||||
|
||||
radius: JamiTheme.messageWebViewFooterButtonRadius
|
||||
preferredSize: JamiTheme.messageWebViewFooterButtonIconSize
|
||||
radius: JamiTheme.chatViewFooterButtonRadius
|
||||
preferredSize: JamiTheme.chatViewFooterButtonIconSize
|
||||
|
||||
toolTipText: JamiStrings.leaveAudioMessage
|
||||
|
||||
|
@ -111,11 +111,11 @@ ColumnLayout {
|
|||
id: videoRecordMessageButton
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredWidth: JamiTheme.messageWebViewFooterButtonSize
|
||||
Layout.preferredHeight: JamiTheme.messageWebViewFooterButtonSize
|
||||
Layout.preferredWidth: JamiTheme.chatViewFooterButtonSize
|
||||
Layout.preferredHeight: JamiTheme.chatViewFooterButtonSize
|
||||
|
||||
radius: JamiTheme.messageWebViewFooterButtonRadius
|
||||
preferredSize: JamiTheme.messageWebViewFooterButtonIconSize
|
||||
radius: JamiTheme.chatViewFooterButtonRadius
|
||||
preferredSize: JamiTheme.chatViewFooterButtonIconSize
|
||||
|
||||
toolTipText: JamiStrings.leaveVideoMessage
|
||||
|
||||
|
@ -144,10 +144,10 @@ ColumnLayout {
|
|||
Layout.fillWidth: true
|
||||
Layout.margins: marginSize / 2
|
||||
Layout.preferredHeight: {
|
||||
return JamiTheme.messageWebViewFooterPreferredHeight
|
||||
> contentHeight ? JamiTheme.messageWebViewFooterPreferredHeight : contentHeight
|
||||
return JamiTheme.chatViewFooterPreferredHeight
|
||||
> contentHeight ? JamiTheme.chatViewFooterPreferredHeight : contentHeight
|
||||
}
|
||||
Layout.maximumHeight: JamiTheme.messageWebViewFooterTextAreaMaximumHeight
|
||||
Layout.maximumHeight: JamiTheme.chatViewFooterTextAreaMaximumHeight
|
||||
- marginSize / 2
|
||||
|
||||
onSendMessagesRequired: root.sendMessageButtonClicked()
|
||||
|
@ -158,11 +158,11 @@ ColumnLayout {
|
|||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.rightMargin: sendMessageButton.visible ? 0 : marginSize
|
||||
Layout.preferredWidth: JamiTheme.messageWebViewFooterButtonSize
|
||||
Layout.preferredHeight: JamiTheme.messageWebViewFooterButtonSize
|
||||
Layout.preferredWidth: JamiTheme.chatViewFooterButtonSize
|
||||
Layout.preferredHeight: JamiTheme.chatViewFooterButtonSize
|
||||
|
||||
radius: JamiTheme.messageWebViewFooterButtonRadius
|
||||
preferredSize: JamiTheme.messageWebViewFooterButtonIconSize
|
||||
radius: JamiTheme.chatViewFooterButtonRadius
|
||||
preferredSize: JamiTheme.chatViewFooterButtonIconSize
|
||||
|
||||
toolTipText: JamiStrings.addEmoji
|
||||
|
||||
|
@ -183,11 +183,11 @@ ColumnLayout {
|
|||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.rightMargin: visible ? marginSize : 0
|
||||
Layout.preferredWidth: scale * JamiTheme.messageWebViewFooterButtonSize
|
||||
Layout.preferredHeight: JamiTheme.messageWebViewFooterButtonSize
|
||||
Layout.preferredWidth: scale * JamiTheme.chatViewFooterButtonSize
|
||||
Layout.preferredHeight: JamiTheme.chatViewFooterButtonSize
|
||||
|
||||
radius: JamiTheme.messageWebViewFooterButtonRadius
|
||||
preferredSize: JamiTheme.messageWebViewFooterButtonIconSize - 6
|
||||
radius: JamiTheme.chatViewFooterButtonRadius
|
||||
preferredSize: JamiTheme.chatViewFooterButtonIconSize - 6
|
||||
|
||||
toolTipText: JamiStrings.send
|
||||
|
||||
|
|
|
@ -136,7 +136,7 @@ Flickable {
|
|||
// Shift + Enter -> Next Line
|
||||
Keys.onPressed: function (keyEvent) {
|
||||
if (keyEvent.matches(StandardKey.Paste)) {
|
||||
MessagesAdapter.pasteKeyDetected()
|
||||
MessagesAdapter.onPaste()
|
||||
keyEvent.accepted = true
|
||||
} else if (keyEvent.key === Qt.Key_Enter ||
|
||||
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
|
||||
rBorderwidth: 0
|
||||
tBorderwidth: 0
|
||||
bBorderwidth: JamiTheme.messageWebViewHairLineSize
|
||||
bBorderwidth: JamiTheme.chatViewHairLineSize
|
||||
borderColor: JamiTheme.tabbarBorderColor
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ Control {
|
|||
|
||||
Rectangle {
|
||||
anchors.top: parent.top
|
||||
height: JamiTheme.messageWebViewHairLineSize
|
||||
height: JamiTheme.chatViewHairLineSize
|
||||
width: parent.width
|
||||
color: JamiTheme.tabbarBorderColor
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/*!
|
||||
/*
|
||||
* Copyright (C) 2020 by Savoir-faire Linux
|
||||
* Author: Edric Ladent Milaret <edric.ladent-milaret@savoirfairelinux.com>
|
||||
* Author: Anthony Léonard <anthony.leonard@savoirfairelinux.com>
|
||||
|
@ -26,7 +26,6 @@
|
|||
#include "appsettingsmanager.h"
|
||||
#include "qtutils.h"
|
||||
#include "utils.h"
|
||||
#include "webchathelpers.h"
|
||||
|
||||
#include <api/datatransfermodel.h>
|
||||
|
||||
|
@ -39,13 +38,30 @@
|
|||
#include <QUrl>
|
||||
#include <QMimeData>
|
||||
#include <QBuffer>
|
||||
#include <QtMath>
|
||||
|
||||
MessagesAdapter::MessagesAdapter(AppSettingsManager* settingsManager,
|
||||
PreviewEngine* previewEngine,
|
||||
LRCInstance* instance,
|
||||
QObject* parent)
|
||||
: QmlAdapterBase(instance, parent)
|
||||
, 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
|
||||
MessagesAdapter::safeInit()
|
||||
|
@ -59,71 +75,26 @@ MessagesAdapter::safeInit()
|
|||
void
|
||||
MessagesAdapter::setupChatView(const QVariantMap& convInfo)
|
||||
{
|
||||
Utils::oneShotConnect(qmlObj_, SIGNAL(messagesCleared()), this, SLOT(slotMessagesCleared()));
|
||||
setMessagesVisibility(false);
|
||||
clearChatView();
|
||||
setIsSwarm(convInfo["isSwarm"].toBool());
|
||||
auto* convModel = lrcInstance_->getCurrentConversationModel();
|
||||
auto convId = convInfo["convId"].toString();
|
||||
if (convInfo["isSwarm"].toBool()) {
|
||||
convModel->loadConversationMessages(convId, loadChunkSize_);
|
||||
}
|
||||
|
||||
// TODO: current conv observe
|
||||
Q_EMIT newMessageBarPlaceholderText(convInfo["title"].toString());
|
||||
}
|
||||
|
||||
void
|
||||
MessagesAdapter::onNewInteraction(const QString& convUid,
|
||||
const QString& interactionId,
|
||||
const lrc::api::interaction::Info& interaction)
|
||||
MessagesAdapter::loadMoreMessages()
|
||||
{
|
||||
auto accountId = lrcInstance_->get_currentAccountId();
|
||||
newInteraction(accountId, convUid, interactionId, interaction);
|
||||
}
|
||||
|
||||
void
|
||||
MessagesAdapter::onInteractionStatusUpdated(const QString& convUid,
|
||||
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 convId = lrcInstance_->get_selectedConvUid();
|
||||
const auto& convInfo = lrcInstance_->getConversationFromConvUid(convId, accountId);
|
||||
if (convInfo.isSwarm()) {
|
||||
auto* convModel = lrcInstance_->getCurrentConversationModel();
|
||||
if (auto optConv = convModel->getConversationForUid(conversationId))
|
||||
setConversationProfileData(optConv->get());
|
||||
convModel->loadConversationMessages(convId, loadChunkSize_);
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -138,33 +109,9 @@ MessagesAdapter::connectConversationModel()
|
|||
Qt::UniqueConnection);
|
||||
|
||||
QObject::connect(currentConversationModel,
|
||||
&ConversationModel::interactionStatusUpdated,
|
||||
&ConversationModel::conversationMessagesLoaded,
|
||||
this,
|
||||
&MessagesAdapter::onInteractionStatusUpdated,
|
||||
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,
|
||||
&MessagesAdapter::onConversationMessagesLoaded,
|
||||
Qt::UniqueConnection);
|
||||
}
|
||||
|
||||
|
@ -174,29 +121,6 @@ MessagesAdapter::sendConversationRequest()
|
|||
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
|
||||
MessagesAdapter::sendMessage(const QString& message)
|
||||
{
|
||||
|
@ -279,7 +203,7 @@ MessagesAdapter::refuseFile(const QString& interactionId)
|
|||
}
|
||||
|
||||
void
|
||||
MessagesAdapter::pasteKeyDetected()
|
||||
MessagesAdapter::onPaste()
|
||||
{
|
||||
const QMimeData* mimeData = QApplication::clipboard()->mimeData();
|
||||
|
||||
|
@ -328,20 +252,7 @@ MessagesAdapter::userIsComposing(bool isComposing)
|
|||
}
|
||||
|
||||
void
|
||||
MessagesAdapter::setConversationProfileData(const conversation::Info& convInfo)
|
||||
{
|
||||
// make the all the participant avatars available within the web view
|
||||
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,
|
||||
MessagesAdapter::onNewInteraction(const QString& convUid,
|
||||
const QString& interactionId,
|
||||
const interaction::Info& interaction)
|
||||
{
|
||||
|
@ -350,164 +261,15 @@ MessagesAdapter::newInteraction(const QString& accountId,
|
|||
if (convUid.isEmpty() || convUid != lrcInstance_->get_selectedConvUid()) {
|
||||
return;
|
||||
}
|
||||
auto accountId = lrcInstance_->get_currentAccountId();
|
||||
auto& accountInfo = lrcInstance_->getAccountInfo(accountId);
|
||||
auto& convModel = accountInfo.conversationModel;
|
||||
convModel->clearUnreadInteractions(convUid);
|
||||
printNewInteraction(*convModel, interactionId, interaction);
|
||||
Q_EMIT newInteraction(static_cast<int>(interaction.type));
|
||||
} 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
|
||||
MessagesAdapter::acceptInvitation(const QString& convId)
|
||||
{
|
||||
|
@ -577,12 +339,75 @@ MessagesAdapter::removeContact(const QString& convUid, bool banContact)
|
|||
}
|
||||
|
||||
void
|
||||
MessagesAdapter::loadMessages(int n)
|
||||
MessagesAdapter::onPreviewInfoReady(QString messageId, QVariantMap info)
|
||||
{
|
||||
auto* convModel = lrcInstance_->getCurrentConversationModel();
|
||||
auto convOpt = convModel->getConversationForUid(lrcInstance_->get_selectedConvUid());
|
||||
if (!convOpt)
|
||||
return;
|
||||
if (convOpt->get().isSwarm() && !convOpt->get().allMessagesLoaded)
|
||||
convModel->loadConversationMessages(convOpt->get().uid, n);
|
||||
const QString& convId = lrcInstance_->get_selectedConvUid();
|
||||
const QString& accId = lrcInstance_->get_currentAccountId();
|
||||
auto& conversation = lrcInstance_->getConversationFromConvUid(convId, accId);
|
||||
conversation.interactions->addHyperlinkInfo(messageId, info);
|
||||
}
|
||||
|
||||
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
|
||||
* Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
|
||||
*
|
||||
|
@ -20,28 +20,66 @@
|
|||
|
||||
#include "lrcinstance.h"
|
||||
#include "qmladapterbase.h"
|
||||
#include "previewengine.h"
|
||||
|
||||
#include "api/chatview.h"
|
||||
|
||||
#include <QObject>
|
||||
#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 MessagesAdapter final : public QmlAdapterBase
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QVariantMap chatviewTranslatedStrings MEMBER chatviewTranslatedStrings_ CONSTANT)
|
||||
QML_RO_PROPERTY(QVariant, messageListModel)
|
||||
|
||||
public:
|
||||
explicit MessagesAdapter(AppSettingsManager* settingsManager,
|
||||
PreviewEngine* previewEngine,
|
||||
LRCInstance* instance,
|
||||
QObject* parent = nullptr);
|
||||
~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:
|
||||
void safeInit() override;
|
||||
|
||||
Q_INVOKABLE void setupChatView(const QVariantMap& convInfo);
|
||||
Q_INVOKABLE void loadMoreMessages();
|
||||
Q_INVOKABLE void connectConversationModel();
|
||||
Q_INVOKABLE void sendConversationRequest();
|
||||
Q_INVOKABLE void removeConversation(const QString& convUid);
|
||||
|
@ -51,70 +89,39 @@ protected:
|
|||
Q_INVOKABLE void refuseInvitation(const QString& convUid = "");
|
||||
Q_INVOKABLE void blockConversation(const QString& convUid = "");
|
||||
Q_INVOKABLE void unbanContact(int index);
|
||||
|
||||
// JS Q_INVOKABLE.
|
||||
Q_INVOKABLE void setDisplayLinks();
|
||||
Q_INVOKABLE void sendMessage(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 refuseFile(const QString& arg);
|
||||
Q_INVOKABLE void pasteKeyDetected();
|
||||
Q_INVOKABLE void userIsComposing(bool isComposing);
|
||||
Q_INVOKABLE void loadMessages(int n);
|
||||
Q_INVOKABLE void openUrl(const QString& url);
|
||||
Q_INVOKABLE void openFile(const QString& arg);
|
||||
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 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.
|
||||
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 setMessagesFileContent(const QString& path);
|
||||
void removeInteraction(const QString& interactionId);
|
||||
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:
|
||||
void slotMessagesCleared();
|
||||
void slotMessagesLoaded();
|
||||
void onNewInteraction(const QString& convUid,
|
||||
const QString& interactionId,
|
||||
const interaction::Info& interaction);
|
||||
void onInteractionStatusUpdated(const QString& convUid,
|
||||
const QString& interactionId,
|
||||
const interaction::Info& interaction);
|
||||
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);
|
||||
void onPreviewInfoReady(QString messageIndex, QVariantMap urlInMessage);
|
||||
void onConversationMessagesLoaded(uint32_t requestId, const QString& convId);
|
||||
void onMessageLinkified(const QString& messageId, const QString& linkified);
|
||||
|
||||
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_;
|
||||
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 "pluginadapter.h"
|
||||
#include "messagesadapter.h"
|
||||
#include "previewengine.h"
|
||||
#include "utilsadapter.h"
|
||||
#include "conversationsadapter.h"
|
||||
#include "currentconversation.h"
|
||||
|
@ -99,21 +100,22 @@ void
|
|||
registerTypes(QQmlEngine* engine,
|
||||
SystemTray* systemTray,
|
||||
LRCInstance* lrcInstance,
|
||||
AppSettingsManager* appSettingsManager,
|
||||
AppSettingsManager* settingsManager,
|
||||
PreviewEngine* previewEngine,
|
||||
ScreenInfo* screenInfo,
|
||||
QObject* parent)
|
||||
{
|
||||
// setup the adapters (their lifetimes are that of MainApplication)
|
||||
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 avAdapter = new AvAdapter(lrcInstance, parent);
|
||||
auto contactAdapter = new ContactAdapter(lrcInstance, parent);
|
||||
auto accountAdapter = new AccountAdapter(appSettingsManager, lrcInstance, parent);
|
||||
auto utilsAdapter = new UtilsAdapter(appSettingsManager, systemTray, lrcInstance, parent);
|
||||
auto accountAdapter = new AccountAdapter(settingsManager, lrcInstance, parent);
|
||||
auto utilsAdapter = new UtilsAdapter(settingsManager, systemTray, lrcInstance, parent);
|
||||
auto pluginAdapter = new PluginAdapter(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);
|
||||
|
||||
// qml adapter registration
|
||||
|
@ -155,12 +157,14 @@ registerTypes(QQmlEngine* engine,
|
|||
QML_REGISTERTYPE(NS_MODELS, PluginListPreferenceModel);
|
||||
QML_REGISTERTYPE(NS_MODELS, FilesToSendListModel);
|
||||
QML_REGISTERTYPE(NS_MODELS, SmartListModel);
|
||||
QML_REGISTERTYPE(NS_MODELS, MessageListModel);
|
||||
|
||||
// Roles & type enums for models
|
||||
QML_REGISTERNAMESPACE(NS_MODELS, AccountList::staticMetaObject, "AccountList");
|
||||
QML_REGISTERNAMESPACE(NS_MODELS, ConversationList::staticMetaObject, "ConversationList");
|
||||
QML_REGISTERNAMESPACE(NS_MODELS, ContactList::staticMetaObject, "ContactList");
|
||||
QML_REGISTERNAMESPACE(NS_MODELS, FilesToSend::staticMetaObject, "FilesToSend");
|
||||
QML_REGISTERNAMESPACE(NS_MODELS, MessageList::staticMetaObject, "MessageList");
|
||||
|
||||
// QQuickItems
|
||||
QML_REGISTERTYPE(NS_MODELS, PreviewRenderer);
|
||||
|
@ -176,10 +180,10 @@ registerTypes(QQmlEngine* engine,
|
|||
|
||||
QML_REGISTERSINGLETONTYPE_POBJECT(NS_CONSTANTS, screenInfo, "ScreenInfo")
|
||||
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 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_MODELS, wizardViewStepModel, "WizardViewStepModel")
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
class SystemTray;
|
||||
class LRCInstance;
|
||||
class AppSettingsManager;
|
||||
class PreviewEngine;
|
||||
class ScreenInfo;
|
||||
|
||||
// Hack for QtCreator autocomplete (part 1)
|
||||
|
@ -63,6 +64,7 @@ void registerTypes(QQmlEngine* engine,
|
|||
SystemTray* systemTray,
|
||||
LRCInstance* lrcInstance,
|
||||
AppSettingsManager* appSettingsManager,
|
||||
PreviewEngine* previewEngine,
|
||||
ScreenInfo* screenInfo,
|
||||
QObject* parent);
|
||||
}
|
||||
|
|
|
@ -789,7 +789,7 @@ Utils::QByteArrayFromFile(const QString& filename)
|
|||
if (file.open(QIODevice::ReadOnly)) {
|
||||
return file.readAll();
|
||||
} else {
|
||||
qDebug() << "can't open file";
|
||||
qDebug() << "QByteArrayFromFile: can't open file";
|
||||
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 "connectivitymonitor.h"
|
||||
#include "systemtray.h"
|
||||
#include "previewengine.h"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
|
@ -75,6 +76,7 @@ public:
|
|||
systemTray_.get(),
|
||||
lrcInstance_.get(),
|
||||
settingsManager_.get(),
|
||||
previewEngine_.get(),
|
||||
&screenInfo_,
|
||||
this);
|
||||
}
|
||||
|
@ -116,6 +118,7 @@ private:
|
|||
QScopedPointer<ConnectivityMonitor> connectivityMonitor_;
|
||||
QScopedPointer<AppSettingsManager> settingsManager_;
|
||||
QScopedPointer<SystemTray> systemTray_;
|
||||
QScopedPointer<PreviewEngine> previewEngine_;
|
||||
ScreenInfo screenInfo_;
|
||||
|
||||
bool muteDring_ {false};
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<qresource prefix="/">
|
||||
<file>src/tst_LocalAccount.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/gz_test.gz</file>
|
||||
<file>src/resources/png_test.png</file>
|
||||
|
|
|
@ -35,13 +35,13 @@ ColumnLayout {
|
|||
width: 300
|
||||
height: uut.implicitHeight
|
||||
|
||||
MessageWebViewFooter {
|
||||
ChatViewFooter {
|
||||
id: uut
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: implicitHeight
|
||||
Layout.maximumHeight: JamiTheme.messageWebViewFooterMaximumHeight
|
||||
Layout.maximumHeight: JamiTheme.chatViewMaximumWidth
|
||||
|
||||
TestCase {
|
||||
name: "MessageWebViewFooter Send Message Button Visibility Test"
|
|
@ -40,9 +40,9 @@ ColumnLayout {
|
|||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: root.width
|
||||
Layout.maximumWidth: JamiTheme.messageWebViewFooterContentMaximumWidth
|
||||
Layout.maximumWidth: JamiTheme.chatViewMaximumWidth
|
||||
Layout.preferredHeight: filesToSendCount ?
|
||||
JamiTheme.messageWebViewFooterFileContainerPreferredHeight : 0
|
||||
JamiTheme.chatViewFooterFileContainerPreferredHeight : 0
|
||||
|
||||
TestCase {
|
||||
name: "FilesToSendContainer add/remove file test"
|
||||
|
|
Loading…
Add table
Reference in a new issue