scroll to first unread message + mark as read

This commit is contained in:
Gaelle Braud 2025-05-07 11:03:28 +02:00
parent 73b83771be
commit 03576d48e8
21 changed files with 122 additions and 41 deletions

View file

@ -48,8 +48,7 @@ AccountCore::AccountCore(const std::shared_ptr<linphone::Account> &account) : QO
mPictureUri = Utils::coreStringToAppString(params->getPictureUri());
mRegistrationState = LinphoneEnums::fromLinphone(account->getState());
mIsDefaultAccount = CoreModel::getInstance()->getCore()->getDefaultAccount() == account;
// mUnreadNotifications = account->getUnreadChatMessageCount() + account->getMissedCallsCount(); // TODO
mUnreadNotifications = account->getMissedCallsCount();
mUnreadNotifications = account->getMissedCallsCount() + account->getUnreadChatMessageCount();
mDisplayName = Utils::coreStringToAppString(identityAddress->getDisplayName());
if (mDisplayName.isEmpty()) {
mDisplayName = ToolModel::getDisplayName(identityAddress);
@ -236,7 +235,6 @@ void AccountCore::setSelf(QSharedPointer<AccountCore> me) {
mAccountModelConnection->makeConnectToCore(&AccountCore::lRefreshNotifications, [this]() {
mAccountModelConnection->invokeToModel([this]() { mAccountModel->refreshUnreadNotifications(); });
});
mCoreModelConnection = SafeConnection<AccountCore, CoreModel>::create(me, CoreModel::getInstance());
mAccountModelConnection->makeConnectToCore(&AccountCore::unreadCallNotificationsChanged, [this]() {
mAccountModelConnection->invokeToModel([this]() { CoreModel::getInstance()->unreadNotificationsChanged(); });
});
@ -269,6 +267,10 @@ void AccountCore::setSelf(QSharedPointer<AccountCore> me) {
mAccountModelConnection->makeConnectToModel(&AccountModel::voicemailAddressChanged, [this](QString value) {
mAccountModelConnection->invokeToCore([this, value]() { setVoicemailAddress(value); });
});
mCoreModelConnection = SafeConnection<AccountCore, CoreModel>::create(me, CoreModel::getInstance());
mCoreModelConnection->makeConnectToModel(&CoreModel::messageReadInChatRoom,
[this] { mAccountModel->refreshUnreadNotifications(); });
}
void AccountCore::reset(const AccountCore &accountCore) {

View file

@ -203,6 +203,7 @@ signals:
void lSetPictureUri(QString pictureUri);
void lSetDefaultAccount();
void lResetMissedCalls();
void lResetUnreadMessages();
void lRefreshNotifications();
void lSetDisplayName(QString displayName);
void lSetDialPlan(QVariantMap internationalPrefix);

View file

@ -64,6 +64,9 @@ ChatCore::ChatCore(const std::shared_ptr<linphone::ChatRoom> &chatRoom) : QObjec
}
}
mUnreadMessagesCount = chatRoom->getUnreadMessagesCount();
connect(this, &ChatCore::unreadMessagesCountChanged, this, [this] {
if (mUnreadMessagesCount == 0) emit lMarkAsRead();
});
mChatModel = Utils::makeQObject_ptr<ChatModel>(chatRoom);
mChatModel->setSelf(mChatModel);
mLastMessageInHistory = mChatModel->getLastMessageInHistory();
@ -93,7 +96,6 @@ void ChatCore::setSelf(QSharedPointer<ChatCore> me) {
});
mChatModelConnection->makeConnectToModel(&ChatModel::historyDeleted, [this]() {
mChatModelConnection->invokeToCore([this]() {
qDebug() << log().arg("history deleted for chatRoom") << this;
clearMessagesList();
Utils::showInformationPopup(tr("Supprimé"), tr("L'historique des messages a été supprimé."), true);
});
@ -104,6 +106,12 @@ void ChatCore::setSelf(QSharedPointer<ChatCore> me) {
mChatModelConnection->invokeToCore([this, count] { setUnreadMessagesCount(count); });
});
});
mChatModelConnection->makeConnectToCore(&ChatCore::lUpdateLastUpdatedTime, [this]() {
mChatModelConnection->invokeToModel([this]() {
auto time = mChatModel->getLastUpdateTime();
mChatModelConnection->invokeToCore([this, time]() { setLastUpdatedTime(time); });
});
});
mChatModelConnection->makeConnectToCore(&ChatCore::lDelete, [this]() {
mChatModelConnection->invokeToModel([this]() { mChatModel->deleteChatRoom(); });
@ -117,6 +125,7 @@ void ChatCore::setSelf(QSharedPointer<ChatCore> me) {
if (mChatModel->getMonitor() != chatRoom) return;
emit lUpdateLastMessage();
emit lUpdateUnreadCount();
emit lUpdateLastUpdatedTime();
auto message = eventLog->getChatMessage();
qDebug() << "EVENT LOG RECEIVED IN CHATROOM" << mChatModel->getTitle();
if (message) {
@ -133,6 +142,7 @@ void ChatCore::setSelf(QSharedPointer<ChatCore> me) {
if (mChatModel->getMonitor() != chatRoom) return;
emit lUpdateLastMessage();
emit lUpdateUnreadCount();
emit lUpdateLastUpdatedTime();
qDebug() << "EVENT LOGS RECEIVED IN CHATROOM" << mChatModel->getTitle();
QList<QSharedPointer<ChatMessageCore>> list;
for (auto &m : chatMessages) {
@ -281,13 +291,13 @@ void ChatCore::appendMessagesToMessageList(QList<QSharedPointer<ChatMessageCore>
mChatMessageList.append(message);
++nbAdded;
}
if (nbAdded > 0) emit messageListChanged();
if (nbAdded > 0) emit messagesInserted(list);
}
void ChatCore::appendMessageToMessageList(QSharedPointer<ChatMessageCore> message) {
if (mChatMessageList.contains(message)) return;
mChatMessageList.append(message);
emit messageListChanged();
emit messagesInserted({message});
}
void ChatCore::removeMessagesFromMessageList(QList<QSharedPointer<ChatMessageCore>> list) {
@ -298,7 +308,7 @@ void ChatCore::removeMessagesFromMessageList(QList<QSharedPointer<ChatMessageCor
++nbRemoved;
}
}
if (nbRemoved > 0) emit messageListChanged();
if (nbRemoved > 0) emit messageRemoved();
}
void ChatCore::clearMessagesList() {

View file

@ -94,6 +94,8 @@ signals:
void peerAddressChanged(QString address);
void unreadMessagesCountChanged(int count);
void messageListChanged();
void messagesInserted(QList<QSharedPointer<ChatMessageCore>> list);
void messageRemoved();
void avatarUriChanged();
void deleted();
void composingUserChanged();
@ -104,6 +106,7 @@ signals:
void lMarkAsRead();
void lUpdateLastMessage();
void lUpdateUnreadCount();
void lUpdateLastUpdatedTime();
void lSendTextMessage(QString message);
void lCompose();

View file

@ -68,7 +68,9 @@ void ChatList::setSelf(QSharedPointer<ChatList> me) {
}
mModelConnection->invokeToCore([this, chats]() {
for (auto &chat : getSharedList<ChatCore>()) {
if (chat) disconnect(chat.get(), &ChatCore::deleted, this, nullptr);
if (chat) {
disconnect(chat.get(), &ChatCore::deleted, this, nullptr);
}
}
for (auto &chat : *chats) {
connect(chat.get(), &ChatCore::deleted, this, [this, chat] {
@ -77,6 +79,7 @@ void ChatList::setSelf(QSharedPointer<ChatList> me) {
// really has removed the item, then emit specific signal
emit chatRemoved(chat ? new ChatGui(chat) : nullptr);
});
connect(chat.get(), &ChatCore::unreadMessagesCountChanged, this, [this] { emit chatUpdated(); });
}
mustBeInMainThread(getClassName());
resetData<ChatCore>(*chats);

View file

@ -48,6 +48,7 @@ signals:
void filterChanged(QString filter);
void chatRemoved(ChatGui *chat);
void chatAdded();
void chatUpdated();
private:
QString mFilter;

View file

@ -44,7 +44,8 @@ void ChatProxy::setSourceModel(QAbstractItemModel *model) {
connect(this, &ChatProxy::filterTextChanged, newChatList,
[this, newChatList] { emit newChatList->filterChanged(getFilterText()); });
connect(newChatList, &ChatList::chatRemoved, this, &ChatProxy::chatRemoved);
connect(newChatList, &ChatList::chatAdded, this, [this] { invalidate(); });
// connect(newChatList, &ChatList::chatAdded, this, [this] { invalidate(); });
connect(newChatList, &ChatList::chatUpdated, this, [this] { invalidate(); });
}
auto firstList = new SortFilterList(model, Qt::AscendingOrder);
setSourceModels(firstList);

View file

@ -53,6 +53,7 @@ ChatMessageCore::ChatMessageCore(const std::shared_ptr<linphone::ChatMessage> &c
auto chatroom = chatmessage->getChatRoom();
mIsFromChatGroup = chatroom->hasCapability((int)linphone::ChatRoom::Capabilities::Conference) &&
!chatroom->hasCapability((int)linphone::ChatRoom::Capabilities::OneToOne);
mIsRead = chatmessage->isRead();
}
ChatMessageCore::~ChatMessageCore() {
@ -67,6 +68,10 @@ void ChatMessageCore::setSelf(QSharedPointer<ChatMessageCore> me) {
Utils::showInformationPopup(tr("Supprimé"), tr("Message supprimé"), true);
emit deleted();
});
mChatMessageModelConnection->makeConnectToCore(&ChatMessageCore::lMarkAsRead, [this] {
mChatMessageModelConnection->invokeToModel([this] { mChatMessageModel->markAsRead(); });
});
mChatMessageModelConnection->makeConnectToModel(&ChatMessageModel::messageRead, [this]() { setIsRead(true); });
}
QDateTime ChatMessageCore::getTimestamp() const {
@ -119,6 +124,17 @@ bool ChatMessageCore::isFromChatGroup() const {
return mIsFromChatGroup;
}
bool ChatMessageCore::isRead() const {
return mIsRead;
}
void ChatMessageCore::setIsRead(bool read) {
if (mIsRead != read) {
mIsRead = read;
emit isReadChanged(read);
}
}
std::shared_ptr<ChatMessageModel> ChatMessageCore::getModel() const {
return mChatMessageModel;
}

View file

@ -42,6 +42,7 @@ class ChatMessageCore : public QObject, public AbstractObject {
Q_PROPERTY(QString fromName READ getFromName CONSTANT)
Q_PROPERTY(bool isRemoteMessage READ isRemoteMessage CONSTANT)
Q_PROPERTY(bool isFromChatGroup READ isFromChatGroup CONSTANT)
Q_PROPERTY(bool isRead READ isRead WRITE setIsRead NOTIFY isReadChanged)
public:
static QSharedPointer<ChatMessageCore> create(const std::shared_ptr<linphone::ChatMessage> &chatmessage);
@ -64,15 +65,21 @@ public:
bool isRemoteMessage() const;
bool isFromChatGroup() const;
bool isRead() const;
void setIsRead(bool read);
std::shared_ptr<ChatMessageModel> getModel() const;
signals:
void timestampChanged(QDateTime timestamp);
void textChanged(QString text);
void isReadChanged(bool read);
void isRemoteMessageChanged(bool isRemote);
void lDelete();
void deleted();
void lMarkAsRead();
void readChanged();
private:
DECLARE_ABSTRACT_OBJECT QString mText;
@ -84,6 +91,7 @@ private:
QDateTime mTimestamp;
bool mIsRemoteMessage = false;
bool mIsFromChatGroup = false;
bool mIsRead = false;
std::shared_ptr<ChatMessageModel> mChatMessageModel;
QSharedPointer<SafeConnection<ChatMessageCore, ChatMessageModel>> mChatMessageModelConnection;
};

View file

@ -70,6 +70,13 @@ void ChatMessageList::setChatCore(QSharedPointer<ChatCore> core) {
if (mChatCore) disconnect(mChatCore.get(), &ChatCore::messageListChanged, this, nullptr);
mChatCore = core;
if (mChatCore) connect(mChatCore.get(), &ChatCore::messageListChanged, this, &ChatMessageList::lUpdate);
if (mChatCore)
connect(mChatCore.get(), &ChatCore::messagesInserted, this,
[this](QList<QSharedPointer<ChatMessageCore>> list) {
for (auto &message : list) {
add(message);
}
});
emit chatChanged();
}
}

View file

@ -67,6 +67,17 @@ ChatMessageGui *ChatMessageProxy::getChatMessageAtIndex(int i) {
return nullptr;
}
int ChatMessageProxy::findFirstUnreadIndex() {
int n = getCount();
for (int i = 0; i < n; ++i) {
auto chat = getItemAt<SortFilterList, ChatMessageList, ChatMessageCore>(i);
if (chat && !chat->isRead()) {
return i;
}
}
return std::max(0, n - 1);
}
bool ChatMessageProxy::SortFilterList::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const {
// auto l = getItemAtSource<ChatMessageList, ChatMessageCore>(sourceRow);
// return l != nullptr;

View file

@ -45,6 +45,7 @@ public:
void setSourceModel(QAbstractItemModel *sourceModel) override;
Q_INVOKABLE ChatMessageGui *getChatMessageAtIndex(int index);
Q_INVOKABLE int findFirstUnreadIndex();
signals:
void chatChanged();

View file

@ -151,13 +151,11 @@ std::shared_ptr<linphone::Account> AccountModel::getAccount() const {
void AccountModel::resetMissedCallsCount() {
mMonitor->resetMissedCallsCount();
emit unreadNotificationsChanged(0 /*mMonitor->getUnreadChatMessageCount()*/,
mMonitor->getMissedCallsCount()); // TODO
emit unreadNotificationsChanged(mMonitor->getUnreadChatMessageCount(), mMonitor->getMissedCallsCount());
}
void AccountModel::refreshUnreadNotifications() {
emit unreadNotificationsChanged(0 /*mMonitor->getUnreadChatMessageCount()*/,
mMonitor->getMissedCallsCount()); // TODO
emit unreadNotificationsChanged(mMonitor->getUnreadChatMessageCount(), mMonitor->getMissedCallsCount());
}
int AccountModel::getMissedCallsCount() const {

View file

@ -34,10 +34,17 @@ ChatModel::ChatModel(const std::shared_ptr<linphone::ChatRoom> &chatroom, QObjec
: ::Listener<linphone::ChatRoom, linphone::ChatRoomListener>(chatroom, parent) {
lDebug() << "[ChatModel] new" << this << " / SDKModel=" << chatroom.get();
mustBeInLinphoneThread(getClassName());
auto coreModel = CoreModel::getInstance();
if (coreModel)
connect(coreModel.get(), &CoreModel::messageReadInChatRoom, this,
[this](std::shared_ptr<linphone::ChatRoom> chatroom) {
if (chatroom == mMonitor) emit messagesRead();
});
}
ChatModel::~ChatModel() {
mustBeInLinphoneThread("~" + getClassName());
disconnect(CoreModel::getInstance().get(), &CoreModel::messageReadInChatRoom, this, nullptr);
}
QDateTime ChatModel::getLastUpdateTime() {
@ -106,6 +113,9 @@ int ChatModel::getUnreadMessagesCount() const {
void ChatModel::markAsRead() {
mMonitor->markAsRead();
for (auto &message : getHistory()) {
message->markAsRead();
}
emit messagesRead();
}

View file

@ -68,6 +68,16 @@ ChatMessageModel::onFileTransferSend(const std::shared_ptr<linphone::ChatMessage
return nullptr;
}
bool ChatMessageModel::isRead() const {
return mMonitor->isRead();
}
void ChatMessageModel::markAsRead() {
mMonitor->markAsRead();
emit messageRead();
emit CoreModel::getInstance()->messageReadInChatRoom(mMonitor->getChatRoom());
}
void ChatMessageModel::deleteMessageFromChatRoom() {
auto chatRoom = mMonitor->getChatRoom();
if (chatRoom) {

View file

@ -44,10 +44,14 @@ public:
QString getFromAddress() const;
QString getToAddress() const;
bool isRead() const;
void markAsRead();
void deleteMessageFromChatRoom();
signals:
void messageDeleted();
void messageRead();
private:
DECLARE_ABSTRACT_OBJECT

View file

@ -82,6 +82,7 @@ signals:
void requestRestart();
void enabledLdapAddressBookSaved();
void magicSearchResultReceived(QString filter);
void messageReadInChatRoom(std::shared_ptr<linphone::ChatRoom> chatRoom);
private:
QString mConfigPath;

View file

@ -14,41 +14,35 @@ ListView {
property color backgroundColor
spacing: Math.round(4 * DefaultStyle.dp)
// Component.onCompleted: positionViewAtIndex(chatMessageProxy.findFirstUnreadIndex(), ListView.Visible)
onAtYEndChanged: if (atYEnd) chat.core.lMarkAsRead();
onChatChanged: if (visible) {
Component.onCompleted: {
var index = chatMessageProxy.findFirstUnreadIndex()
console.log("visible, first unread at index", index)
mainItem.positionViewAtIndex(index, ListView.Visible)
positionViewAtIndex(index, ListView.End)
var chatMessage = chatMessageProxy.getChatMessageAtIndex(index)
if (chatMessage && !chatMessage.core.isRead) chatMessage.core.lMarkAsRead()
}
RoundButton {
Button {
visible: !mainItem.atYEnd
icon.source: AppIcons.downArrow
// Layout.preferredWidth: 40 * DefaultStyle.dp
// Layout.preferredHeight: 40 * DefaultStyle.dp
leftPadding: Math.round(16 * DefaultStyle.dp)
rightPadding: Math.round(16 * DefaultStyle.dp)
topPadding: Math.round(16 * DefaultStyle.dp)
bottomPadding: Math.round(16 * DefaultStyle.dp)
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.bottomMargin: Math.round(18 * DefaultStyle.dp)
anchors.rightMargin: Math.round(18 * DefaultStyle.dp)
onClicked: {
var index = chatMessageProxy.findFirstUnreadIndex()
console.log("clicked, first unread at index", index)
mainItem.positionViewAtIndex(index, ListView.Visible)
// var chatMessage = chatMessageProxy.getChatMessageAtIndex(index)
// if (chatMessage && !chatMessage.core.isRead) chatMessage.core.lMarkAsRead()
mainItem.positionViewAtIndex(index, ListView.End)
var chatMessage = chatMessageProxy.getChatMessageAtIndex(index)
if (chatMessage && !chatMessage.core.isRead) chatMessage.core.lMarkAsRead()
}
}
model: ChatMessageProxy {
id: chatMessageProxy
chatGui: mainItem.chat
onCountChanged: {
var indexToSelect = mainItem.currentIndex
mainItem.currentIndex = -1
mainItem.currentIndex = indexToSelect
}
}
header: Item {

View file

@ -33,8 +33,8 @@ FocusScope {
// Use directly previous initial
property real itemsRightMargin: Math.round(39 * DefaultStyle.dp)
property var displayName: searchResultItem.core.fullName
property string initial: displayName ? displayName[0].toLocaleLowerCase(ConstantsCpp.DefaultLocale) : ''
property string displayName: searchResultItem? searchResultItem.core.fullName : ""
property string initial: displayName != "" ? displayName[0].toLocaleLowerCase(ConstantsCpp.DefaultLocale) : ''
signal clicked(var mouse)
signal contactDeletionRequested(FriendGui contact)
@ -49,7 +49,7 @@ FocusScope {
verticalAlignment: Text.AlignVCenter
width: Math.round(20 * DefaultStyle.dp)
opacity: previousInitial != mainItem.initial ? 1 : 0
text: mainItem.initial
text: mainItem.initial || ""
color: DefaultStyle.main2_400
font {
pixelSize: Math.round(20 * DefaultStyle.dp)

View file

@ -15,10 +15,10 @@ Notification {
overriddenWidth: Math.round(400 * DefaultStyle.dp)
overriddenHeight: content.height
property string avatarUri: notificationData && notificationData.avatarUri
property string chatRoomName: notificationData && notificationData.chatRoomName
property string remoteAddress: notificationData && notificationData.remoteAddress
property string message: notificationData && notificationData.message
property string avatarUri: notificationData ? notificationData.avatarUri : ""
property string chatRoomName: notificationData ? notificationData.chatRoomName : ""
property string remoteAddress: notificationData ? notificationData.remoteAddress : ""
property string message: notificationData ? notificationData.message : ""
Popup {
id: content

View file

@ -18,7 +18,7 @@ RowLayout {
onChatChanged: {
// TODO : call when all messages read after scroll to unread feature available
if (chat) chat.core.lMarkAsRead()
// if (chat) chat.core.lMarkAsRead()
}
MainRightPanel {
id: splitPanel