mirror of
https://github.com/savoirfairelinux/jami-client-qt.git
synced 2026-01-08 23:17:32 +08:00
messageListView: add typing indicator
Gitlab: #552 Change-Id: I0a4dc3b61a22aafb40d8a301033c59d2cc02bc79
This commit is contained in:
committed by
Sébastien Blin
parent
02a80519ab
commit
3185df0837
1
qml.qrc
1
qml.qrc
@@ -169,5 +169,6 @@
|
||||
<file>src/commoncomponents/GeneratedMessageDelegate.qml</file>
|
||||
<file>src/commoncomponents/DataTransferMessageDelegate.qml</file>
|
||||
<file>src/mainview/components/ScrollToBottomButton.qml</file>
|
||||
<file>src/commoncomponents/TypingDots.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
107
src/commoncomponents/TypingDots.qml
Normal file
107
src/commoncomponents/TypingDots.qml
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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 QtGraphicalEffects 1.15
|
||||
|
||||
import net.jami.Constants 1.1
|
||||
|
||||
Row {
|
||||
id: root
|
||||
|
||||
property int currentRect: 0
|
||||
|
||||
spacing: 5
|
||||
|
||||
Timer {
|
||||
repeat: true
|
||||
running: true
|
||||
interval: JamiTheme.typingDotsAnimationInterval
|
||||
|
||||
onTriggered: {
|
||||
if (root.currentRect < 2)
|
||||
root.currentRect ++
|
||||
else
|
||||
root.currentRect = 0
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: 3
|
||||
|
||||
Rectangle {
|
||||
id: circleRect
|
||||
|
||||
radius: JamiTheme.typingDotsRadius
|
||||
|
||||
width: JamiTheme.typingDotsSize
|
||||
height: JamiTheme.typingDotsSize
|
||||
color: JamiTheme.typingDotsNormalColor
|
||||
|
||||
states: State {
|
||||
id: enlargeState
|
||||
|
||||
name: "enlarge"
|
||||
when: root.currentRect === index
|
||||
}
|
||||
|
||||
transitions: [
|
||||
Transition {
|
||||
to: "enlarge"
|
||||
ParallelAnimation {
|
||||
NumberAnimation {
|
||||
from: 1.0
|
||||
to: 1.3
|
||||
target: circleRect
|
||||
duration: JamiTheme.typingDotsAnimationInterval
|
||||
property: "scale"
|
||||
}
|
||||
|
||||
ColorAnimation {
|
||||
from: JamiTheme.typingDotsNormalColor
|
||||
to: JamiTheme.typingDotsEnlargeColor
|
||||
target: circleRect
|
||||
property: "color"
|
||||
duration: JamiTheme.typingDotsAnimationInterval
|
||||
}
|
||||
}
|
||||
},
|
||||
Transition {
|
||||
from: "enlarge"
|
||||
ParallelAnimation {
|
||||
NumberAnimation {
|
||||
from: 1.3
|
||||
to: 1.0
|
||||
target: circleRect
|
||||
duration: JamiTheme.typingDotsAnimationInterval
|
||||
property: "scale"
|
||||
}
|
||||
ColorAnimation {
|
||||
from: JamiTheme.typingDotsEnlargeColor
|
||||
to: JamiTheme.typingDotsNormalColor
|
||||
target: circleRect
|
||||
property: "color"
|
||||
duration: JamiTheme.typingDotsAnimationInterval
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,7 +79,7 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
TextMetrics {
|
||||
Text {
|
||||
id: globalTextMetrics
|
||||
}
|
||||
|
||||
@@ -87,6 +87,6 @@ Item {
|
||||
globalTextMetrics.font = font
|
||||
globalTextMetrics.text = text
|
||||
|
||||
return globalTextMetrics.boundingRect
|
||||
return Qt.size(globalTextMetrics.contentWidth, globalTextMetrics.contentHeight)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,6 +257,10 @@ Item {
|
||||
|
||||
// Chatview footer
|
||||
property string jumpToLatest: qsTr("Jump to latest")
|
||||
property string typeIndicatorSingle: qsTr("{} is typing…")
|
||||
property string typeIndicatorPlural: qsTr("{} are typing…")
|
||||
property string typeIndicatorMax: qsTr("Several people are typing…")
|
||||
property string typeIndicatorAnd: qsTr(" and ")
|
||||
|
||||
// ConnectToAccountManager
|
||||
property string enterJAMSURL: qsTr("Enter Jami Account Management Server (JAMS) URL")
|
||||
|
||||
@@ -172,6 +172,10 @@ Item {
|
||||
// Files To Send Container
|
||||
property color removeFileButtonColor: Qt.rgba(96, 95, 97, 0.5)
|
||||
|
||||
// TypingDots
|
||||
property color typingDotsNormalColor: darkTheme ? "#686b72" : "lightgrey"
|
||||
property color typingDotsEnlargeColor: darkTheme ? "white" : Qt.darker("lightgrey", 3.0)
|
||||
|
||||
// Font.
|
||||
property color faddedFontColor: darkTheme? "#c0c0c0" : "#a0a0a0"
|
||||
property color faddedLastInteractionFontColor: darkTheme ? "#c0c0c0" : "#505050"
|
||||
@@ -284,6 +288,11 @@ Item {
|
||||
property real chatViewFooterTextAreaMaximumHeight: 130
|
||||
property real chatViewScrollToBottomButtonBottomMargin: 8
|
||||
|
||||
// TypingDots
|
||||
property real typingDotsAnimationInterval: 500
|
||||
property real typingDotsRadius: 30
|
||||
property real typingDotsSize: 8
|
||||
|
||||
// MessageWebView File Transfer Container
|
||||
property real filesToSendContainerSpacing: 5
|
||||
property real filesToSendContainerPadding: 10
|
||||
|
||||
@@ -151,6 +151,7 @@ ColumnLayout {
|
||||
- marginSize / 2
|
||||
|
||||
onSendMessagesRequired: root.sendMessageButtonClicked()
|
||||
onTextChanged: MessagesAdapter.userIsComposing(text ? true : false)
|
||||
}
|
||||
|
||||
PushButton {
|
||||
|
||||
@@ -273,4 +273,84 @@ ListView {
|
||||
onClicked: root.ScrollBar.vertical.position =
|
||||
1.0 - root.ScrollBar.vertical.size
|
||||
}
|
||||
|
||||
header: Control {
|
||||
id: typeIndicatorContainer
|
||||
|
||||
topPadding: 3
|
||||
|
||||
width: root.width
|
||||
height: typeIndicatorNameText.contentHeight + topPadding
|
||||
|
||||
visible: MessagesAdapter.currentConvComposingList.length
|
||||
|
||||
TypingDots {
|
||||
id: typingDots
|
||||
|
||||
anchors.left: typeIndicatorContainer.left
|
||||
anchors.leftMargin: 5
|
||||
anchors.verticalCenter: typeIndicatorContainer.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
id: typeIndicatorNameText
|
||||
|
||||
anchors.left: typingDots.right
|
||||
anchors.leftMargin: 5
|
||||
anchors.verticalCenter: typeIndicatorContainer.verticalCenter
|
||||
|
||||
width: {
|
||||
var textSize = text ? JamiQmlUtils.getTextBoundingRect(font, text).width : 0
|
||||
var typingContentWidth = typingDots.width + typingDots.anchors.leftMargin
|
||||
+ typeIndicatorNameText.anchors.leftMargin
|
||||
+ typeIndicatorEndingText.contentWidth
|
||||
return Math.min(typeIndicatorContainer.width - 5 - typingContentWidth, textSize)
|
||||
}
|
||||
|
||||
font.pointSize: 8
|
||||
font.bold: Font.DemiBold
|
||||
elide: Text.ElideRight
|
||||
color: JamiTheme.textColor
|
||||
text: {
|
||||
var finalText = ""
|
||||
var nameList = MessagesAdapter.currentConvComposingList
|
||||
|
||||
if (nameList.length > 4)
|
||||
return ""
|
||||
if (nameList.length === 1)
|
||||
return nameList[0]
|
||||
|
||||
for (var i = 0; i < nameList.length; i++) {
|
||||
finalText += nameList[i]
|
||||
|
||||
if (i === nameList.length - 2)
|
||||
finalText += JamiStrings.typeIndicatorAnd
|
||||
else if (i !== nameList.length - 1)
|
||||
finalText += ", "
|
||||
}
|
||||
|
||||
return finalText
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: typeIndicatorEndingText
|
||||
|
||||
anchors.left: typeIndicatorNameText.right
|
||||
anchors.verticalCenter: typeIndicatorContainer.verticalCenter
|
||||
|
||||
font.pointSize: 8
|
||||
color: JamiTheme.textColor
|
||||
text: {
|
||||
var nameList = MessagesAdapter.currentConvComposingList
|
||||
|
||||
if (nameList.length > 4)
|
||||
return JamiStrings.typeIndicatorMax
|
||||
if (nameList.length === 1)
|
||||
return JamiStrings.typeIndicatorSingle.replace("{}", "")
|
||||
|
||||
return JamiStrings.typeIndicatorPlural.replace("{}", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,8 @@ Control {
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
cursorShape: root.opacity ? Qt.PointingHandCursor :
|
||||
Qt.ArrowCursor
|
||||
|
||||
onClicked: root.clicked()
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ MessagesAdapter::MessagesAdapter(AppSettingsManager* settingsManager,
|
||||
const auto& conversation = lrcInstance_->getConversationFromConvUid(convId);
|
||||
filteredMsgListModel_->setSourceModel(conversation.interactions.get());
|
||||
set_messageListModel(QVariant::fromValue(filteredMsgListModel_));
|
||||
set_currentConvComposingList({});
|
||||
});
|
||||
|
||||
connect(previewEngine_, &PreviewEngine::infoReady, this, &MessagesAdapter::onPreviewInfoReady);
|
||||
@@ -113,6 +114,12 @@ MessagesAdapter::connectConversationModel()
|
||||
this,
|
||||
&MessagesAdapter::onConversationMessagesLoaded,
|
||||
Qt::UniqueConnection);
|
||||
|
||||
QObject::connect(currentConversationModel,
|
||||
&ConversationModel::composingStatusChanged,
|
||||
this,
|
||||
&MessagesAdapter::onComposingStatusChanged,
|
||||
Qt::UniqueConnection);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -414,6 +421,22 @@ MessagesAdapter::onMessageLinkified(const QString& messageId, const QString& lin
|
||||
conversation.interactions->linkifyMessage(messageId, linkified);
|
||||
}
|
||||
|
||||
void
|
||||
MessagesAdapter::onComposingStatusChanged(const QString& convId,
|
||||
const QString& contactUri,
|
||||
bool isComposing)
|
||||
{
|
||||
if (lrcInstance_->get_selectedConvUid() == convId) {
|
||||
auto name = lrcInstance_->getCurrentContactModel()->bestNameForContact(contactUri);
|
||||
if (isComposing)
|
||||
currentConvComposingList_.append(name);
|
||||
else
|
||||
currentConvComposingList_.removeOne(name);
|
||||
|
||||
Q_EMIT currentConvComposingListChanged();
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
MessagesAdapter::isLocalImage(const QString& msg)
|
||||
{
|
||||
|
||||
@@ -60,6 +60,7 @@ class MessagesAdapter final : public QmlAdapterBase
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_RO_PROPERTY(QVariant, messageListModel)
|
||||
QML_RO_PROPERTY(QList<QString>, currentConvComposingList)
|
||||
|
||||
public:
|
||||
explicit MessagesAdapter(AppSettingsManager* settingsManager,
|
||||
@@ -121,6 +122,9 @@ private Q_SLOTS:
|
||||
void onPreviewInfoReady(QString messageIndex, QVariantMap urlInMessage);
|
||||
void onConversationMessagesLoaded(uint32_t requestId, const QString& convId);
|
||||
void onMessageLinkified(const QString& messageId, const QString& linkified);
|
||||
void onComposingStatusChanged(const QString& convId,
|
||||
const QString& contactUri,
|
||||
bool isComposing);
|
||||
|
||||
private:
|
||||
AppSettingsManager* settingsManager_;
|
||||
|
||||
Reference in New Issue
Block a user