diff --git a/Linphone/core/chat/message/content/ChatMessageContentCore.cpp b/Linphone/core/chat/message/content/ChatMessageContentCore.cpp index 944f5edee..8fe773e25 100644 --- a/Linphone/core/chat/message/content/ChatMessageContentCore.cpp +++ b/Linphone/core/chat/message/content/ChatMessageContentCore.cpp @@ -63,6 +63,8 @@ ChatMessageContentCore::ChatMessageContentCore(const std::shared_ptrgetFileDuration(); mFileOffset = 0; mUtf8Text = Utils::coreStringToAppString(content->getUtf8Text()); + auto chatRoom = chatMessageModel ? chatMessageModel->getMonitor()->getChatRoom() : nullptr; + mRichFormatText = ToolModel::encodeTextToQmlRichFormat(mUtf8Text, {}, chatRoom); mWasDownloaded = !mFilePath.isEmpty() && QFileInfo(mFilePath).isFile(); mThumbnail = mFilePath.isEmpty() ? QString() diff --git a/Linphone/core/chat/message/content/ChatMessageContentCore.hpp b/Linphone/core/chat/message/content/ChatMessageContentCore.hpp index d60832fdc..0c4652a05 100644 --- a/Linphone/core/chat/message/content/ChatMessageContentCore.hpp +++ b/Linphone/core/chat/message/content/ChatMessageContentCore.hpp @@ -40,6 +40,7 @@ class ChatMessageContentCore : public QObject, public AbstractObject { Q_PROPERTY(bool wasDownloaded READ wasDownloaded WRITE setWasDownloaded NOTIFY wasDownloadedChanged) Q_PROPERTY(QString filePath READ getFilePath WRITE setFilePath NOTIFY filePathChanged) Q_PROPERTY(QString utf8Text READ getUtf8Text CONSTANT) + Q_PROPERTY(QString richFormatText MEMBER mRichFormatText CONSTANT) Q_PROPERTY(bool isFile READ isFile WRITE setIsFile NOTIFY isFileChanged) Q_PROPERTY(bool isFileEncrypted READ isFileEncrypted WRITE setIsFileEncrypted NOTIFY isFileEncryptedChanged) Q_PROPERTY(bool isFileTransfer READ isFileTransfer WRITE setIsFileTransfer NOTIFY isFileTransferChanged) @@ -122,6 +123,7 @@ private: int mFileDuration; QString mThumbnail; QString mUtf8Text; + QString mRichFormatText; QString mFilePath; QString mName; quint64 mFileSize; diff --git a/Linphone/model/tool/ToolModel.cpp b/Linphone/model/tool/ToolModel.cpp index b123ff59e..f4db8bb91 100644 --- a/Linphone/model/tool/ToolModel.cpp +++ b/Linphone/model/tool/ToolModel.cpp @@ -24,6 +24,7 @@ #include "core/path/Paths.hpp" #include "model/core/CoreModel.hpp" #include "model/friend/FriendsManager.hpp" +#include "tool/UriTools.hpp" #include "tool/Utils.hpp" #include #include @@ -117,6 +118,108 @@ QString ToolModel::getDisplayName(QString address) { return nameSplitted.join(" "); } +QString ToolModel::encodeTextToQmlRichFormat(const QString &text, + const QVariantMap &options, + std::shared_ptr chatRoom) { + QStringList formattedText; + bool lastWasUrl = false; + auto primaryColor = QColor::fromString("#4AA8FF"); + + if (options.contains("noLink") && options["noLink"].toBool()) { + formattedText.append(Utils::encodeEmojiToQmlRichFormat(text)); + } else { + + auto iriParsed = UriTools::parseIri(text); + + for (int i = 0; i < iriParsed.size(); ++i) { + QString iri = iriParsed[i] + .second.replace('&', "&") + .replace('<', "\u2063<") + .replace('>', "\u2063>") + .replace('"', """) + .replace('\'', "'"); + if (!iriParsed[i].first) { + if (lastWasUrl) { + lastWasUrl = false; + if (iri.front() != ' ') iri.push_front(' '); + } + formattedText.append(Utils::encodeEmojiToQmlRichFormat(iri)); + } else { + QString uri = + iriParsed[i].second.left(3) == "www" ? "http://" + iriParsed[i].second : iriParsed[i].second; + /* TODO : preview from link + int extIndex = iriParsed[i].second.lastIndexOf('.'); + QString ext; + if( extIndex >= 0) + ext = iriParsed[i].second.mid(extIndex+1).toUpper(); + if(imageFormat.contains(ext.toLatin1())){// imagesHeight is not used because of bugs on display + (blank image if set without width) images += ""+uri+""; + }else{ + */ + formattedText.append("" + iri + + ""); + lastWasUrl = true; + /*}*/ + } + } + } + if (lastWasUrl && formattedText.last().back() != ' ') { + formattedText.push_back(" "); + } + if (chatRoom) { + auto participants = chatRoom->getParticipants(); + auto mentionsParsed = UriTools::parseMention(formattedText.join("")); + formattedText.clear(); + + for (int i = 0; i < mentionsParsed.size(); ++i) { + QString mention = mentionsParsed[i].second; + + if (mentionsParsed[i].first) { + QString mentions = mentionsParsed[i].second; + QStringList finalMentions; + QStringList parts = mentions.split(" "); + for (auto part : parts) { + if (part.startsWith("@")) { // mention + QString username = part; + username.removeFirst(); + auto it = std::find_if(participants.begin(), participants.end(), + [username](std::shared_ptr p) { + return p->getAddress() && username == p->getAddress()->getUsername(); + }); + if (it != participants.end()) { + auto foundParticipant = *it; + // participants.at(std::distance(participants.begin(), it)); + auto address = foundParticipant->getAddress()->clone(); + auto isFriend = findFriendByAddress(address); + address->clean(); + auto addressString = Utils::coreStringToAppString(address->asStringUriOnly()); + if (isFriend) + part = "@" + Utils::coreStringToAppString(isFriend->getAddress()->getDisplayName()); + QString participantLink = "" + part + ""; + finalMentions.append(participantLink); + } else { + finalMentions.append(part); + } + } else { + finalMentions.append(part); + } + } + formattedText.push_back(finalMentions.join(" ")); + } else { + formattedText.push_back(mentionsParsed[i].second); + } + } + } + return "

" + formattedText.join(""); +} + std::shared_ptr ToolModel::findFriendByAddress(const QString &address) { auto defaultFriendList = CoreModel::getInstance()->getCore()->getDefaultFriendList(); if (!defaultFriendList) return nullptr; diff --git a/Linphone/model/tool/ToolModel.hpp b/Linphone/model/tool/ToolModel.hpp index b180c3667..43972f923 100644 --- a/Linphone/model/tool/ToolModel.hpp +++ b/Linphone/model/tool/ToolModel.hpp @@ -50,6 +50,10 @@ public: static QString getDisplayName(const std::shared_ptr &address); static QString getDisplayName(QString address); + static QString encodeTextToQmlRichFormat(const QString &text, + const QVariantMap &options, + std::shared_ptr chatRoom); + static std::shared_ptr findFriendByAddress(const QString &address); static std::shared_ptr findFriendByAddress(std::shared_ptr linphoneAddr); diff --git a/Linphone/view/Control/Display/Chat/ChatMessagesListView.qml b/Linphone/view/Control/Display/Chat/ChatMessagesListView.qml index 91029e7a5..643884d2d 100644 --- a/Linphone/view/Control/Display/Chat/ChatMessagesListView.qml +++ b/Linphone/view/Control/Display/Chat/ChatMessagesListView.qml @@ -88,7 +88,10 @@ ListView { initialDisplayItems: 10 onEventInserted: (index, gui) => { if (!mainItem.visible) return - if(mainItem.lastItemVisible) mainItem.positionViewAtIndex(index, ListView.Beginning) + if(mainItem.lastItemVisible) { + mainItem.positionViewAtIndex(index, ListView.Beginning) + markIndexAsRead(index) + } } Component.onCompleted: loading = true onListAboutToBeReset: loading = true @@ -239,6 +242,7 @@ ListView { Component.onCompleted: { if (index === 0) mainItem.lastItemVisible = isFullyVisible } + onYChanged: if (index === 0) mainItem.lastItemVisible = isFullyVisible chat: mainItem.chat searchedTextPart: mainItem.filterText maxWidth: Math.round(mainItem.width * (3/4)) diff --git a/Linphone/view/Control/Display/Chat/ChatTextContent.qml b/Linphone/view/Control/Display/Chat/ChatTextContent.qml index bb3bdad5a..d592d87a6 100644 --- a/Linphone/view/Control/Display/Chat/ChatTextContent.qml +++ b/Linphone/view/Control/Display/Chat/ChatTextContent.qml @@ -26,13 +26,10 @@ TextEdit { readOnly: true selectByMouse: true - property var encodeTextObj: visible ? UtilsCpp.encodeTextToQmlRichFormat(contentGui.core.utf8Text, {}, mainItem.chatGui) - : '' - text: encodeTextObj - && (searchedTextPart !== "" - ? UtilsCpp.boldTextPart(encodeTextObj.value, searchedTextPart) - : encodeTextObj.value) - || "" + text: searchedTextPart !== "" + ? UtilsCpp.boldTextPart(contentGui.core.richFormatText, searchedTextPart) + : contentGui.core.richFormatText + textFormat: Text.RichText // To supports links and imgs. wrapMode: TextEdit.Wrap