mirror of
https://gitlab.linphone.org/BC/public/linphone-desktop.git
synced 2026-02-07 15:08:24 +00:00
feat(Chat): supports messages composing
This commit is contained in:
parent
36b349eda8
commit
0829dd92fa
9 changed files with 118 additions and 13 deletions
|
|
@ -414,6 +414,10 @@
|
|||
<translation>Unable to send file.
|
||||
Server url not configured.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>isComposing</source>
|
||||
<translation>%1 is typing...</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Cli</name>
|
||||
|
|
|
|||
|
|
@ -414,6 +414,10 @@
|
|||
<translation>Impossible d'envoyer un fichier.
|
||||
Url du serveur non configurée.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>isComposing</source>
|
||||
<translation>%1 est en train d'écrire...</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Cli</name>
|
||||
|
|
|
|||
|
|
@ -199,6 +199,20 @@ ChatModel::ChatModel (QObject *parent) : QAbstractListModel(parent) {
|
|||
|
||||
QObject::connect(mCoreHandlers.get(), &CoreHandlers::messageReceived, this, &ChatModel::handleMessageReceived);
|
||||
QObject::connect(mCoreHandlers.get(), &CoreHandlers::callStateChanged, this, &ChatModel::handleCallStateChanged);
|
||||
|
||||
// Deal with remote composing.
|
||||
QTimer *timer = new QTimer(this);
|
||||
timer->setInterval(500);
|
||||
|
||||
QObject::connect(timer, &QTimer::timeout, this, [this] {
|
||||
bool isRemoteComposing = mChatRoom->isRemoteComposing();
|
||||
if (isRemoteComposing != mIsRemoteComposing) {
|
||||
mIsRemoteComposing = isRemoteComposing;
|
||||
emit isRemoteComposingChanged(mIsRemoteComposing);
|
||||
}
|
||||
});
|
||||
|
||||
timer->start();
|
||||
}
|
||||
|
||||
ChatModel::~ChatModel () {
|
||||
|
|
@ -259,7 +273,7 @@ bool ChatModel::removeRows (int row, int count, const QModelIndex &parent) {
|
|||
|
||||
QString ChatModel::getSipAddress () const {
|
||||
if (!mChatRoom)
|
||||
return "";
|
||||
return QString("");
|
||||
|
||||
return ::Utils::coreStringToAppString(
|
||||
mChatRoom->getPeerAddress()->asStringUriOnly()
|
||||
|
|
@ -305,6 +319,10 @@ void ChatModel::setSipAddress (const QString &sipAddress) {
|
|||
emit sipAddressChanged(sipAddress);
|
||||
}
|
||||
|
||||
bool ChatModel::getIsRemoteComposing () const {
|
||||
return mIsRemoteComposing;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
void ChatModel::removeEntry (int id) {
|
||||
|
|
@ -473,6 +491,10 @@ bool ChatModel::fileWasDownloaded (int id) {
|
|||
return entry.second && ::fileWasDownloaded(static_pointer_cast<linphone::ChatMessage>(entry.second));
|
||||
}
|
||||
|
||||
void ChatModel::compose () {
|
||||
return mChatRoom->compose();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
const ChatModel::ChatEntryData ChatModel::getFileMessageEntry (int id) {
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ class ChatModel : public QAbstractListModel {
|
|||
Q_OBJECT;
|
||||
|
||||
Q_PROPERTY(QString sipAddress READ getSipAddress WRITE setSipAddress NOTIFY sipAddressChanged);
|
||||
Q_PROPERTY(bool isRemoteComposing READ getIsRemoteComposing NOTIFY isRemoteComposingChanged);
|
||||
|
||||
public:
|
||||
enum Roles {
|
||||
|
|
@ -88,6 +89,8 @@ public:
|
|||
QString getSipAddress () const;
|
||||
void setSipAddress (const QString &sipAddress);
|
||||
|
||||
bool getIsRemoteComposing () const;
|
||||
|
||||
void removeEntry (int id);
|
||||
void removeAllEntries ();
|
||||
|
||||
|
|
@ -105,8 +108,12 @@ public:
|
|||
|
||||
bool fileWasDownloaded (int id);
|
||||
|
||||
void compose ();
|
||||
|
||||
signals:
|
||||
void sipAddressChanged (const QString &sipAddress);
|
||||
bool isRemoteComposingChanged (bool status);
|
||||
|
||||
void allEntriesRemoved ();
|
||||
|
||||
void messageSent (const std::shared_ptr<linphone::ChatMessage> &message);
|
||||
|
|
@ -133,6 +140,8 @@ private:
|
|||
void handleCallStateChanged (const std::shared_ptr<linphone::Call> &call, linphone::CallState state);
|
||||
void handleMessageReceived (const std::shared_ptr<linphone::ChatMessage> &message);
|
||||
|
||||
bool mIsRemoteComposing = false;
|
||||
|
||||
QList<ChatEntryData> mEntries;
|
||||
std::shared_ptr<linphone::ChatRoom> mChatRoom;
|
||||
|
||||
|
|
|
|||
|
|
@ -69,6 +69,14 @@ ChatProxyModel::ChatProxyModel (QObject *parent) : QSortFilterProxyModel(parent)
|
|||
|
||||
ChatModel *chat = static_cast<ChatModel *>(mChatModelFilter->sourceModel());
|
||||
|
||||
QObject::connect(chat, &ChatModel::sipAddressChanged, this, [this](const QString &sipAddress) {
|
||||
emit sipAddressChanged(sipAddress);
|
||||
});
|
||||
|
||||
QObject::connect(chat, &ChatModel::isRemoteComposingChanged, this, [this](bool status) {
|
||||
emit isRemoteComposingChanged(status);
|
||||
});
|
||||
|
||||
QObject::connect(chat, &ChatModel::messageReceived, this, [this](const shared_ptr<linphone::ChatMessage> &) {
|
||||
mMaxDisplayedEntries++;
|
||||
});
|
||||
|
|
@ -88,13 +96,21 @@ ChatProxyModel::ChatProxyModel (QObject *parent) : QSortFilterProxyModel(parent)
|
|||
); \
|
||||
}
|
||||
|
||||
#define CREATE_PARENT_MODEL_FUNCTION_PARAM(METHOD, ARG_TYPE) \
|
||||
#define CREATE_PARENT_MODEL_FUNCTION(METHOD) \
|
||||
void ChatProxyModel::METHOD() { \
|
||||
static_cast<ChatModel *>(mChatModelFilter->sourceModel())->METHOD(); \
|
||||
}
|
||||
|
||||
#define CREATE_PARENT_MODEL_FUNCTION_WITH_PARAM(METHOD, ARG_TYPE) \
|
||||
void ChatProxyModel::METHOD(ARG_TYPE value) { \
|
||||
static_cast<ChatModel *>(mChatModelFilter->sourceModel())->METHOD(value); \
|
||||
}
|
||||
|
||||
CREATE_PARENT_MODEL_FUNCTION_PARAM(sendFileMessage, const QString &);
|
||||
CREATE_PARENT_MODEL_FUNCTION_PARAM(sendMessage, const QString &);
|
||||
CREATE_PARENT_MODEL_FUNCTION(compose);
|
||||
CREATE_PARENT_MODEL_FUNCTION(removeAllEntries);
|
||||
|
||||
CREATE_PARENT_MODEL_FUNCTION_WITH_PARAM(sendFileMessage, const QString &);
|
||||
CREATE_PARENT_MODEL_FUNCTION_WITH_PARAM(sendMessage, const QString &);
|
||||
|
||||
CREATE_PARENT_MODEL_FUNCTION_WITH_ID(downloadFile);
|
||||
CREATE_PARENT_MODEL_FUNCTION_WITH_ID(openFile);
|
||||
|
|
@ -103,14 +119,11 @@ CREATE_PARENT_MODEL_FUNCTION_WITH_ID(removeEntry);
|
|||
CREATE_PARENT_MODEL_FUNCTION_WITH_ID(resendMessage);
|
||||
|
||||
#undef CREATE_PARENT_MODEL_FUNCTION
|
||||
#undef CREATE_PARENT_MODEL_FUNCTION_WITH_PARAM
|
||||
#undef CREATE_PARENT_MODEL_FUNCTION_WITH_ID
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
void ChatProxyModel::removeAllEntries () {
|
||||
static_cast<ChatModel *>(mChatModelFilter->sourceModel())->removeAllEntries();
|
||||
}
|
||||
|
||||
QString ChatProxyModel::getSipAddress () const {
|
||||
return static_cast<ChatModel *>(mChatModelFilter->sourceModel())->getSipAddress();
|
||||
}
|
||||
|
|
@ -121,6 +134,10 @@ void ChatProxyModel::setSipAddress (const QString &sipAddress) {
|
|||
);
|
||||
}
|
||||
|
||||
bool ChatProxyModel::getIsRemoteComposing () const {
|
||||
return static_cast<ChatModel *>(mChatModelFilter->sourceModel())->getIsRemoteComposing();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
void ChatProxyModel::loadMoreEntries () {
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ class ChatProxyModel : public QSortFilterProxyModel {
|
|||
Q_OBJECT;
|
||||
|
||||
Q_PROPERTY(QString sipAddress READ getSipAddress WRITE setSipAddress NOTIFY sipAddressChanged);
|
||||
Q_PROPERTY(bool isRemoteComposing READ getIsRemoteComposing NOTIFY isRemoteComposingChanged);
|
||||
|
||||
public:
|
||||
ChatProxyModel (QObject *parent = Q_NULLPTR);
|
||||
|
|
@ -54,8 +55,12 @@ public:
|
|||
Q_INVOKABLE void openFile (int id);
|
||||
Q_INVOKABLE void openFileDirectory (int id);
|
||||
|
||||
Q_INVOKABLE void compose ();
|
||||
|
||||
signals:
|
||||
void sipAddressChanged (const QString &sipAddress);
|
||||
bool isRemoteComposingChanged (bool status);
|
||||
|
||||
void moreEntriesLoaded (int n);
|
||||
|
||||
void entryTypeFilterChanged (ChatModel::EntryType type);
|
||||
|
|
@ -67,6 +72,8 @@ private:
|
|||
QString getSipAddress () const;
|
||||
void setSipAddress (const QString &sipAddress);
|
||||
|
||||
bool getIsRemoteComposing () const;
|
||||
|
||||
ChatModelFilter *mChatModelFilter;
|
||||
int mMaxDisplayedEntries = ENTRIES_CHUNK_SIZE;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,12 @@
|
|||
// `Chat.qml` Logic.
|
||||
// =============================================================================
|
||||
|
||||
.import Linphone 1.0 as Linphone
|
||||
|
||||
.import 'qrc:/ui/scripts/LinphoneUtils/linphone-utils.js' as LinphoneUtils
|
||||
|
||||
// =============================================================================
|
||||
|
||||
function initView () {
|
||||
chat.tryToLoadMoreEntries = false
|
||||
chat.bindToEnd = true
|
||||
|
|
@ -11,7 +17,7 @@ function loadMoreEntries () {
|
|||
if (chat.atYBeginning && !chat.tryToLoadMoreEntries) {
|
||||
chat.tryToLoadMoreEntries = true
|
||||
chat.positionViewAtBeginning()
|
||||
proxyModel.loadMoreEntries()
|
||||
container.proxyModel.loadMoreEntries()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -20,16 +26,28 @@ function getComponentFromEntry (chatEntry) {
|
|||
return 'FileMessage.qml'
|
||||
}
|
||||
|
||||
if (chatEntry.type === ChatModel.CallEntry) {
|
||||
if (chatEntry.type === Linphone.ChatModel.CallEntry) {
|
||||
return 'Event.qml'
|
||||
}
|
||||
|
||||
return chatEntry.isOutgoing ? 'OutgoingMessage.qml' : 'IncomingMessage.qml'
|
||||
}
|
||||
|
||||
function getIsComposingMessage () {
|
||||
if (!container.proxyModel.isRemoteComposing) {
|
||||
return ''
|
||||
}
|
||||
|
||||
var sipAddressObserver = chat.sipAddressObserver
|
||||
return qsTr('isComposing').replace(
|
||||
'%1',
|
||||
LinphoneUtils.getContactUsername(sipAddressObserver.contact || sipAddressObserver.sipAddress)
|
||||
)
|
||||
}
|
||||
|
||||
function handleFilesDropped (files) {
|
||||
chat.bindToEnd = true
|
||||
files.forEach(proxyModel.sendFileMessage)
|
||||
files.forEach(container.proxyModel.sendFileMessage)
|
||||
}
|
||||
|
||||
function handleMoreEntriesLoaded (n) {
|
||||
|
|
@ -47,8 +65,12 @@ function handleMovementStarted () {
|
|||
chat.bindToEnd = false
|
||||
}
|
||||
|
||||
function handleTextChanged () {
|
||||
container.proxyModel.compose()
|
||||
}
|
||||
|
||||
function sendMessage (text) {
|
||||
textArea.text = ''
|
||||
chat.bindToEnd = true
|
||||
proxyModel.sendMessage(text)
|
||||
container.proxyModel.sendMessage(text)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import 'Chat.js' as Logic
|
|||
// =============================================================================
|
||||
|
||||
Rectangle {
|
||||
id: container
|
||||
|
||||
property alias proxyModel: chat.model
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -196,6 +198,8 @@ Rectangle {
|
|||
Layout.preferredHeight: ChatStyle.sendArea.height + ChatStyle.sendArea.border.width
|
||||
|
||||
borderColor: ChatStyle.sendArea.border.color
|
||||
|
||||
bottomWidth: ChatStyle.sendArea.border.width
|
||||
topWidth: ChatStyle.sendArea.border.width
|
||||
|
||||
DroppableTextArea {
|
||||
|
|
@ -208,9 +212,20 @@ Rectangle {
|
|||
placeholderText: qsTr('newMessagePlaceholder')
|
||||
|
||||
onDropped: Logic.handleFilesDropped(files)
|
||||
onTextChanged: Logic.handleTextChanged(text)
|
||||
onValidText: Logic.sendMessage(text)
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
|
||||
color: ChatStyle.composingText.color
|
||||
font.pointSize: ChatStyle.composingText.pointSize
|
||||
leftPadding: ChatStyle.composingText.leftPadding
|
||||
|
||||
text: Logic.getIsComposingMessage()
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -33,6 +33,12 @@ QtObject {
|
|||
}
|
||||
}
|
||||
|
||||
property QtObject composingText: QtObject {
|
||||
property color color: Colors.b
|
||||
property int pointSize: Units.dp * 9
|
||||
property int leftPadding: 6
|
||||
}
|
||||
|
||||
property QtObject entry: QtObject {
|
||||
property int bottomMargin: 10
|
||||
property int deleteIconSize: 17
|
||||
|
|
@ -101,7 +107,6 @@ QtObject {
|
|||
|
||||
property QtObject images: QtObject {
|
||||
property int height: 48
|
||||
// `width` can be used.
|
||||
}
|
||||
|
||||
property QtObject incoming: QtObject {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue