Commit 0829dd92 authored by Wescoeur's avatar Wescoeur

feat(Chat): supports messages composing

parent 36b349ed
......@@ -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&apos;envoyer un fichier.
Url du serveur non configurée.</translation>
</message>
<message>
<source>isComposing</source>
<translation>%1 est en train d&apos;é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 {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment