diff --git a/tests/assets/images/chat_amount.svg b/tests/assets/images/chat_amount.svg
new file mode 100644
index 000000000..ff60f7d77
--- /dev/null
+++ b/tests/assets/images/chat_amount.svg
@@ -0,0 +1,18 @@
+
+
diff --git a/tests/assets/images/chat_count.svg b/tests/assets/images/chat_count.svg
new file mode 100644
index 000000000..4045488a6
--- /dev/null
+++ b/tests/assets/images/chat_count.svg
@@ -0,0 +1,14 @@
+
+
diff --git a/tests/resources.qrc b/tests/resources.qrc
index 1e43911b4..5b7e9f2ce 100644
--- a/tests/resources.qrc
+++ b/tests/resources.qrc
@@ -24,6 +24,8 @@
assets/images/camera_on_hovered.svg
assets/images/camera_on_normal.svg
assets/images/camera_on_pressed.svg
+ assets/images/chat_amount.svg
+ assets/images/chat_count.svg
assets/images/chat_error.svg
assets/images/chat_hovered.svg
assets/images/chat_normal.svg
@@ -208,6 +210,7 @@
ui/modules/Linphone/Contact/Avatar.qml
ui/modules/Linphone/Contact/ContactDescription.qml
ui/modules/Linphone/Contact/Contact.qml
+ ui/modules/Linphone/Contact/MessagesCounter.qml
ui/modules/Linphone/Notifications/CallNotification.qml
ui/modules/Linphone/Notifications/Notification.qml
ui/modules/Linphone/Notifications/ReceivedMessageNotification.qml
@@ -220,6 +223,7 @@
ui/modules/Linphone/Styles/Contact/AvatarStyle.qml
ui/modules/Linphone/Styles/Contact/ContactDescriptionStyle.qml
ui/modules/Linphone/Styles/Contact/ContactStyle.qml
+ ui/modules/Linphone/Styles/Contact/MessagesCounterStyle.qml
ui/modules/Linphone/Styles/NotificationStyle.qml
ui/modules/Linphone/Styles/Presence/PresenceStringStyle.qml
ui/modules/Linphone/Styles/qmldir
diff --git a/tests/src/components/chat/ChatModel.cpp b/tests/src/components/chat/ChatModel.cpp
index fda7dba23..f605ca3c3 100644
--- a/tests/src/components/chat/ChatModel.cpp
+++ b/tests/src/components/chat/ChatModel.cpp
@@ -78,11 +78,11 @@ ChatModel::ChatModel (QObject *parent) : QAbstractListModel(parent) {
CoreManager::getInstance()->getSipAddressesModel()->connectToChatModel(this);
QObject::connect(
- &(*m_core_handlers), &CoreHandlers::receivedMessage,
+ &(*m_core_handlers), &CoreHandlers::messageReceived,
this, [this](const shared_ptr &message) {
if (m_chat_room == message->getChatRoom()) {
insertMessageAtEnd(message);
- m_chat_room->markAsRead();
+ resetMessagesCount();
emit messageReceived(message);
}
@@ -165,10 +165,11 @@ void ChatModel::setSipAddress (const QString &sip_address) {
m_entries.clear();
shared_ptr core = CoreManager::getInstance()->getCore();
- string std_sip_address = ::Utils::qStringToLinphoneString(sip_address);
- m_chat_room = core->getChatRoomFromUri(std_sip_address);
- m_chat_room->markAsRead();
+ m_chat_room = core->getChatRoomFromUri(::Utils::qStringToLinphoneString(sip_address));
+
+ if (m_chat_room->getUnreadMessagesCount() > 0)
+ resetMessagesCount();
// Get messages.
for (auto &message : m_chat_room->getHistory(0)) {
@@ -336,3 +337,8 @@ void ChatModel::insertMessageAtEnd (const shared_ptr &mes
endInsertRows();
}
+
+void ChatModel::resetMessagesCount () {
+ m_chat_room->markAsRead();
+ emit messagesCountReset();
+}
diff --git a/tests/src/components/chat/ChatModel.hpp b/tests/src/components/chat/ChatModel.hpp
index 888db62c4..0ba1d300f 100644
--- a/tests/src/components/chat/ChatModel.hpp
+++ b/tests/src/components/chat/ChatModel.hpp
@@ -75,6 +75,8 @@ signals:
void messageSent (const std::shared_ptr &message);
void messageReceived (const std::shared_ptr &message);
+ void messagesCountReset ();
+
private:
void fillMessageEntry (
QVariantMap &dest,
@@ -95,6 +97,8 @@ private:
void insertMessageAtEnd (const std::shared_ptr &message);
+ void resetMessagesCount ();
+
QList m_entries;
std::shared_ptr m_chat_room;
diff --git a/tests/src/components/chat/ChatProxyModel.cpp b/tests/src/components/chat/ChatProxyModel.cpp
index 80567244d..257863c3e 100644
--- a/tests/src/components/chat/ChatProxyModel.cpp
+++ b/tests/src/components/chat/ChatProxyModel.cpp
@@ -2,33 +2,48 @@
// =============================================================================
-ChatModelFilter::ChatModelFilter (QObject *parent) : QSortFilterProxyModel(parent) {
- setSourceModel(&m_chat_model);
-}
+// Fetch the L last filtered chat entries.
+class ChatProxyModel::ChatModelFilter : public QSortFilterProxyModel {
+public:
+ ChatModelFilter (QObject *parent) : QSortFilterProxyModel(parent) {
+ setSourceModel(&m_chat_model);
+ }
-bool ChatModelFilter::filterAcceptsRow (int source_row, const QModelIndex &) const {
- if (m_entry_type_filter == ChatModel::EntryType::GenericEntry)
- return true;
+ ChatModel::EntryType getEntryTypeFilter () {
+ return m_entry_type_filter;
+ }
- QModelIndex index = sourceModel()->index(source_row, 0, QModelIndex());
- const QVariantMap &data = index.data().toMap();
+ void setEntryTypeFilter (ChatModel::EntryType type) {
+ m_entry_type_filter = type;
+ invalidate();
+ }
- return data["type"].toInt() == m_entry_type_filter;
-}
+protected:
+ bool filterAcceptsRow (int source_row, const QModelIndex &) const override {
+ if (m_entry_type_filter == ChatModel::EntryType::GenericEntry)
+ return true;
-void ChatModelFilter::setEntryTypeFilter (ChatModel::EntryType type) {
- m_entry_type_filter = type;
- invalidate();
-}
+ QModelIndex index = sourceModel()->index(source_row, 0, QModelIndex());
+ const QVariantMap &data = index.data().toMap();
+
+ return data["type"].toInt() == m_entry_type_filter;
+ }
+
+private:
+ ChatModel m_chat_model;
+ ChatModel::EntryType m_entry_type_filter = ChatModel::EntryType::GenericEntry;
+};
// =============================================================================
const unsigned int ChatProxyModel::ENTRIES_CHUNK_SIZE = 50;
ChatProxyModel::ChatProxyModel (QObject *parent) : QSortFilterProxyModel(parent) {
- setSourceModel(&m_chat_model_filter);
+ m_chat_model_filter = new ChatModelFilter(this);
- ChatModel *chat = static_cast(m_chat_model_filter.sourceModel());
+ setSourceModel(m_chat_model_filter);
+
+ ChatModel *chat = static_cast(m_chat_model_filter->sourceModel());
QObject::connect(
chat, &ChatModel::messageReceived, this, [this](const shared_ptr &) {
@@ -45,7 +60,7 @@ ChatProxyModel::ChatProxyModel (QObject *parent) : QSortFilterProxyModel(parent)
void ChatProxyModel::loadMoreEntries () {
int count = rowCount();
- int parent_count = m_chat_model_filter.rowCount();
+ int parent_count = m_chat_model_filter->rowCount();
if (count < parent_count) {
// Do not increase `m_n_max_displayed_entries` if it's not necessary...
@@ -62,19 +77,37 @@ void ChatProxyModel::loadMoreEntries () {
}
void ChatProxyModel::setEntryTypeFilter (ChatModel::EntryType type) {
- if (m_chat_model_filter.getEntryTypeFilter() != type) {
- m_chat_model_filter.setEntryTypeFilter(type);
+ if (m_chat_model_filter->getEntryTypeFilter() != type) {
+ m_chat_model_filter->setEntryTypeFilter(type);
emit entryTypeFilterChanged(type);
}
}
void ChatProxyModel::removeEntry (int id) {
QModelIndex source_index = mapToSource(index(id, 0));
- static_cast(m_chat_model_filter.sourceModel())->removeEntry(
- m_chat_model_filter.mapToSource(source_index).row()
+ static_cast(m_chat_model_filter->sourceModel())->removeEntry(
+ m_chat_model_filter->mapToSource(source_index).row()
);
}
-bool ChatProxyModel::filterAcceptsRow (int source_row, const QModelIndex &) const {
- return m_chat_model_filter.rowCount() - source_row <= m_n_max_displayed_entries;
+void ChatProxyModel::removeAllEntries () {
+ static_cast(m_chat_model_filter->sourceModel())->removeAllEntries();
+}
+
+void ChatProxyModel::sendMessage (const QString &message) {
+ static_cast(m_chat_model_filter->sourceModel())->sendMessage(message);
+}
+
+bool ChatProxyModel::filterAcceptsRow (int source_row, const QModelIndex &) const {
+ return m_chat_model_filter->rowCount() - source_row <= m_n_max_displayed_entries;
+}
+
+QString ChatProxyModel::getSipAddress () const {
+ return static_cast(m_chat_model_filter->sourceModel())->getSipAddress();
+}
+
+void ChatProxyModel::setSipAddress (const QString &sip_address) {
+ static_cast(m_chat_model_filter->sourceModel())->setSipAddress(
+ sip_address
+ );
}
diff --git a/tests/src/components/chat/ChatProxyModel.hpp b/tests/src/components/chat/ChatProxyModel.hpp
index 4b6f5ece0..dbee58dd0 100644
--- a/tests/src/components/chat/ChatProxyModel.hpp
+++ b/tests/src/components/chat/ChatProxyModel.hpp
@@ -5,35 +5,11 @@
#include "ChatModel.hpp"
-// =============================================================================
-// Fetch the L last filtered chat entries.
-// =============================================================================
-
-// Cannot be used as a nested class by Qt and `Q_OBJECT` macro
-// must be used in header c++ file.
-class ChatModelFilter : public QSortFilterProxyModel {
- Q_OBJECT;
-
-public:
- ChatModelFilter (QObject *parent = Q_NULLPTR);
-
- ChatModel::EntryType getEntryTypeFilter () {
- return m_entry_type_filter;
- }
-
- void setEntryTypeFilter (ChatModel::EntryType type);
-
-protected:
- bool filterAcceptsRow (int source_row, const QModelIndex &parent) const override;
-
-private:
- ChatModel m_chat_model;
- ChatModel::EntryType m_entry_type_filter = ChatModel::EntryType::GenericEntry;
-};
-
// =============================================================================
class ChatProxyModel : public QSortFilterProxyModel {
+ class ChatModelFilter;
+
Q_OBJECT;
Q_PROPERTY(
@@ -50,16 +26,12 @@ public:
Q_INVOKABLE void setEntryTypeFilter (ChatModel::EntryType type);
Q_INVOKABLE void removeEntry (int id);
- Q_INVOKABLE void removeAllEntries () {
- static_cast(m_chat_model_filter.sourceModel())->removeAllEntries();
- }
+ Q_INVOKABLE void removeAllEntries ();
- Q_INVOKABLE void sendMessage (const QString &message) {
- static_cast(m_chat_model_filter.sourceModel())->sendMessage(message);
- }
+ Q_INVOKABLE void sendMessage (const QString &message);
signals:
- void sipAddressChanged (const QString &sipAddress);
+ void sipAddressChanged (const QString &sip_address);
void moreEntriesLoaded (int n);
void entryTypeFilterChanged (ChatModel::EntryType type);
@@ -68,17 +40,10 @@ protected:
bool filterAcceptsRow (int source_row, const QModelIndex &source_parent) const override;
private:
- QString getSipAddress () const {
- return static_cast(m_chat_model_filter.sourceModel())->getSipAddress();
- }
+ QString getSipAddress () const;
+ void setSipAddress (const QString &sip_address);
- void setSipAddress (const QString &sip_address) {
- static_cast(m_chat_model_filter.sourceModel())->setSipAddress(
- sip_address
- );
- }
-
- ChatModelFilter m_chat_model_filter;
+ ChatModelFilter *m_chat_model_filter;
int m_n_max_displayed_entries = ENTRIES_CHUNK_SIZE;
static const unsigned int ENTRIES_CHUNK_SIZE;
diff --git a/tests/src/components/core/CoreHandlers.cpp b/tests/src/components/core/CoreHandlers.cpp
index 33e19df5c..d7e8a13dc 100644
--- a/tests/src/components/core/CoreHandlers.cpp
+++ b/tests/src/components/core/CoreHandlers.cpp
@@ -31,7 +31,7 @@ void CoreHandlers::onMessageReceived (
const shared_ptr &room,
const shared_ptr &message
) {
- emit receivedMessage(message);
+ emit messageReceived(message);
const App *app = App::getInstance();
if (!app->hasFocus())
diff --git a/tests/src/components/core/CoreHandlers.hpp b/tests/src/components/core/CoreHandlers.hpp
index 916faa0ba..d1bb63cde 100644
--- a/tests/src/components/core/CoreHandlers.hpp
+++ b/tests/src/components/core/CoreHandlers.hpp
@@ -12,7 +12,7 @@ class CoreHandlers :
Q_OBJECT;
signals:
- void receivedMessage (const std::shared_ptr &message);
+ void messageReceived (const std::shared_ptr &message);
private:
void onAuthenticationRequested (
diff --git a/tests/src/components/sip-addresses/SipAddressesModel.cpp b/tests/src/components/sip-addresses/SipAddressesModel.cpp
index d9cdd87de..6474adcac 100644
--- a/tests/src/components/sip-addresses/SipAddressesModel.cpp
+++ b/tests/src/components/sip-addresses/SipAddressesModel.cpp
@@ -21,7 +21,7 @@ SipAddressesModel::SipAddressesModel (QObject *parent) : QAbstractListModel(pare
m_handlers = CoreManager::getInstance()->getHandlers();
QObject::connect(
- &(*m_handlers), &CoreHandlers::receivedMessage,
+ &(*m_handlers), &CoreHandlers::messageReceived,
this, [this](const std::shared_ptr &message) {
const QString &sip_address = ::Utils::linphoneStringToQString(message->getFromAddress()->asStringUriOnly());
addOrUpdateSipAddress(sip_address, nullptr, message);
@@ -104,13 +104,33 @@ void SipAddressesModel::connectToChatModel (ChatModel *chat_model) {
}
);
+ for (auto &signal : { &ChatModel::messageSent, &ChatModel::messageReceived }) {
+ QObject::connect(
+ chat_model, signal,
+ this, [this](const std::shared_ptr &message) {
+ addOrUpdateSipAddress(
+ ::Utils::linphoneStringToQString(message->getToAddress()->asStringUriOnly()), nullptr, message
+ );
+ }
+ );
+ }
+
QObject::connect(
- chat_model, &ChatModel::messageSent,
- this, [this](const std::shared_ptr &message) {
- addOrUpdateSipAddress(
- ::Utils::linphoneStringToQString(message->getToAddress()->asStringUriOnly()), nullptr, message
- );
- });
+ chat_model, &ChatModel::messagesCountReset, this, [this, chat_model]() {
+ const QString &sip_address = chat_model->getSipAddress();
+
+ auto it = m_sip_addresses.find(sip_address);
+ if (it != m_sip_addresses.end()) {
+ (*it)["unreadMessagesCount"] = 0;
+
+ int row = m_refs.indexOf(&(*it));
+ Q_ASSERT(row != -1);
+ emit dataChanged(index(row, 0), index(row, 0));
+
+ return;
+ }
+ }
+ );
}
// -----------------------------------------------------------------------------
diff --git a/tests/ui/modules/Linphone/Contact/Contact.qml b/tests/ui/modules/Linphone/Contact/Contact.qml
index 7d653b032..ac5be8627 100644
--- a/tests/ui/modules/Linphone/Contact/Contact.qml
+++ b/tests/ui/modules/Linphone/Contact/Contact.qml
@@ -1,11 +1,9 @@
import QtQuick 2.7
import QtQuick.Layouts 1.3
-import Common 1.0
import Linphone 1.0
import LinphoneUtils 1.0
import Linphone.Styles 1.0
-import Utils 1.0
// =============================================================================
@@ -14,10 +12,11 @@ Rectangle {
// ---------------------------------------------------------------------------
- property alias actions: actionBar.data
property alias sipAddressColor: description.sipAddressColor
property alias usernameColor: description.usernameColor
+ property bool displayUnreadMessagesCount: false
+
property var entry
property var _contact: entry.contact
@@ -33,7 +32,7 @@ Rectangle {
leftMargin: ContactStyle.leftMargin
rightMargin: ContactStyle.rightMargin
}
- spacing: ContactStyle.spacing
+ spacing: 0
Avatar {
id: avatar
@@ -50,14 +49,17 @@ Rectangle {
Layout.fillHeight: true
Layout.fillWidth: true
+ Layout.leftMargin: ContactStyle.spacing
+
sipAddress: entry.sipAddress
username: avatar.username
}
- ActionBar {
- id: actionBar
+ MessagesCounter {
+ Layout.alignment: Qt.AlignTop
- Layout.preferredHeight: ContactStyle.contentHeight
+ count: entry.unreadMessagesCount || 0
+ visible: displayUnreadMessagesCount && entry.unreadMessagesCount > 0
}
}
}
diff --git a/tests/ui/modules/Linphone/Contact/MessagesCounter.qml b/tests/ui/modules/Linphone/Contact/MessagesCounter.qml
new file mode 100644
index 000000000..eb019ed36
--- /dev/null
+++ b/tests/ui/modules/Linphone/Contact/MessagesCounter.qml
@@ -0,0 +1,40 @@
+import QtQuick 2.7
+import QtQuick.Layouts 1.3
+
+import Common 1.0
+import Linphone.Styles 1.0
+
+// =============================================================================
+
+Item {
+ id: messagesCounter
+
+ property int count
+
+ implicitHeight: counterIcon.height + MessagesCounterStyle.verticalMargins * 2
+ implicitWidth: counterIcon.width + MessagesCounterStyle.horizontalMargins * 2
+
+ Icon {
+ id: counterIcon
+
+ anchors.centerIn: parent
+
+ icon: 'chat_count'
+ iconSize: MessagesCounterStyle.iconSize.message
+
+ Icon {
+ anchors.horizontalCenter: parent.right
+ anchors.verticalCenter: parent.bottom
+
+ icon: 'chat_amount'
+ iconSize: MessagesCounterStyle.iconSize.amount
+
+ Text {
+ anchors.centerIn: parent
+ color: MessagesCounterStyle.text.color
+ font.pointSize: MessagesCounterStyle.text.fontSize
+ text: messagesCounter.count
+ }
+ }
+ }
+}
diff --git a/tests/ui/modules/Linphone/Styles/Contact/MessagesCounterStyle.qml b/tests/ui/modules/Linphone/Styles/Contact/MessagesCounterStyle.qml
new file mode 100644
index 000000000..ff0763df4
--- /dev/null
+++ b/tests/ui/modules/Linphone/Styles/Contact/MessagesCounterStyle.qml
@@ -0,0 +1,21 @@
+pragma Singleton
+import QtQuick 2.7
+
+import Common 1.0
+
+// =============================================================================
+
+QtObject {
+ property int horizontalMargins: 0
+ property int verticalMargins: 10
+
+ property QtObject iconSize: QtObject {
+ property int amount: 16
+ property int message: 18
+ }
+
+ property QtObject text: QtObject {
+ property color color: Colors.k
+ property int fontSize: 7
+ }
+}
diff --git a/tests/ui/modules/Linphone/Styles/qmldir b/tests/ui/modules/Linphone/Styles/qmldir
index 5ae24cc2f..9b23e21cf 100644
--- a/tests/ui/modules/Linphone/Styles/qmldir
+++ b/tests/ui/modules/Linphone/Styles/qmldir
@@ -11,6 +11,7 @@ singleton ChatStyle 1.0 ChatStyle.qml
singleton AvatarStyle 1.0 Contact/AvatarStyle.qml
singleton ContactDescriptionStyle 1.0 Contact/ContactDescriptionStyle.qml
singleton ContactStyle 1.0 Contact/ContactStyle.qml
+singleton MessagesCounterStyle 1.0 Contact/MessagesCounterStyle.qml
singleton NotificationStyle 1.0 NotificationStyle.qml
diff --git a/tests/ui/modules/Linphone/Timeline.qml b/tests/ui/modules/Linphone/Timeline.qml
index 79f3fdb9f..5cdf09fb5 100644
--- a/tests/ui/modules/Linphone/Timeline.qml
+++ b/tests/ui/modules/Linphone/Timeline.qml
@@ -113,6 +113,7 @@ ColumnLayout {
? TimelineStyle.contact.backgroundColor.a
: TimelineStyle.contact.backgroundColor.b
)
+ displayUnreadMessagesCount: view.currentIndex !== index
entry: $timelineEntry
sipAddressColor: view.currentIndex === index
? TimelineStyle.contact.sipAddress.color.selected