1
0
Fork 0
mirror of https://git.jami.net/savoirfairelinux/jami-client-qt.git synced 2025-07-16 05:25:22 +02:00

messagelist: use history given from daemon (except SIP accounts)

With Jami-Daemon >= 14.0.0, the client doesn't need to construct
itself the history. This part is now handled by the daemon.
This patch uses the new API:
+ loadConversationMessages->loadConversation
+ SwarmMessageReceived/SwarmMessageUpdated/ReactionAdded/ReactionRemoved
+ remove MessageReceived
+ ConversationLoaded->SwarmLoaded

+ No need to use loadConversationUntil, the daemon will load whatever
the client needs.
+ No need to clear cache, just reset the body and emit data changes

Everything should work like before (even re-translation & changing
preview preference)

Change-Id: Iaf1fa3e84e8e157ae2d0bec210977f9a34415ebc
This commit is contained in:
Sébastien Blin 2023-07-06 11:31:20 -04:00
parent 2bbd9637da
commit 32b76c8da4
34 changed files with 753 additions and 882 deletions

2
daemon

@ -1 +1 @@
Subproject commit 317b7317dcda4afb733ddb9bd5b450d4635941ae
Subproject commit 8468f15927ec7c83a5fca671bac1a4112883b8c9

View file

@ -137,4 +137,11 @@ AppSettingsManager::loadTranslations()
}
Q_EMIT retranslate();
loadHistory();
}
void
AppSettingsManager::loadHistory()
{
Q_EMIT reloadHistory();
}

View file

@ -140,9 +140,11 @@ public:
QString getLanguage();
void loadTranslations();
void loadHistory();
Q_SIGNALS:
void retranslate();
void reloadHistory();
private:
QSettings* settings_;

View file

@ -131,7 +131,7 @@ Popup {
Repeater {
model: emojiArray.length < 15 ? emojiArray.length : 15
delegate: Text {
text: emojiArray[index]
text: emojiArray[index].body
horizontalAlignment: Text.AlignRight
font.pointSize: JamiTheme.emojiPopupFontsize
}
@ -147,7 +147,7 @@ Popup {
delegate: Button {
id: emojiButton
text: emojiArray[index]
text: emojiArray[index].body
font.pointSize: JamiTheme.emojiPopupFontsize
background.visible: false
padding: 0
@ -155,13 +155,13 @@ Popup {
Text {
visible: emojiButton.hovered
anchors.centerIn: parent
text: emojiArray[index]
text: emojiArray[index].body
font.pointSize: JamiTheme.emojiPopupFontsizeBig
z: 1
}
onClicked: {
MessagesAdapter.removeEmojiReaction(CurrentConversation.id, emojiButton.text, msgId);
MessagesAdapter.removeEmojiReaction(CurrentConversation.id, emojiButton.text, emojiArray[index].commitId);
if (emojiArray.length === 1)
close();
}

View file

@ -43,7 +43,7 @@ Item {
for (const reaction of Object.entries(reactions)) {
var authorEmojiList = reaction[1];
for (var emojiIndex in authorEmojiList) {
var emoji = authorEmojiList[emojiIndex];
var emoji = authorEmojiList[emojiIndex].body;
if (emojiList.includes(emoji)) {
var findIndex = emojiList.indexOf(emoji);
if (findIndex != -1)
@ -75,7 +75,7 @@ Item {
var authorEmojiList = reaction[1];
if (CurrentAccount.uri === authorUri) {
for (var emojiIndex in authorEmojiList) {
list[index] = authorEmojiList[emojiIndex];
list[index] = authorEmojiList[emojiIndex].body;
index++;
}
return list;

View file

@ -31,16 +31,6 @@ Item {
property int requestId: -1
property var replyTransferName: MessagesAdapter.dataForInteraction(ReplyTo, MessageList.TransferName)
Component.onCompleted: {
// Make sure we show the original post
// In the future, we may just want to load the previous interaction of the thread
// and not show it, but for now we can simplify.
if (ReplyTo !== "") {
// Store the request Id for later filtering.
requestId = MessagesAdapter.loadConversationUntil(ReplyTo);
}
}
Connections {
target: MessagesAdapter

View file

@ -110,19 +110,20 @@ BaseContextMenu {
onClosed: if (emojiPicker)
emojiPicker.closeEmojiPicker()
function getModel() {
function getQuickEmojiListModel() {
const defaultModel = ["👍", "👎", "😂"];
const reactedEmojis = Array.isArray(emojiReplied) ? emojiReplied.slice(0, defaultModel.length) : [];
const uniqueEmojis = Array.from(new Set(reactedEmojis));
const missingEmojis = defaultModel.filter(emoji => !uniqueEmojis.includes(emoji));
return uniqueEmojis.concat(missingEmojis);
const result = uniqueEmojis.concat(missingEmojis);
return result;
}
property list<MenuItem> menuItems: [
GeneralMenuItemList {
id: audioMessage
id: emojiQuickReactions
modelList: getModel()
modelList: getQuickEmojiListModel()
canTrigger: true
iconSource: JamiResources.add_reaction_svg
itemName: JamiStrings.copy

View file

@ -109,19 +109,32 @@ ConversationListModelBase::dataForItem(item_t item, int role) const
return QVariant(item.unreadMessages);
case Role::LastInteractionTimeStamp: {
if (!item.interactions->empty()) {
auto ts = static_cast<qint32>(item.interactions->at(item.lastMessageUid).timestamp);
auto ts = static_cast<qint32>(item.interactions->rbegin()->second.timestamp);
return QVariant(ts);
}
break;
}
case Role::LastInteraction: {
if (!item.interactions->empty()) {
auto interaction = item.interactions->at(item.lastMessageUid);
auto body_ = interaction.body;
auto interaction = item.interactions->rbegin()->second;
auto& accInfo = lrcInstance_->getCurrentAccountInfo();
if (interaction.type == interaction::Type::DATA_TRANSFER) {
body_ = interaction.commit.value("displayName");
return QVariant(interaction.commit.value("displayName"));
} else if (interaction.type == lrc::api::interaction::Type::CALL) {
return QVariant(interaction::getCallInteractionString(interaction.authorUri
== accInfo.profileInfo.uri,
interaction));
} else if (interaction.type == lrc::api::interaction::Type::CONTACT) {
auto bestName = interaction.authorUri == accInfo.profileInfo.uri
? accInfo.accountModel->bestNameForAccount(accInfo.id)
: accInfo.contactModel->bestNameForContact(
interaction.authorUri);
return QVariant(
interaction::getContactInteractionString(bestName,
interaction::to_action(
interaction.commit["action"])));
}
return QVariant(body_);
return QVariant(interaction.body);
}
break;
}

View file

@ -158,7 +158,7 @@ private:
MapStringString contentDrafts_;
MapStringString lastConferences_;
conversation::Info invalid {};
conversation::Info invalid {"", nullptr};
bool debugMode_ {false};
bool muteDaemon_ {true};

View file

@ -35,14 +35,6 @@ ListSelectionView {
visible: false
onPresented: visible = true
Connections {
target: CurrentConversation
function onReloadInteractions() {
UtilsAdapter.clearInteractionsCache(CurrentAccount.id, CurrentConversation.id);
MessagesAdapter.loadMoreMessages();
}
}
onDismissed: {
callStackView.needToCloseInCallConversationAndPotentialWindow();
LRCInstance.deselectConversation();
@ -51,12 +43,6 @@ ListSelectionView {
property string currentAccountId: CurrentAccount.id
onCurrentAccountIdChanged: dismiss()
onVisibleChanged: {
if (visible)
return;
UtilsAdapter.clearInteractionsCache(CurrentAccount.id, CurrentConversation.id);
}
color: JamiTheme.transparentColor
leftPaneItem: viewCoordinator.getView("SidePanel")

View file

@ -328,9 +328,8 @@ Rectangle {
}
onHeightChanged: {
if (loader.item != null) {
if (loader.item)
Qt.callLater(loader.item.scrollToBottom);
}
}
Layout.alignment: Qt.AlignHCenter

View file

@ -36,8 +36,10 @@ JamiListView {
}
function loadMoreMsgsIfNeeded() {
if (atYBeginning && !CurrentConversation.allMessagesLoaded)
if (atYBeginning && !CurrentConversation.allMessagesLoaded) {
print("load more messages", atYBeginning, CurrentConversation.allMessagesLoaded)
MessagesAdapter.loadMoreMessages()
}
}
function computeTimestampVisibility(item1, item1Index, item2, item2Index) {
@ -252,6 +254,19 @@ JamiListView {
onAtYBeginningChanged: loadMoreMsgsIfNeeded()
Timer {
id: chunkLoadDebounceTimer
interval: 100
repeat: false
running: false
onTriggered: {
if (root.contentHeight < root.height) {
root.loadMoreMsgsIfNeeded();
}
}
}
Connections {
target: MessagesAdapter
@ -263,9 +278,9 @@ JamiListView {
}
function onMoreMessagesLoaded(loadingRequestId) {
if (root.contentHeight < root.height || root.atYBeginning) {
root.loadMoreMsgsIfNeeded()
}
// This needs to be throttled, otherwise we will continue to load more messages
// prior to the loaded chunk being rendered and changing the contentHeight.
chunkLoadDebounceTimer.restart();
}
function onFileCopied(dest) {

View file

@ -46,13 +46,6 @@ ItemDelegate {
property string lastInteractionFormattedDate: MessagesAdapter.getBestFormattedDate(lastInteractionDate)
Connections {
target: UtilsAdapter
function onChangeLanguage() {
UtilsAdapter.clearInteractionsCache(root.accountId, root.convId)
}
}
property bool showSharePositionIndicator: PositionManager.isPositionSharedToConv(accountId, UID)
property bool showSharedPositionIndicator: PositionManager.isConvSharingPosition(accountId, UID)

View file

@ -51,13 +51,18 @@ MessagesAdapter::MessagesAdapter(AppSettingsManager* settingsManager,
, settingsManager_(settingsManager)
, messageParser_(new MessageParser(previewEngine, this))
, filteredMsgListModel_(new FilteredMsgListModel(this))
, mediaInteractions_(std::make_unique<MessageListModel>())
, mediaInteractions_(std::make_unique<MessageListModel>(nullptr))
, timestampTimer_(new QTimer(this))
{
setObjectName(typeid(*this).name());
set_messageListModel(QVariant::fromValue(filteredMsgListModel_));
connect(settingsManager_,
&AppSettingsManager::reloadHistory,
&lrcInstance_->accountModel(),
&AccountModel::reloadHistory);
connect(lrcInstance_, &LRCInstance::selectedConvUidChanged, this, [this]() {
set_replyToId("");
set_editId("");
@ -68,7 +73,7 @@ MessagesAdapter::MessagesAdapter(AppSettingsManager* settingsManager,
filteredMsgListModel_->setSourceModel(conversation.interactions.get());
set_currentConvComposingList(conversationTypersUrlToName(conversation.typers));
mediaInteractions_.reset(new MessageListModel(this));
mediaInteractions_.reset(new MessageListModel(&lrcInstance_->getCurrentAccountInfo(), this));
set_mediaMessageListModel(QVariant::fromValue(mediaInteractions_.get()));
});
@ -98,37 +103,14 @@ MessagesAdapter::loadMoreMessages()
auto convId = lrcInstance_->get_selectedConvUid();
try {
const auto& convInfo = lrcInstance_->getConversationFromConvUid(convId, accountId);
if (convInfo.isSwarm()) {
auto* convModel = lrcInstance_->getCurrentConversationModel();
convModel->loadConversationMessages(convId, loadChunkSize_);
}
if (convInfo.isSwarm())
lrcInstance_->getCurrentConversationModel()->loadConversationMessages(convId,
loadChunkSize_);
} catch (const std::exception& e) {
qWarning() << e.what();
}
}
int
MessagesAdapter::loadConversationUntil(const QString& to)
{
try {
if (auto* model = getMsgListSourceModel()) {
auto idx = model->indexOfMessage(to);
if (idx == -1) {
auto accountId = lrcInstance_->get_currentAccountId();
auto convId = lrcInstance_->get_selectedConvUid();
const auto& convInfo = lrcInstance_->getConversationFromConvUid(convId, accountId);
if (convInfo.isSwarm()) {
auto* convModel = lrcInstance_->getCurrentConversationModel();
return convModel->loadConversationUntil(convId, to);
}
}
}
} catch (const std::exception& e) {
qWarning() << e.what();
}
return 0;
}
void
MessagesAdapter::connectConversationModel()
{
@ -200,11 +182,8 @@ MessagesAdapter::removeEmojiReaction(const QString& convId,
const QString& messageId)
{
try {
const auto authorUri = lrcInstance_->getCurrentAccountInfo().profileInfo.uri;
// check if this emoji has already been added by this author
auto emojiId = lrcInstance_->getConversationFromConvUid(convId)
.interactions->findEmojiReaction(emoji, authorUri, messageId);
editMessage(convId, "", emojiId);
editMessage(convId, "", messageId);
} catch (...) {
qDebug() << "Exception during removeEmojiReaction():" << messageId;
}
@ -267,13 +246,6 @@ MessagesAdapter::copyToDownloads(const QString& interactionId, const QString& di
}
}
void
MessagesAdapter::deleteInteraction(const QString& interactionId)
{
lrcInstance_->getCurrentConversationModel()
->clearInteractionFromConversation(lrcInstance_->get_selectedConvUid(), interactionId);
}
void
MessagesAdapter::openUrl(const QString& url)
{
@ -754,13 +726,13 @@ MessagesAdapter::getFormattedDay(const quint64 timestamp)
void
MessagesAdapter::startSearch(const QString& text, bool isMedia)
{
mediaInteractions_.reset(new MessageListModel(this));
auto accountId = lrcInstance_->get_currentAccountId();
mediaInteractions_.reset(new MessageListModel(&lrcInstance_->getCurrentAccountInfo(), this));
set_mediaMessageListModel(QVariant::fromValue(mediaInteractions_.get()));
if (text.isEmpty() && !isMedia)
return;
auto accountId = lrcInstance_->get_currentAccountId();
auto convId = lrcInstance_->get_selectedConvUid();
try {

View file

@ -83,7 +83,6 @@ Q_SIGNALS:
protected:
Q_INVOKABLE bool isDocument(const interaction::Type& type);
Q_INVOKABLE void loadMoreMessages();
Q_INVOKABLE qint32 loadConversationUntil(const QString& to);
Q_INVOKABLE void connectConversationModel();
Q_INVOKABLE void sendConversationRequest();
Q_INVOKABLE void removeConversation(const QString& convUid);
@ -112,7 +111,6 @@ protected:
Q_INVOKABLE void openUrl(const QString& url);
Q_INVOKABLE void openDirectory(const QString& arg);
Q_INVOKABLE void removeFile(const QString& interactionId, const QString& path);
Q_INVOKABLE void deleteInteraction(const QString& interactionId);
Q_INVOKABLE void joinCall(const QString& uri,
const QString& deviceId,
const QString& confId,

View file

@ -86,6 +86,8 @@ UtilsAdapter::setAppValue(const Settings::Key key, const QVariant& value)
set_isRTL(isRTL());
} else if (key == Settings::Key::BaseZoom)
Q_EMIT changeFontSize();
else if (key == Settings::Key::DisplayHyperlinkPreviews)
settingsManager_->loadHistory();
else if (key == Settings::Key::EnableExperimentalSwarm)
Q_EMIT showExperimentalCallSwarm();
else if (key == Settings::Key::ShowChatviewHorizontally)
@ -517,17 +519,6 @@ UtilsAdapter::monitor(const bool& continuous)
lrcInstance_->monitor(continuous);
}
void
UtilsAdapter::clearInteractionsCache(const QString& accountId, const QString& convId)
{
try {
auto& accInfo = lrcInstance_->accountModel().getAccountInfo(accountId);
auto& convModel = accInfo.conversationModel;
convModel->clearInteractionsCache(convId);
} catch (...) {
}
}
QVariantMap
UtilsAdapter::supportedLang()
{

View file

@ -128,7 +128,6 @@ public:
Q_INVOKABLE void setDownloadPath(QString dir);
Q_INVOKABLE void setScreenshotPath(QString dir);
Q_INVOKABLE void monitor(const bool& continuous);
Q_INVOKABLE void clearInteractionsCache(const QString& accountId, const QString& convUid);
Q_INVOKABLE QVariantMap supportedLang();
Q_INVOKABLE QString tempCreationImage(const QString& imageId = "temp") const;
Q_INVOKABLE void setTempCreationImageFromString(const QString& image = "",

View file

@ -1205,6 +1205,14 @@ AccountModel::notificationsCount() const
return total;
}
void
AccountModel::reloadHistory()
{
for (const auto& [_id, account] : pimpl_->accounts) {
account.first.conversationModel->reloadHistory();
}
}
QString
AccountModel::avatar(const QString& accountId) const
{

View file

@ -252,6 +252,7 @@ public:
* Get notifications count across accounts
*/
int notificationsCount() const;
void reloadHistory();
/**
* Retrieve account's avatar
*/

View file

@ -59,17 +59,26 @@ to_mode(const int intMode)
struct Info
{
Info()
: interactions(std::make_unique<MessageListModel>(nullptr))
{}
explicit Info(const QString& uid, const account::Info* acc)
: uid(uid)
, interactions(std::make_unique<MessageListModel>(acc, nullptr))
{
account = acc;
if (acc) {
accountId = acc->id;
accountUri = acc->profileInfo.uri;
}
}
Info(const Info& other) = delete;
Info(Info&& other) = default;
Info& operator=(const Info& other) = delete;
Info& operator=(Info&& other) = default;
bool allMessagesLoaded = false;
QString uid = "";
QString uid;
QString accountId;
const account::Info* account {nullptr};
QString accountUri;
QVector<member::Member> participants;
VectorMapStringString activeCalls;
VectorMapStringString ignoredActiveCalls;
@ -77,7 +86,6 @@ struct Info
QString callId;
QString confId;
std::unique_ptr<MessageListModel> interactions;
QString lastMessageUid;
QString lastSelfMessageId;
QHash<QString, QString> parentsId; // pair messageid/parentid for messages without parent loaded
unsigned int unreadMessages = 0;

View file

@ -268,17 +268,6 @@ public:
* clear all history
*/
void clearAllHistory();
/**
* Clear one interaction from the history
* @param convId
* @param interactionId
*/
void clearInteractionFromConversation(const QString& convId, const QString& interactionId);
/**
* Clear the cache for interactions in the conversation
* @param convId
*/
void clearInteractionsCache(const QString& convId);
/**
* @param convId
* @param interactionId
@ -335,7 +324,6 @@ public:
* @return id for loading request. -1 if not loaded
*/
int loadConversationMessages(const QString& conversationId, const int size = 1);
int loadConversationUntil(const QString& conversationId, const QString& to);
/**
* accept request for conversation
* @param conversationId conversation's id
@ -413,6 +401,7 @@ public:
* @return number of conversations requests + unread
*/
int notificationsCount() const;
void reloadHistory() const;
const VectorString peersForConversation(const QString& conversationId);
// Presentation

View file

@ -269,9 +269,61 @@ getContactInteractionString(const QString& authorUri, const ContactAction& actio
case ContactAction::UNBANNED:
return QObject::tr("%1 was re-added").arg(authorUri);
case ContactAction::INVALID:
return {};
return QObject::tr("Contact added");
}
return QObject::tr("Contact added");
}
static inline QString
getFormattedCallDuration(const std::time_t duration)
{
if (duration == 0)
return {};
std::string formattedString;
auto minutes = duration / 60;
auto seconds = duration % 60;
if (minutes > 0) {
formattedString += std::to_string(minutes) + ":";
if (formattedString.length() == 2) {
formattedString = "0" + formattedString;
}
} else {
formattedString += "00:";
}
if (seconds < 10)
formattedString += "0";
formattedString += std::to_string(seconds);
return QString::fromStdString(formattedString);
}
/**
* Get a formatted string for a call interaction's body
* @param isSelf
* @param info
* @return the formatted and translated call message string
*/
static inline QString
getCallInteractionStringNonSwarm(bool isSelf, const std::time_t& duration)
{
if (duration < 0) {
if (isSelf) {
return QObject::tr("Outgoing call");
} else {
return QObject::tr("Incoming call");
}
} else if (isSelf) {
if (duration) {
return QObject::tr("Outgoing call") + " - " + getFormattedCallDuration(duration);
} else {
return QObject::tr("Missed outgoing call");
}
} else {
if (duration) {
return QObject::tr("Incoming call") + " - " + getFormattedCallDuration(duration);
} else {
return QObject::tr("Missed incoming call");
}
}
return {};
}
struct Body
@ -287,6 +339,17 @@ public:
std::time_t timestamp;
};
struct Emoji
{
Q_GADGET
Q_PROPERTY(QString commitId MEMBER commitId)
Q_PROPERTY(QString body MEMBER body)
public:
QString commitId;
QString body;
};
/**
* @var authorUri
* @var body
@ -336,7 +399,7 @@ struct Info
this->isRead = isRead;
}
Info(const MapStringString& message, const QString& accountURI)
void init(const MapStringString& message, const QString& accountURI)
{
type = to_type(message["type"]);
if (message.contains("react-to") && type == Type::TEXT) {
@ -345,7 +408,7 @@ struct Info
}
authorUri = message["author"];
if (type == Type::TEXT || type == Type::EDITED || type == Type::REACTION) {
if (type == Type::TEXT) {
body = message["body"];
}
timestamp = message["timestamp"].toInt();
@ -367,6 +430,36 @@ struct Info
}
commit = message;
}
Info(const MapStringString& message, const QString& accountURI)
{
init(message, accountURI);
}
Info(const SwarmMessage& msg, const QString& accountUri)
{
MapStringString msgBody;
for (const auto& key : msg.body.keys()) {
msgBody.insert(key, msg.body.value(key));
}
init(msgBody, accountUri);
parentId = msg.linearizedParent;
type = to_type(msg.type);
for (const auto& edition : msg.editions)
previousBodies.append(Body {edition.value("id"),
edition.value("body"),
QString(edition.value("timestamp")).toInt()});
QMap<QString, QVariantList> mapStringEmoji;
for (const auto& reaction : msg.reactions) {
auto author = reaction.value("author");
auto body = reaction.value("body");
auto emoji = Emoji {reaction.value("id"), body};
QVariant variant = QVariant::fromValue(emoji);
mapStringEmoji[author].append(variant);
}
for (auto i = mapStringEmoji.begin(); i != mapStringEmoji.end(); i++)
reactions.insert(i.key(), i.value());
}
};
static inline bool
@ -375,6 +468,17 @@ isOutgoing(const Info& interaction)
return interaction.authorUri.isEmpty();
}
static inline QString
getCallInteractionString(bool isSelf, const Info& info)
{
if (!info.confId.isEmpty()) {
if (info.duration <= 0) {
return QObject::tr("Join call");
}
}
return getCallInteractionStringNonSwarm(isSelf, info.duration);
}
} // namespace interaction
} // namespace api
} // namespace lrc

View file

@ -144,78 +144,6 @@ prepareUri(const QString& uri, api::profile::Type type)
}
}
QString
getFormattedCallDuration(const std::time_t duration)
{
if (duration == 0)
return {};
std::string formattedString;
auto minutes = duration / 60;
auto seconds = duration % 60;
if (minutes > 0) {
formattedString += std::to_string(minutes) + ":";
if (formattedString.length() == 2) {
formattedString = "0" + formattedString;
}
} else {
formattedString += "00:";
}
if (seconds < 10)
formattedString += "0";
formattedString += std::to_string(seconds);
return QString::fromStdString(formattedString);
}
QString
getCallInteractionStringNonSwarm(bool isSelf, const std::time_t& duration)
{
if (duration < 0) {
if (isSelf) {
return QObject::tr("Outgoing call");
} else {
return QObject::tr("Incoming call");
}
} else if (isSelf) {
if (duration) {
return QObject::tr("Outgoing call") + " - " + getFormattedCallDuration(duration);
} else {
return QObject::tr("Missed outgoing call");
}
} else {
if (duration) {
return QObject::tr("Incoming call") + " - " + getFormattedCallDuration(duration);
} else {
return QObject::tr("Missed incoming call");
}
}
}
QString
getCallInteractionString(bool isSelf, const api::interaction::Info& info)
{
if (!info.confId.isEmpty()) {
if (info.duration <= 0) {
return QObject::tr("Join call");
}
}
return getCallInteractionStringNonSwarm(isSelf, info.duration);
}
QString
getContactInteractionString(const QString& authorUri, const api::interaction::Status& status)
{
if (authorUri.isEmpty()) {
return QObject::tr("Contact added");
} else {
if (status == api::interaction::Status::UNKNOWN) {
return QObject::tr("Invitation received");
} else if (status == api::interaction::Status::SUCCESS) {
return QObject::tr("Invitation accepted");
}
}
return {};
}
namespace vcard {
QString
compressedAvatar(const QString& image)
@ -515,6 +443,21 @@ beginConversationWithPeer(Database& db,
return newConversationsId;
}
QString
getContactInteractionString(const QString& authorUri, const api::interaction::Status& status)
{
if (authorUri.isEmpty()) {
return QObject::tr("Contact added");
} else {
if (status == api::interaction::Status::UNKNOWN) {
return QObject::tr("Invitation received");
} else if (status == api::interaction::Status::SUCCESS) {
return QObject::tr("Invitation accepted");
}
}
return {};
}
void
getHistory(Database& db, api::conversation::Info& conversation, const QString& localUri)
{
@ -540,9 +483,11 @@ getHistory(Database& db, api::conversation::Info& conversation, const QString& l
: std::stoi(durationString.toStdString());
auto status = api::interaction::to_status(payloads[i + 5]);
if (type == api::interaction::Type::CALL) {
body = getCallInteractionStringNonSwarm(payloads[i + 1] == localUri, duration);
body = api::interaction::getCallInteractionStringNonSwarm(payloads[i + 1]
== localUri,
duration);
} else if (type == api::interaction::Type::CONTACT) {
body = getContactInteractionString(payloads[i + 1], status);
body = storage::getContactInteractionString(payloads[i + 1], status);
}
auto msg = api::interaction::Info({payloads[i + 1],
body,
@ -552,7 +497,6 @@ getHistory(Database& db, api::conversation::Info& conversation, const QString& l
status,
(payloads[i + 6] == "1" ? true : false)});
conversation.interactions->emplace(payloads[i], std::move(msg));
conversation.lastMessageUid = payloads[i];
if (status != api::interaction::Status::DISPLAYED || !payloads[i + 1].isEmpty()) {
continue;
}
@ -764,20 +708,6 @@ clearHistory(Database& db, const QString& conversationId)
}
}
void
clearInteractionFromConversation(Database& db,
const QString& conversationId,
const QString& interactionId)
{
try {
db.deleteFrom("interactions",
"conversation=:conversation AND id=:id",
{{":conversation", conversationId}, {":id", interactionId}});
} catch (Database::QueryDeleteError& e) {
qWarning() << "deleteFrom error: " << e.details();
}
}
void
clearAllHistory(Database& db)
{

View file

@ -54,15 +54,6 @@ QString getPath();
*/
QString prepareUri(const QString& uri, api::profile::Type type);
/**
* Get a formatted string for a call interaction's body
* @param isSelf
* @param info
* @return the formatted and translated call message string
*/
QString getCallInteractionString(bool isSelf, const api::interaction::Info& info);
QString getCallInteractionStringNonSwarm(bool isSelf, const std::time_t& duration);
/**
* Get a formatted string for a contact interaction's body
* @param author_uri
@ -99,12 +90,6 @@ void setProfile(const QString& accountId,
} // namespace vcard
/**
* @param duration
* @return a human readable call duration (M:ss)
*/
QString getFormattedCallDuration(const std::time_t duration);
/**
* Get all conversations with a given participant's URI
* @param db
@ -311,16 +296,6 @@ void setInteractionRead(Database& db, const QString& id);
*/
void clearHistory(Database& db, const QString& conversationId);
/**
* Clear interaction from history
* @param db
* @param conversationId
* @param interactionId
*/
void clearInteractionFromConversation(Database& db,
const QString& conversationId,
const QString& interactionId);
/**
* Clear all history stored in the interactions table of the database
* @param db

View file

@ -301,9 +301,9 @@ CallbacksHandler::CallbacksHandler(const Lrc& parent)
&CallbacksHandler::slotAudioMeterReceived,
Qt::QueuedConnection);
connect(&ConfigurationManager::instance(),
&ConfigurationManagerInterface::conversationLoaded,
&ConfigurationManagerInterface::swarmLoaded,
this,
&CallbacksHandler::slotConversationLoaded,
&CallbacksHandler::slotSwarmLoaded,
Qt::QueuedConnection);
connect(&ConfigurationManager::instance(),
&ConfigurationManagerInterface::messagesFound,
@ -311,10 +311,25 @@ CallbacksHandler::CallbacksHandler(const Lrc& parent)
&CallbacksHandler::slotMessagesFound,
Qt::QueuedConnection);
connect(&ConfigurationManager::instance(),
&ConfigurationManagerInterface::messageReceived,
&ConfigurationManagerInterface::swarmMessageReceived,
this,
&CallbacksHandler::slotMessageReceived,
Qt::QueuedConnection);
connect(&ConfigurationManager::instance(),
&ConfigurationManagerInterface::swarmMessageUpdated,
this,
&CallbacksHandler::slotMessageUpdated,
Qt::QueuedConnection);
connect(&ConfigurationManager::instance(),
&ConfigurationManagerInterface::reactionAdded,
this,
&CallbacksHandler::slotReactionAdded,
Qt::QueuedConnection);
connect(&ConfigurationManager::instance(),
&ConfigurationManagerInterface::reactionRemoved,
this,
&CallbacksHandler::slotReactionRemoved,
Qt::QueuedConnection);
connect(&ConfigurationManager::instance(),
&ConfigurationManagerInterface::conversationProfileUpdated,
this,
@ -721,13 +736,14 @@ CallbacksHandler::slotRemoteRecordingChanged(const QString& callId,
}
void
CallbacksHandler::slotConversationLoaded(uint32_t requestId,
const QString& accountId,
const QString& conversationId,
const VectorMapStringString& messages)
CallbacksHandler::slotSwarmLoaded(uint32_t requestId,
const QString& accountId,
const QString& conversationId,
const VectorSwarmMessage& messages)
{
Q_EMIT conversationLoaded(requestId, accountId, conversationId, messages);
Q_EMIT swarmLoaded(requestId, accountId, conversationId, messages);
}
void
CallbacksHandler::slotMessagesFound(uint32_t requestId,
const QString& accountId,
@ -740,11 +756,37 @@ CallbacksHandler::slotMessagesFound(uint32_t requestId,
void
CallbacksHandler::slotMessageReceived(const QString& accountId,
const QString& conversationId,
const MapStringString& message)
const SwarmMessage& message)
{
Q_EMIT messageReceived(accountId, conversationId, message);
}
void
CallbacksHandler::slotMessageUpdated(const QString& accountId,
const QString& conversationId,
const SwarmMessage& message)
{
Q_EMIT messageUpdated(accountId, conversationId, message);
}
void
CallbacksHandler::slotReactionAdded(const QString& accountId,
const QString& conversationId,
const QString& messageId,
const MapStringString& reaction)
{
Q_EMIT reactionAdded(accountId, conversationId, messageId, reaction);
}
void
CallbacksHandler::slotReactionRemoved(const QString& accountId,
const QString& conversationId,
const QString& messageId,
const QString& reactionId)
{
Q_EMIT reactionRemoved(accountId, conversationId, messageId, reactionId);
}
void
CallbacksHandler::slotConversationProfileUpdated(const QString& accountId,
const QString& conversationId,

View file

@ -22,6 +22,8 @@
#include "api/datatransfer.h"
#include "qtwrapper/conversions_wrap.hpp"
#include <conversation_interface.h>
#include <QObject>
#include <memory>
@ -335,17 +337,28 @@ Q_SIGNALS:
* @param code
*/
void remoteRecordingChanged(const QString& callId, const QString& peerNumber, bool state);
void conversationLoaded(uint32_t requestId,
const QString& accountId,
const QString& conversationId,
const VectorMapStringString& messages);
void swarmLoaded(uint32_t requestId,
const QString& accountId,
const QString& conversationId,
const VectorSwarmMessage& messages);
void messagesFound(uint32_t requestId,
const QString& accountId,
const QString& conversationId,
const VectorMapStringString& messages);
void messageReceived(const QString& accountId,
const QString& conversationId,
const MapStringString& message);
const SwarmMessage& message);
void messageUpdated(const QString& accountId,
const QString& conversationId,
const SwarmMessage& message);
void reactionAdded(const QString& accountId,
const QString& conversationId,
const QString& messageId,
const MapStringString& reaction);
void reactionRemoved(const QString& accountId,
const QString& conversationId,
const QString& messageId,
const QString& reactionId);
void conversationProfileUpdated(const QString& accountId,
const QString& conversationId,
const MapStringString& profile);
@ -643,17 +656,28 @@ private Q_SLOTS:
* @param state, new state
*/
void slotRemoteRecordingChanged(const QString& callId, const QString& contactId, bool state);
void slotConversationLoaded(uint32_t requestId,
const QString& accountId,
const QString& conversationId,
const VectorMapStringString& messages);
void slotSwarmLoaded(uint32_t requestId,
const QString& accountId,
const QString& conversationId,
const VectorSwarmMessage& messages);
void slotMessagesFound(uint32_t requestId,
const QString& accountId,
const QString& conversationId,
const VectorMapStringString& messages);
void slotMessageReceived(const QString& accountId,
const QString& conversationId,
const MapStringString& message);
const SwarmMessage& message);
void slotMessageUpdated(const QString& accountId,
const QString& conversationId,
const SwarmMessage& message);
void slotReactionAdded(const QString& accountId,
const QString& conversationId,
const QString& messageId,
const MapStringString& reaction);
void slotReactionRemoved(const QString& accountId,
const QString& conversationId,
const QString& messageId,
const QString& reactionId);
void slotConversationProfileUpdated(const QString& accountId,
const QString& conversationId,
const MapStringString& message);

View file

@ -914,7 +914,7 @@ CallModel::getFormattedCallDuration(const QString& callId) const
auto d = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()
- startTime.time_since_epoch())
.count();
return authority::storage::getFormattedCallDuration(d);
return interaction::getFormattedCallDuration(d);
}
bool

View file

@ -215,11 +215,6 @@ public:
// filter out ourself from conversation participants.
const VectorString peersForConversation(const conversation::Info& conversation) const;
// insert swarm interactions. Return false if interaction already exists.
bool insertSwarmInteraction(const QString& interactionId,
interaction::Info& interaction,
conversation::Info& conversation,
bool insertAtBegin);
void invalidateModel();
void emplaceBackConversation(conversation::Info&& conversation);
void eraseConversation(const QString& convId);
@ -350,10 +345,10 @@ public Q_SLOTS:
datatransfer::Info info,
interaction::Status newStatus,
bool& updated);
void slotConversationLoaded(uint32_t requestId,
const QString& accountId,
const QString& conversationId,
const VectorMapStringString& messages);
void slotSwarmLoaded(uint32_t requestId,
const QString& accountId,
const QString& conversationId,
const VectorSwarmMessage& messages);
/**
* Listen messageFound signal.
* Is the search response from MessagesAdapter::getConvMedias()
@ -368,7 +363,18 @@ public Q_SLOTS:
const VectorMapStringString& messages);
void slotMessageReceived(const QString& accountId,
const QString& conversationId,
const MapStringString& message);
const SwarmMessage& message);
void slotMessageUpdated(const QString& accountId,
const QString& conversationId,
const SwarmMessage& message);
void slotReactionAdded(const QString& accountId,
const QString& conversationId,
const QString& messageId,
const MapStringString& reaction);
void slotReactionRemoved(const QString& accountId,
const QString& conversationId,
const QString& messageId,
const QString& reactionId);
void slotConversationProfileUpdated(const QString& accountId,
const QString& conversationId,
const MapStringString& profile);
@ -1149,6 +1155,16 @@ ConversationModel::notificationsCount() const
return notificationsCount;
}
void
ConversationModel::reloadHistory() const
{
std::for_each(pimpl_->conversations.begin(), pimpl_->conversations.end(), [&](const auto& c) {
c.interactions->reloadHistory();
Q_EMIT conversationUpdated(c.uid);
Q_EMIT dataChanged(pimpl_->indexOf(c.uid));
});
}
QString
ConversationModel::title(const QString& conversationId) const
{
@ -1345,7 +1361,6 @@ ConversationModel::sendMessage(const QString& uid, const QString& body, const QS
return;
}
newConv.lastMessageUid = msgId;
newConv.lastSelfMessageId = msgId;
// Emit this signal for chatview in the client
Q_EMIT newInteraction(convId, msgId, msg);
@ -1501,101 +1516,6 @@ ConversationModel::clearHistory(const QString& uid)
Q_EMIT dataChanged(conversationIdx);
}
void
ConversationModel::clearInteractionFromConversation(const QString& convId,
const QString& interactionId)
{
auto conversationIdx = pimpl_->indexOf(convId);
if (conversationIdx == -1)
return;
auto erased_keys = 0;
bool lastInteractionUpdated = false;
bool updateDisplayedUid = false;
QString newDisplayedUid = 0;
QString participantURI = "";
{
std::lock_guard<std::mutex> lk(pimpl_->interactionsLocks[convId]);
try {
auto& conversation = pimpl_->conversations.at(conversationIdx);
if (conversation.isSwarm()) {
// WARNING: clearInteractionFromConversation not implemented for swarm
return;
}
storage::clearInteractionFromConversation(pimpl_->db, convId, interactionId);
erased_keys = conversation.interactions->erase(interactionId);
participantURI = pimpl_->peersForConversation(conversation).front();
auto messageId = conversation.interactions->getRead(participantURI);
if (messageId != "" && messageId == interactionId) {
for (auto iter = conversation.interactions->find(interactionId);
iter != conversation.interactions->end();
--iter) {
if (isOutgoing(iter->second) && iter->first != interactionId) {
newDisplayedUid = iter->first;
break;
}
}
updateDisplayedUid = true;
conversation.interactions->setRead(participantURI, newDisplayedUid);
}
if (conversation.lastMessageUid == interactionId) {
// Update lastMessageUid
auto newLastId = QString::number(0);
if (!conversation.interactions->empty())
newLastId = conversation.interactions->rbegin()->first;
conversation.lastMessageUid = newLastId;
lastInteractionUpdated = true;
}
if (conversation.lastSelfMessageId == interactionId) {
conversation.lastSelfMessageId = conversation.interactions->lastSelfMessageId(
owner.profileInfo.uri);
}
} catch (const std::out_of_range& e) {
qDebug() << "can't clear interaction from conversation: " << e.what();
}
}
if (updateDisplayedUid) {
Q_EMIT displayedInteractionChanged(convId, participantURI, interactionId, newDisplayedUid);
}
if (erased_keys > 0) {
pimpl_->filteredConversations.invalidate();
Q_EMIT interactionRemoved(convId, interactionId);
}
if (lastInteractionUpdated) {
// last interaction as changed, so the order can change.
Q_EMIT modelChanged();
Q_EMIT dataChanged(conversationIdx);
}
}
void
ConversationModel::clearInteractionsCache(const QString& convId)
{
auto conversationIdx = pimpl_->indexOf(convId);
if (conversationIdx == -1)
return;
try {
auto& conversation = pimpl_->conversations.at(conversationIdx);
if (!conversation.isRequest && !conversation.needsSyncing && conversation.isSwarm()) {
{
std::lock_guard<std::mutex> lk(pimpl_->interactionsLocks[conversation.uid]);
conversation.interactions->clear();
}
conversation.allMessagesLoaded = false;
conversation.lastMessageUid = "";
conversation.lastSelfMessageId = "";
ConfigurationManager::instance().loadConversationMessages(owner.id, convId, "", 1);
}
} catch (const std::out_of_range& e) {
qDebug() << "can't find interaction from conversation: " << e.what();
return;
}
}
bool
ConversationModel::isLastDisplayed(const QString& convId,
const QString& interactionId,
@ -1689,29 +1609,10 @@ ConversationModel::loadConversationMessages(const QString& conversationId, const
}
auto lastMsgId = conversation.interactions->empty() ? ""
: conversation.interactions->front().first;
return ConfigurationManager::instance().loadConversationMessages(owner.id,
conversationId,
lastMsgId,
size);
}
int
ConversationModel::loadConversationUntil(const QString& conversationId, const QString& to)
{
auto conversationOpt = getConversationForUid(conversationId);
if (!conversationOpt.has_value()) {
return -1;
}
auto& conversation = conversationOpt->get();
if (conversation.allMessagesLoaded) {
return -1;
}
auto lastMsgId = conversation.interactions->empty() ? ""
: conversation.interactions->front().first;
return ConfigurationManager::instance().loadConversationUntil(owner.id,
conversationId,
lastMsgId,
to);
return ConfigurationManager::instance().loadConversation(owner.id,
conversationId,
lastMsgId,
size);
}
void
@ -1892,9 +1793,9 @@ ConversationModelPimpl::ConversationModelPimpl(const ConversationModel& linked,
&ConversationModelPimpl::slotTransferStatusUnjoinable);
// swarm conversations
connect(&callbacksHandler,
&CallbacksHandler::conversationLoaded,
&CallbacksHandler::swarmLoaded,
this,
&ConversationModelPimpl::slotConversationLoaded);
&ConversationModelPimpl::slotSwarmLoaded);
connect(&callbacksHandler,
&CallbacksHandler::messagesFound,
this,
@ -1903,6 +1804,18 @@ ConversationModelPimpl::ConversationModelPimpl(const ConversationModel& linked,
&CallbacksHandler::messageReceived,
this,
&ConversationModelPimpl::slotMessageReceived);
connect(&callbacksHandler,
&CallbacksHandler::messageUpdated,
this,
&ConversationModelPimpl::slotMessageUpdated);
connect(&callbacksHandler,
&CallbacksHandler::reactionAdded,
this,
&ConversationModelPimpl::slotReactionAdded);
connect(&callbacksHandler,
&CallbacksHandler::reactionRemoved,
this,
&ConversationModelPimpl::slotReactionRemoved);
connect(&callbacksHandler,
&CallbacksHandler::conversationProfileUpdated,
this,
@ -2044,9 +1957,9 @@ ConversationModelPimpl::~ConversationModelPimpl()
&ConversationModelPimpl::slotTransferStatusUnjoinable);
// swarm conversations
disconnect(&callbacksHandler,
&CallbacksHandler::conversationLoaded,
&CallbacksHandler::swarmLoaded,
this,
&ConversationModelPimpl::slotConversationLoaded);
&ConversationModelPimpl::slotSwarmLoaded);
disconnect(&callbacksHandler,
&CallbacksHandler::messagesFound,
this,
@ -2055,6 +1968,18 @@ ConversationModelPimpl::~ConversationModelPimpl()
&CallbacksHandler::messageReceived,
this,
&ConversationModelPimpl::slotMessageReceived);
disconnect(&callbacksHandler,
&CallbacksHandler::messageUpdated,
this,
&ConversationModelPimpl::slotMessageUpdated);
disconnect(&callbacksHandler,
&CallbacksHandler::reactionAdded,
this,
&ConversationModelPimpl::slotReactionAdded);
disconnect(&callbacksHandler,
&CallbacksHandler::reactionRemoved,
this,
&ConversationModelPimpl::slotReactionRemoved);
disconnect(&callbacksHandler,
&CallbacksHandler::conversationProfileUpdated,
this,
@ -2332,8 +2257,8 @@ ConversationModelPimpl::sort(const conversation::Info& convA, const conversation
return true;
// Sort by last Interaction
try {
auto lastMessageA = historyA->at(convA.lastMessageUid);
auto lastMessageB = historyB->at(convB.lastMessageUid);
auto lastMessageA = historyA->rbegin()->second;
auto lastMessageB = historyB->rbegin()->second;
return lastMessageA.timestamp > lastMessageB.timestamp;
} catch (const std::exception& e) {
qDebug() << "ConversationModel::sortConversations(), can't get lastMessage";
@ -2353,38 +2278,26 @@ ConversationModelPimpl::sendContactRequest(const QString& contactUri)
} catch (std::out_of_range& e) {
}
}
void
ConversationModelPimpl::slotConversationLoaded(uint32_t requestId,
const QString& accountId,
const QString& conversationId,
const VectorMapStringString& messages)
ConversationModelPimpl::slotSwarmLoaded(uint32_t requestId,
const QString& accountId,
const QString& conversationId,
const VectorSwarmMessage& messages)
{
if (accountId != linked.owner.id) {
if (accountId != linked.owner.id)
return;
}
auto allLoaded = messages.size() == 0;
auto allLoaded = false;
try {
auto& conversation = getConversationForUid(conversationId).get();
QString oldLast, oldBegin; // Used to detect loading loops just in case.
if (conversation.interactions->size() != 0) {
oldBegin = conversation.interactions->begin()->first;
oldLast = conversation.interactions->rbegin()->first;
}
for (const auto& message : messages) {
if (message["type"].isEmpty()) {
continue;
}
auto msgId = message["id"];
QString msgId = message.id;
auto msg = interaction::Info(message, linked.owner.profileInfo.uri);
conversation.interactions->editMessage(msgId, msg);
conversation.interactions->reactToMessage(msgId, msg);
auto downloadFile = false;
if (msg.type == interaction::Type::INITIAL) {
allLoaded = true;
} else if (msg.type == interaction::Type::DATA_TRANSFER) {
auto fileId = message["fileId"];
QString fileId = message.body.value("fileId");
QString path;
qlonglong bytesProgress, totalSize;
linked.owner.dataTransferModel->fileTransferInfo(accountId,
@ -2404,68 +2317,43 @@ ConversationModelPimpl::slotConversationLoaded(uint32_t requestId,
: interaction::Status::TRANSFER_ONGOING;
linked.owner.dataTransferModel->registerTransferId(fileId, msgId);
downloadFile = (bytesProgress == 0);
} else if (msg.type == interaction::Type::CALL) {
msg.body = storage::getCallInteractionString(msg.authorUri
== linked.owner.profileInfo.uri,
msg);
} else if (msg.type == interaction::Type::CONTACT) {
auto bestName = msg.authorUri == linked.owner.profileInfo.uri
? linked.owner.accountModel->bestNameForAccount(linked.owner.id)
: linked.owner.contactModel->bestNameForContact(msg.authorUri);
msg.body = interaction::getContactInteractionString(bestName,
interaction::to_action(
message["action"]));
} else if (msg.type == interaction::Type::EDITED) {
conversation.interactions->addEdition(msgId, msg, false);
} else if (msg.type == interaction::Type::REACTION) {
conversation.interactions->addReaction(msg.react_to, msgId);
}
insertSwarmInteraction(msgId, msg, conversation, true);
{
// If message is loaded, insert message at beginning
std::lock_guard<std::mutex> lk(interactionsLocks[conversation.uid]);
auto itExists = conversation.interactions->find(msgId);
// If found, nothing to do.
if (itExists != conversation.interactions->end())
continue;
auto result = conversation.interactions->insert(std::make_pair(msgId, msg), true);
if (!result.second) {
continue;
}
}
if (downloadFile) {
// Note, we must do this after insertSwarmInteraction to find the interaction
handleIncomingFile(conversationId, msgId, message["totalSize"].toInt());
handleIncomingFile(conversationId,
msgId,
QString(message.body.value("totalSize")).toInt());
}
}
conversation.lastMessageUid = conversation.interactions->lastMessageUid();
conversation.lastSelfMessageId = conversation.interactions->lastSelfMessageId(
linked.owner.profileInfo.uri);
if (conversation.lastMessageUid.isEmpty() && !conversation.allMessagesLoaded
&& messages.size() != 0) {
if (conversation.interactions->size() > 0) {
QString newLast, newBegin;
if (conversation.interactions->size() > 0) {
newBegin = conversation.interactions->begin()->first;
newLast = conversation.interactions->rbegin()->first;
}
if (newLast == oldLast && !newLast.isEmpty() && newBegin == oldBegin
&& !newBegin.isEmpty()) { // [[unlikely]] in c++20
qCritical() << "Loading loop detected for " << conversationId << "(" << newBegin
<< " ; " << newLast << ")";
return;
}
}
// In this case, we only have loaded merge commits. Load more messages
ConfigurationManager::instance().loadConversationMessages(linked.owner.id,
conversationId,
messages.rbegin()->value(
"id"),
2);
return;
}
invalidateModel();
Q_EMIT linked.modelChanged();
Q_EMIT linked.newMessagesAvailable(linked.owner.id, conversationId);
auto conversationIdx = indexOf(conversationId);
Q_EMIT linked.dataChanged(conversationIdx);
Q_EMIT linked.conversationMessagesLoaded(requestId, conversationId);
if (allLoaded) {
conversation.allMessagesLoaded = true;
Q_EMIT linked.conversationUpdated(conversationId);
}
} catch (const std::exception& e) {
qDebug() << "messages loaded for not existing conversation";
qWarning() << e.what();
}
}
@ -2508,35 +2396,27 @@ ConversationModelPimpl::slotMessagesFound(uint32_t requestId,
void
ConversationModelPimpl::slotMessageReceived(const QString& accountId,
const QString& conversationId,
const MapStringString& message)
const SwarmMessage& message)
{
if (accountId != linked.owner.id) {
if (accountId != linked.owner.id)
return;
}
try {
auto& conversation = getConversationForUid(conversationId).get();
if (message["type"].isEmpty() || message["type"] == "application/update-profile") {
return;
}
if (message["type"] == "initial") {
if (message.type == "initial") {
conversation.allMessagesLoaded = true;
Q_EMIT linked.conversationUpdated(conversationId);
if (message.find("invited") == message.end()) {
if (message.body.find("invited") == message.body.end()) {
return;
}
}
auto msgId = message["id"];
QString msgId = message.id;
auto msg = interaction::Info(message, linked.owner.profileInfo.uri);
conversation.interactions->editMessage(msgId, msg);
api::datatransfer::Info info;
QString fileId;
auto updateUnread = false;
if (msg.type == interaction::Type::DATA_TRANSFER) {
// save data transfer interaction to db and assosiate daemon id with interaction id,
// conversation id and db id
QString fileId = message["fileId"];
QString fileId = message.body.value("fileId");
QString path;
qlonglong bytesProgress, totalSize;
linked.owner.dataTransferModel->fileTransferInfo(accountId,
@ -2555,60 +2435,32 @@ ConversationModelPimpl::slotMessageReceived(const QString& accountId,
: bytesProgress == totalSize ? interaction::Status::TRANSFER_FINISHED
: interaction::Status::TRANSFER_ONGOING;
linked.owner.dataTransferModel->registerTransferId(fileId, msgId);
if (msg.authorUri != linked.owner.profileInfo.uri) {
updateUnread = true;
}
} else if (msg.type == interaction::Type::CALL) {
// If we're a call in a swarm
if (msg.authorUri != linked.owner.profileInfo.uri)
updateUnread = true;
msg.body = storage::getCallInteractionString(msg.authorUri
== linked.owner.profileInfo.uri,
msg);
} else if (msg.type == interaction::Type::CONTACT) {
auto bestName = msg.authorUri == linked.owner.profileInfo.uri
? linked.owner.accountModel->bestNameForAccount(linked.owner.id)
: linked.owner.contactModel->bestNameForContact(msg.authorUri);
msg.body = interaction::getContactInteractionString(bestName,
interaction::to_action(
message["action"]));
if (msg.authorUri != linked.owner.profileInfo.uri) {
updateUnread = true;
}
} else if (msg.type == interaction::Type::TEXT) {
if (msg.authorUri != linked.owner.profileInfo.uri) {
updateUnread = true;
}
} else if (msg.type == interaction::Type::REACTION) {
conversation.interactions->addReaction(msg.react_to, msgId);
} else if (msg.type == interaction::Type::EDITED) {
conversation.interactions->addEdition(msgId, msg, true);
}
if (!insertSwarmInteraction(msgId, msg, conversation, false)) {
// message already exists
return;
}
// once the reaction is added to interactions, we can update the reacted
// message
if (msg.type == interaction::Type::REACTION) {
auto reactInteraction = conversation.interactions->find(msg.react_to);
if (reactInteraction != conversation.interactions->end()) {
conversation.interactions->reactToMessage(msg.react_to, reactInteraction->second);
{
// If message is received, insert message after its parent.
std::lock_guard<std::mutex> lk(interactionsLocks[conversation.uid]);
auto itExists = conversation.interactions->find(msgId);
// If found, nothing to do.
if (itExists != conversation.interactions->end())
return;
int index = conversation.interactions->indexOfMessage(msg.parentId);
if (index >= 0) {
auto result = conversation.interactions->insert(index + 1, qMakePair(msgId, msg));
if (!result.second) {
return;
}
} else {
return;
}
}
if (updateUnread) {
auto updateUnread = msg.authorUri != linked.owner.profileInfo.uri;
if (updateUnread)
conversation.unreadMessages++;
}
if (msg.type == interaction::Type::MERGE) {
invalidateModel();
return;
}
conversation.lastMessageUid = conversation.interactions->lastMessageUid();
conversation.lastSelfMessageId = conversation.interactions->lastSelfMessageId(
linked.owner.profileInfo.uri);
invalidateModel();
if (!interaction::isOutgoing(msg)) {
if (!interaction::isOutgoing(msg) && updateUnread) {
Q_EMIT behaviorController.newUnreadInteraction(linked.owner.id,
conversationId,
msgId,
@ -2616,8 +2468,10 @@ ConversationModelPimpl::slotMessageReceived(const QString& accountId,
}
Q_EMIT linked.newInteraction(conversationId, msgId, msg);
Q_EMIT linked.modelChanged();
if (msg.status == interaction::Status::TRANSFER_AWAITING_HOST) {
handleIncomingFile(conversationId, msgId, message["totalSize"].toInt());
if (msg.status == interaction::Status::TRANSFER_AWAITING_HOST && updateUnread) {
handleIncomingFile(conversationId,
msgId,
QString(message.body.value("totalSize")).toInt());
}
Q_EMIT linked.dataChanged(indexOf(conversationId));
} catch (const std::exception& e) {
@ -2625,6 +2479,82 @@ ConversationModelPimpl::slotMessageReceived(const QString& accountId,
}
}
void
ConversationModelPimpl::slotMessageUpdated(const QString& accountId,
const QString& conversationId,
const SwarmMessage& message)
{
if (accountId != linked.owner.id)
return;
try {
auto& conversation = getConversationForUid(conversationId).get();
QString msgId = message.id;
auto msg = interaction::Info(message, linked.owner.profileInfo.uri);
{
std::lock_guard<std::mutex> lk(interactionsLocks[conversation.uid]);
auto itExists = conversation.interactions->find(msgId);
// If not found, nothing to do.
if (itExists == conversation.interactions->end())
return;
// Now there is two cases:
// ParentId changed, in this case, remove previous message and re-insert at new place
// Else, just update body
conversation.interactions->erase(itExists);
int index = conversation.interactions->indexOfMessage(msg.parentId);
if (index >= 0) {
auto result = conversation.interactions->insert(index + 1, qMakePair(msgId, msg));
if (!result.second) {
return;
}
} else {
return;
}
}
invalidateModel();
Q_EMIT linked.modelChanged();
Q_EMIT linked.dataChanged(indexOf(conversationId));
} catch (const std::exception& e) {
qDebug() << "messages received for not existing conversation";
}
}
void
ConversationModelPimpl::slotReactionAdded(const QString& accountId,
const QString& conversationId,
const QString& messageId,
const MapStringString& reaction)
{
if (accountId != linked.owner.id) {
return;
}
try {
// qInfo() << "Add Reaction to " << messageId << " in " << conversationId;
auto& conversation = getConversationForUid(conversationId).get();
conversation.interactions->addReaction(messageId, reaction);
} catch (const std::exception& e) {
qWarning() << e.what();
}
}
void
ConversationModelPimpl::slotReactionRemoved(const QString& accountId,
const QString& conversationId,
const QString& messageId,
const QString& reactionId)
{
if (accountId != linked.owner.id) {
return;
}
try {
// qInfo() << "Remove Reaction from " << messageId << " in " << conversationId;
auto& conversation = getConversationForUid(conversationId).get();
conversation.interactions->rmReaction(messageId, reactionId);
} catch (const std::exception& e) {
qWarning() << e.what();
}
}
void
ConversationModelPimpl::slotConversationProfileUpdated(const QString& accountId,
const QString& conversationId,
@ -2641,51 +2571,6 @@ ConversationModelPimpl::slotConversationProfileUpdated(const QString& accountId,
}
}
bool
ConversationModelPimpl::insertSwarmInteraction(const QString& interactionId,
interaction::Info& interaction,
conversation::Info& conversation,
bool insertAtBegin)
{
std::lock_guard<std::mutex> lk(interactionsLocks[conversation.uid]);
auto itExists = conversation.interactions->find(interactionId);
if (itExists != conversation.interactions->end()) {
// Erase interaction if exists, as it will be updated via a re-insertion
if (itExists->second.previousBodies.size() != 0) {
// If the message was edited, we should keep this state
interaction.body = itExists->second.body;
interaction.previousBodies = itExists->second.previousBodies;
}
itExists = conversation.interactions->erase(itExists);
if (itExists != conversation.interactions->end()) {
// next interaction doesn't have parent anymore.
conversation.parentsId[itExists->first] = interactionId;
}
}
int index = conversation.interactions->indexOfMessage(interaction.parentId);
if (index >= 0) {
auto result = conversation.interactions->insert(index + 1,
qMakePair(interactionId, interaction));
if (!result.second)
return false;
} else {
auto result = conversation.interactions->insert(std::make_pair(interactionId, interaction),
insertAtBegin);
if (!result.second)
return false;
if (!interaction.parentId.isEmpty())
conversation.parentsId[interactionId] = interaction.parentId;
}
if (!conversation.parentsId.values().contains(interactionId)) {
return true;
}
auto msgIds = conversation.parentsId.keys(interactionId);
conversation.interactions->moveMessages(msgIds, interactionId);
for (auto& msg : msgIds)
conversation.parentsId.remove(msg);
return true;
}
void
ConversationModelPimpl::slotConversationRequestReceived(const QString& accountId,
const QString&,
@ -2749,10 +2634,7 @@ ConversationModelPimpl::slotConversationReady(const QString& accountId,
conversation.needsSyncing = false;
Q_EMIT linked.conversationUpdated(conversationId);
Q_EMIT linked.dataChanged(conversationIdx);
ConfigurationManager::instance().loadConversationMessages(linked.owner.id,
conversationId,
"",
0);
ConfigurationManager::instance().loadConversation(linked.owner.id, conversationId, "", 0);
auto& peers = peersForConversation(conversation);
if (peers.size() == 1)
Q_EMIT linked.conversationReady(conversationId, peers.front());
@ -2895,7 +2777,6 @@ ConversationModelPimpl::slotActiveCallsChanged(const QString& accountId,
void
ConversationModelPimpl::slotContactAdded(const QString& contactUri)
{
QString convId;
try {
convId = linked.owner.contactModel->getContact(contactUri).conversationId;
@ -2904,14 +2785,15 @@ ConversationModelPimpl::slotContactAdded(const QString& contactUri)
}
auto isSwarm = !convId.isEmpty();
auto conv = !isSwarm? storage::getConversationsWithPeer(db, contactUri) : VectorString {convId};
auto conv = !isSwarm ? storage::getConversationsWithPeer(db, contactUri)
: VectorString {convId};
if (conv.isEmpty()) {
if (linked.owner.profileInfo.type == profile::Type::SIP) {
auto convId = storage::beginConversationWithPeer(db,
contactUri,
true,
linked.owner.contactModel->getAddedTs(
contactUri));
contactUri,
true,
linked.owner.contactModel->getAddedTs(
contactUri));
addConversationWith(convId, contactUri, false);
Q_EMIT linked.conversationReady(convId, contactUri);
Q_EMIT linked.newConversation(convId);
@ -2922,7 +2804,7 @@ ConversationModelPimpl::slotContactAdded(const QString& contactUri)
try {
auto& conversation = getConversationForUid(convId).get();
MapStringString details = ConfigurationManager::instance()
.conversationInfos(linked.owner.id, conversation.uid);
.conversationInfos(linked.owner.id, conversation.uid);
bool needsSyncing = details["syncing"] == "true";
if (conversation.needsSyncing != needsSyncing) {
conversation.isRequest = false;
@ -2948,9 +2830,7 @@ ConversationModelPimpl::addContactRequest(const QString& contactUri)
return;
} catch (std::out_of_range&) {
// no conversation exists. Add contact request
conversation::Info conversation;
conversation.uid = contactUri;
conversation.accountId = linked.owner.id;
conversation::Info conversation(contactUri, &linked.owner);
conversation.participants = {{contactUri, member::Role::INVITED}};
conversation.mode = conversation::Mode::NON_SWARM;
conversation.isRequest = true;
@ -2974,12 +2854,10 @@ ConversationModelPimpl::addConversationRequest(const MapStringString& convReques
QString callId, confId;
const MapStringString& details = ConfigurationManager::instance()
.conversationInfos(linked.owner.id, convId);
conversation::Info conversation;
conversation.uid = convId;
conversation::Info conversation(convId, &linked.owner);
conversation.infos = details;
conversation.callId = callId;
conversation.confId = confId;
conversation.accountId = linked.owner.id;
conversation.participants = {{linked.owner.profileInfo.uri, member::Role::INVITED},
{peerUri, member::Role::MEMBER}};
conversation.mode = mode;
@ -2993,8 +2871,10 @@ ConversationModelPimpl::addConversationRequest(const MapStringString& convReques
};
auto msg = interaction::Info(messageMap, linked.owner.profileInfo.uri);
insertSwarmInteraction(convId, msg, conversation, true);
conversation.lastMessageUid = convId;
{
std::lock_guard<std::mutex> lk(interactionsLocks[convId]);
conversation.interactions->insert(std::make_pair(convId, msg), true);
}
// add the author to the contact model's contact list as a PENDING
// if they aren't already a contact
@ -3023,7 +2903,7 @@ ConversationModelPimpl::addConversationRequest(const MapStringString& convReques
Q_EMIT linked.modelChanged();
if (!callId.isEmpty()) {
// If we replace a non swarm request by a swarm request while having a call.
Q_EMIT linked.selectConversation(convId);
linked.selectConversation(convId);
}
if (emitToClient)
Q_EMIT behaviorController.newTrustRequest(linked.owner.id, convId, peerUri);
@ -3032,7 +2912,7 @@ ConversationModelPimpl::addConversationRequest(const MapStringString& convReques
void
ConversationModelPimpl::slotPendingContactAccepted(const QString& uri)
{
auto type = linked.owner.profileInfo.type;
profile::Type type;
try {
type = linked.owner.contactModel->getContact(uri).profileInfo.type;
} catch (std::out_of_range& e) {
@ -3110,16 +2990,14 @@ ConversationModelPimpl::slotContactModelUpdated(const QString& uri)
searchResults.clear();
auto users = linked.owner.contactModel->getSearchResults();
for (auto& user : users) {
conversation::Info conversationInfo;
auto uid = linked.owner.profileInfo.type == profile::Type::SIP ? "SEARCHSIP"
: user.profileInfo.uri;
conversation::Info conversationInfo(uid, &linked.owner);
// For SIP, we always got one search result, so "" is ok as there is no empty uri
// For Jami accounts, the nameserver can return several results, so we use the uniqueness of
// the id as id for a temporary conversation.
conversationInfo.uid = linked.owner.profileInfo.type == profile::Type::SIP
? "SEARCHSIP"
: user.profileInfo.uri;
conversationInfo.participants.append(
member::Member {user.profileInfo.uri, member::Role::MEMBER});
conversationInfo.accountId = linked.owner.id;
searchResults.emplace_front(std::move(conversationInfo));
}
Q_EMIT linked.searchResultUpdated();
@ -3129,6 +3007,11 @@ ConversationModelPimpl::slotContactModelUpdated(const QString& uri)
void
ConversationModelPimpl::addSwarmConversation(const QString& convId)
{
if (Lrc::dbusIsValid()) {
// Because the daemon may have already loaded interactions
// we clear them to receive all signals
ConfigurationManager::instance().clearCache(linked.owner.id, convId);
}
QVector<member::Member> participants;
const VectorMapStringString& members = ConfigurationManager::instance()
.getConversationMembers(linked.owner.id, convId);
@ -3137,10 +3020,8 @@ ConversationModelPimpl::addSwarmConversation(const QString& convId)
const MapStringString& details = ConfigurationManager::instance()
.conversationInfos(linked.owner.id, convId);
auto mode = conversation::to_mode(details["mode"].toInt());
conversation::Info conversation;
conversation::Info conversation(convId, &linked.owner);
conversation.infos = details;
conversation.uid = convId;
conversation.accountId = linked.owner.id;
VectorMapStringString activeCalls = ConfigurationManager::instance()
.getActiveCalls(linked.owner.id, convId);
conversation.activeCalls = activeCalls;
@ -3212,14 +3093,16 @@ ConversationModelPimpl::addSwarmConversation(const QString& convId)
};
auto msg = interaction::Info(messageMap, linked.owner.profileInfo.uri);
insertSwarmInteraction(convId, msg, conversation, true);
conversation.lastMessageUid = convId;
{
std::lock_guard<std::mutex> lk(interactionsLocks[convId]);
conversation.interactions->insert(std::make_pair(convId, msg), true);
}
conversation.needsSyncing = true;
Q_EMIT linked.conversationUpdated(conversation.uid);
Q_EMIT linked.dataChanged(indexOf(conversation.uid));
}
emplaceBackConversation(std::move(conversation));
ConfigurationManager::instance().loadConversationMessages(linked.owner.id, convId, "", 1);
ConfigurationManager::instance().loadConversation(linked.owner.id, convId, "", 1);
}
void
@ -3227,9 +3110,7 @@ ConversationModelPimpl::addConversationWith(const QString& convId,
const QString& contactUri,
bool isRequest)
{
conversation::Info conversation;
conversation.uid = convId;
conversation.accountId = linked.owner.id;
conversation::Info conversation(convId, &linked.owner);
conversation.participants = {{contactUri, member::Role::MEMBER}};
conversation.mode = conversation::Mode::NON_SWARM;
conversation.needsSyncing = false;
@ -3510,14 +3391,14 @@ ConversationModelPimpl::addOrUpdateCallMessage(const QString& callId,
// update the db
auto msgId = storage::addOrUpdateMessage(db, conv_it->uid, msg, callId);
// now set the formatted call message string in memory only
msg.body = storage::getCallInteractionString(msg.authorUri == linked.owner.profileInfo.uri, msg);
msg.body = interaction::getCallInteractionString(msg.authorUri == linked.owner.profileInfo.uri,
msg);
bool newInteraction = false;
{
std::lock_guard<std::mutex> lk(interactionsLocks[conv_it->uid]);
auto interactionIt = conv_it->interactions->find(msgId);
newInteraction = interactionIt == conv_it->interactions->end();
if (newInteraction) {
conv_it->lastMessageUid = msgId;
conv_it->interactions->emplace(msgId, msg);
} else {
interactionIt->second = msg;
@ -3627,7 +3508,6 @@ ConversationModelPimpl::addIncomingMessage(const QString& peerId,
std::lock_guard<std::mutex> lk(interactionsLocks[conversations[conversationIdx].uid]);
conversations[conversationIdx].interactions->emplace(msgId, msg);
}
conversations[conversationIdx].lastMessageUid = msgId;
conversations[conversationIdx].unreadMessages = getNumberOfUnreadMessagesFor(convIds[0]);
}
@ -4115,7 +3995,6 @@ ConversationModelPimpl::slotTransferStatusCreated(const QString& fileId, datatra
std::lock_guard<std::mutex> lk(interactionsLocks[conversations[conversationIdx].uid]);
conversations[conversationIdx].interactions->emplace(interactionId, interaction);
}
conversations[conversationIdx].lastMessageUid = interactionId;
conversations[conversationIdx].unreadMessages = getNumberOfUnreadMessagesFor(convId);
}
Q_EMIT behaviorController.newUnreadInteraction(linked.owner.id,

View file

@ -42,6 +42,7 @@ Q_DECLARE_METATYPE(VectorString)
Q_DECLARE_METATYPE(MapStringVectorString)
Q_DECLARE_METATYPE(VectorVectorByte)
Q_DECLARE_METATYPE(DataTransferInfo)
Q_DECLARE_METATYPE(SwarmMessage)
Q_DECLARE_METATYPE(uint64_t)
Q_DECLARE_METATYPE(Message)
@ -86,6 +87,36 @@ operator>>(const QDBusArgument& argument, DataTransferInfo& info)
return argument;
}
static inline QDBusArgument&
operator<<(QDBusArgument& argument, const SwarmMessage& m)
{
argument.beginStructure();
argument << m.id;
argument << m.type;
argument << m.linearizedParent;
argument << m.body;
argument << m.reactions;
argument << m.editions;
argument.endStructure();
return argument;
}
static inline const QDBusArgument&
operator>>(const QDBusArgument& argument, SwarmMessage& m)
{
argument.beginStructure();
argument >> m.id;
argument >> m.type;
argument >> m.linearizedParent;
argument >> m.body;
argument >> m.reactions;
argument >> m.editions;
argument.endStructure();
return argument;
}
static inline QDBusArgument&
operator<<(QDBusArgument& argument, const Message& m)
{
@ -140,6 +171,10 @@ registerCommTypes()
qDBusRegisterMetaType<VectorVectorByte>();
qRegisterMetaType<DataTransferInfo>("DataTransferInfo");
qDBusRegisterMetaType<DataTransferInfo>();
qRegisterMetaType<SwarmMessage>("SwarmMessage");
qDBusRegisterMetaType<SwarmMessage>();
qRegisterMetaType<VectorSwarmMessage>("VectorSwarmMessage");
qDBusRegisterMetaType<VectorSwarmMessage>();
qRegisterMetaType<Message>("Message");
qDBusRegisterMetaType<Message>();
qRegisterMetaType<QVector<Message>>("QVector<Message>");

View file

@ -21,6 +21,9 @@
#include "messagelistmodel.h"
#include "authority/storagehelper.h"
#include "api/accountmodel.h"
#include "api/contactmodel.h"
#include "api/conversationmodel.h"
#include "api/interaction.h"
#include "qtwrapper/conversions_wrap.hpp"
@ -36,9 +39,9 @@ using constIterator = MessageListModel::constIterator;
using iterator = MessageListModel::iterator;
using reverseIterator = MessageListModel::reverseIterator;
MessageListModel::MessageListModel(QObject* parent)
MessageListModel::MessageListModel(const account::Info* account, QObject* parent)
: QAbstractListModel(parent)
, account_(account)
{}
QPair<iterator, bool>
@ -195,8 +198,16 @@ MessageListModel::clear()
Q_EMIT beginResetModel();
interactions_.clear();
replyTo_.clear();
editedBodies_.clear();
reactedMessages_.clear();
Q_EMIT endResetModel();
}
void
MessageListModel::reloadHistory()
{
Q_EMIT beginResetModel();
for (auto& interaction : interactions_) {
interaction.second.linkPreviewInfo.clear();
}
Q_EMIT endResetModel();
}
@ -266,54 +277,6 @@ MessageListModel::indexOfMessage(const QString& msgId, bool reverse) const
: getIndex(interactions_.begin(), interactions_.end());
}
void
MessageListModel::moveMessages(QList<QString> msgIds, const QString& parentId)
{
for (auto msgId : msgIds) {
moveMessage(msgId, parentId);
}
}
void
MessageListModel::moveMessage(const QString& msgId, const QString& parentId)
{
int currentIndex = indexOfMessage(msgId);
if (currentIndex == -1) {
qWarning() << "Incorrect index detected in MessageListModel::moveMessage";
return;
}
// if we have a next element check if it is a child interaction
QString childMessageIdToMove;
if (currentIndex < (interactions_.size() - 1)) {
const auto& next = interactions_.at(currentIndex + 1);
if (next.second.parentId == msgId) {
childMessageIdToMove = next.first;
}
}
auto endIdx = currentIndex;
auto pId = msgId;
// move a message
int newIndex = indexOfMessage(parentId) + 1;
if (newIndex >= interactions_.size()) {
newIndex = interactions_.size() - 1;
// If we can move all the messages after the current one, we can do it directly
childMessageIdToMove.clear();
endIdx = std::max(endIdx, newIndex - 1);
}
if (currentIndex == newIndex || newIndex == -1)
return;
// Pretty every messages is moved
moveMessages(currentIndex, endIdx, newIndex);
// move a child message
if (!childMessageIdToMove.isEmpty())
moveMessage(childMessageIdToMove, msgId);
}
void
MessageListModel::updateReplies(item_t& message)
{
@ -359,19 +322,6 @@ MessageListModel::removeMessage(int index, iterator it)
Q_EMIT endRemoveRows();
}
void
MessageListModel::moveMessages(int from, int last, int to)
{
if (last < from)
return;
QModelIndex sourceIndex = QAbstractListModel::index(from, 0);
QModelIndex destinationIndex = QAbstractListModel::index(to, 0);
Q_EMIT beginMoveRows(sourceIndex, from, last, destinationIndex, to);
for (int i = 0; i < (last - from); ++i)
interactions_.move(last, to);
Q_EMIT endMoveRows();
}
bool
MessageListModel::contains(const QString& msgId)
{
@ -433,8 +383,26 @@ MessageListModel::dataForItem(item_t item, int, int role) const
return QVariant(item.first);
case Role::Author:
return QVariant(item.second.authorUri);
case Role::Body:
case Role::Body: {
if (account_) {
if (item.second.type == lrc::api::interaction::Type::CALL) {
return QVariant(
interaction::getCallInteractionString(item.second.authorUri
== account_->profileInfo.uri,
item.second));
} else if (item.second.type == lrc::api::interaction::Type::CONTACT) {
auto bestName = item.second.authorUri == account_->profileInfo.uri
? account_->accountModel->bestNameForAccount(account_->id)
: account_->contactModel->bestNameForContact(
item.second.authorUri);
return QVariant(
interaction::getContactInteractionString(bestName,
interaction::to_action(
item.second.commit["action"])));
}
}
return QVariant(item.second.body);
}
case Role::Timestamp:
return QVariant::fromValue(item.second.timestamp);
case Role::Duration:
@ -544,6 +512,45 @@ MessageListModel::addHyperlinkInfo(const QString& messageId, const QVariantMap&
Q_EMIT dataChanged(modelIndex, modelIndex, {Role::LinkPreviewInfo});
}
void
MessageListModel::addReaction(const QString& messageId, const MapStringString& reaction)
{
int index = getIndexOfMessage(messageId);
if (index == -1)
return;
QModelIndex modelIndex = QAbstractListModel::index(index, 0);
auto emoji = api::interaction::Emoji {reaction["id"], reaction["body"]};
auto& pList = interactions_[index].second.reactions[reaction["author"]];
QList<QVariant> newList = pList.toList();
newList.emplace_back(QVariant::fromValue(emoji));
pList = QVariantList::fromVector(newList);
Q_EMIT dataChanged(modelIndex, modelIndex, {Role::Reactions});
}
void
MessageListModel::rmReaction(const QString& messageId, const QString& reactionId)
{
int index = getIndexOfMessage(messageId);
if (index == -1)
return;
QModelIndex modelIndex = QAbstractListModel::index(index, 0);
auto& reactions = interactions_[index].second.reactions;
for (const auto& key : reactions.keys()) {
QList<QVariant> emojis = reactions[key].toList();
for (auto it = emojis.begin(); it != emojis.end(); ++it) {
auto emoji = it->value<api::interaction::Emoji>();
if (emoji.commitId == reactionId) {
emojis.erase(it);
reactions[key] = emojis;
Q_EMIT dataChanged(modelIndex, modelIndex, {Role::Reactions});
return;
}
}
}
}
void
MessageListModel::setParsedMessage(const QString& messageId, const QString& parsed)
{
@ -610,147 +617,6 @@ MessageListModel::emitDataChanged(const QString& msgId, VectorInt roles)
Q_EMIT dataChanged(modelIndex, modelIndex, roles);
}
void
MessageListModel::addEdition(const QString& msgId, const interaction::Info& info, bool end)
{
auto editedId = info.commit["edit"];
if (editedId.isEmpty())
return;
auto& edited = editedBodies_[editedId];
auto editedMsgIt = std::find_if(edited.begin(), edited.end(), [&](const auto& v) {
return msgId == v.commitId;
});
if (editedMsgIt != edited.end())
return; // Already added
auto value = interaction::Body {msgId, info.body, info.timestamp};
if (end)
edited.push_back(value);
else
edited.push_front(value);
auto editedIt = find(editedId);
if (editedIt != interactions_.end()) {
// If already there, we can update the content
editMessage(editedId, editedIt->second);
if (!editedIt->second.react_to.isEmpty()) {
auto reactToIt = find(editedIt->second.react_to);
if (reactToIt != interactions_.end())
reactToMessage(editedIt->second.react_to, reactToIt->second);
}
}
}
void
MessageListModel::addReaction(const QString& messageId, const QString& reactionId)
{
auto itReacted = reactedMessages_.find(messageId);
if (itReacted != reactedMessages_.end()) {
itReacted->insert(reactionId);
} else {
QSet<QString> emojiList;
emojiList.insert(reactionId);
reactedMessages_.insert(messageId, emojiList);
}
auto interaction = find(reactionId);
if (interaction != interactions_.end()) {
// Edit reaction if needed
editMessage(reactionId, interaction->second);
}
}
QVariantMap
MessageListModel::convertReactMessagetoQVariant(const QSet<QString>& emojiIdList)
{
QVariantMap convertedMap;
QMap<QString, QStringList> mapStringEmoji;
for (auto emojiId = emojiIdList.begin(); emojiId != emojiIdList.end(); emojiId++) {
auto interaction = find(*emojiId);
if (interaction != interactions_.end()) {
auto author = interaction->second.authorUri;
auto body = interaction->second.body;
if (!body.isEmpty()) {
auto itAuthor = mapStringEmoji.find(author);
if (itAuthor != mapStringEmoji.end()) {
mapStringEmoji[author].append(body);
} else {
QStringList emojiList;
emojiList.append(body);
mapStringEmoji.insert(author, emojiList);
}
}
}
}
for (auto i = mapStringEmoji.begin(); i != mapStringEmoji.end(); i++) {
convertedMap.insert(i.key(), i.value());
}
return convertedMap;
}
void
MessageListModel::editMessage(const QString& msgId, interaction::Info& info)
{
auto it = editedBodies_.find(msgId);
if (it != editedBodies_.end()) {
if (info.previousBodies.isEmpty()) {
info.previousBodies.push_back(interaction::Body {msgId, info.body, info.timestamp});
}
// Find if already added (because MessageReceived can be triggered
// multiple times for same message)
for (const auto& editedBody : *it) {
auto itCommit = std::find_if(info.previousBodies.begin(),
info.previousBodies.end(),
[&](const auto& element) {
return element.commitId == editedBody.commitId;
});
if (itCommit == info.previousBodies.end()) {
info.previousBodies.push_back(editedBody);
}
}
info.body = it->rbegin()->body;
info.parsedBody.clear();
editedBodies_.erase(it);
emitDataChanged(msgId,
{MessageList::Role::Body,
MessageList::Role::ParsedBody,
MessageList::Role::PreviousBodies,
MessageList::Role::IsEmojiOnly});
// Body changed, replies should update
for (const auto& replyId : replyTo_[msgId]) {
int index = getIndexOfMessage(replyId);
if (index == -1)
continue;
QModelIndex modelIndex = QAbstractListModel::index(index, 0);
Q_EMIT dataChanged(modelIndex, modelIndex, {Role::ReplyToBody});
}
}
}
void
MessageListModel::reactToMessage(const QString& msgId, interaction::Info& info)
{
// If already there, we can update the content
auto itReact = reactedMessages_.find(msgId);
if (itReact != reactedMessages_.end()) {
auto convertedMap = convertReactMessagetoQVariant(reactedMessages_[msgId]);
info.reactions = convertedMap;
emitDataChanged(find(msgId), {Role::Reactions});
}
}
QString
MessageListModel::lastMessageUid() const
{
for (auto it = interactions_.rbegin(); it != interactions_.rend(); ++it) {
auto lastType = it->second.type;
if (lastType != interaction::Type::MERGE and lastType != interaction::Type::EDITED
and !it->second.body.isEmpty()) {
return it->first;
}
}
return {};
}
QString
MessageListModel::lastSelfMessageId(const QString& id) const
{
@ -763,20 +629,4 @@ MessageListModel::lastSelfMessageId(const QString& id) const
}
return {};
}
QString
MessageListModel::findEmojiReaction(const QString& emoji,
const QString& authorURI,
const QString& messageId)
{
auto& messageReactions = reactedMessages_[messageId];
for (auto it = messageReactions.begin(); it != messageReactions.end(); it++) {
auto interaction = find(*it);
if (interaction != interactions_.end() && interaction->second.body == emoji
&& interaction->second.authorUri == authorURI) {
return *it;
}
}
return {};
}
} // namespace lrc

View file

@ -20,6 +20,7 @@
#pragma once
#include "api/interaction.h"
#include "api/account.h"
#include <QAbstractListModel>
@ -79,7 +80,7 @@ public:
typedef QList<QPair<QString, interaction::Info>>::Iterator iterator;
typedef QList<QPair<QString, interaction::Info>>::reverse_iterator reverseIterator;
explicit MessageListModel(QObject* parent = nullptr);
explicit MessageListModel(const account::Info* account, QObject* parent = nullptr);
~MessageListModel() = default;
// map functions
@ -105,6 +106,7 @@ public:
reverseIterator rbegin();
Q_INVOKABLE int size() const;
void clear();
void reloadHistory();
bool empty() const;
interaction::Info at(const QString& intId) const;
QPair<QString, interaction::Info> front() const;
@ -113,7 +115,6 @@ public:
QPair<iterator, bool> insert(int index, QPair<QString, interaction::Info> message);
int indexOfMessage(const QString& msgId, bool reverse = true) const;
void moveMessages(QList<QString> msgIds, const QString& parentId);
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
Q_INVOKABLE virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
@ -123,6 +124,8 @@ public:
bool contains(const QString& msgId);
int getIndexOfMessage(const QString& messageId) const;
void addHyperlinkInfo(const QString& messageId, const QVariantMap& info);
void addReaction(const QString& messageId, const MapStringString& reaction);
void rmReaction(const QString& messageId, const QString& reactionId);
void setParsedMessage(const QString& messageId, const QString& parsed);
void setRead(const QString& peer, const QString& messageId);
@ -136,18 +139,9 @@ public:
void emitDataChanged(const QString& msgId, VectorInt roles = {});
bool isOnlyEmoji(const QString& text) const;
void addEdition(const QString& msgId, const interaction::Info& info, bool end);
void addReaction(const QString& messageId, const QString& reactionId);
void editMessage(const QString& msgId, interaction::Info& info);
void reactToMessage(const QString& msgId, interaction::Info& info);
QVariantMap convertReactMessagetoQVariant(const QSet<QString>&);
QString lastMessageUid() const;
QString lastSelfMessageId(const QString& id) const;
QString findEmojiReaction(const QString& emoji,
const QString& authorURI,
const QString& messageId);
protected:
using Role = MessageList::Role;
@ -161,17 +155,12 @@ private:
QMap<QString, QString> lastDisplayedMessageUid_;
QMap<QString, QStringList> messageToReaders_;
QMap<QString, QSet<QString>> replyTo_;
const account::Info* account_;
void updateReplies(item_t& message);
QMap<QString, QVector<interaction::Body>> editedBodies_;
// key = messageId and values = QSet of reactionIds
QMap<QString, QSet<QString>> reactedMessages_;
void moveMessage(const QString& msgId, const QString& parentId);
void insertMessage(int index, item_t& message);
iterator insertMessage(iterator it, item_t& message);
void removeMessage(int index, iterator it);
void moveMessages(int from, int last, int to);
};
} // namespace api
} // namespace lrc

View file

@ -259,15 +259,25 @@ public:
}),
};
conversationsHandlers
= {exportable_callback<ConversationSignal::ConversationLoaded>(
= {exportable_callback<ConversationSignal::SwarmLoaded>(
[this](uint32_t id,
const std::string& accountId,
const std::string& conversationId,
const std::vector<std::map<std::string, std::string>>& messages) {
Q_EMIT conversationLoaded(id,
QString(accountId.c_str()),
QString(conversationId.c_str()),
convertVecMap(messages));
const std::vector<libjami::SwarmMessage>& messages) {
VectorSwarmMessage vec;
for (const auto& msg : messages) {
vec.push_back({msg.id.c_str(),
msg.type.c_str(),
msg.linearizedParent.c_str(),
convertMap(msg.body),
convertVecMap(msg.reactions),
convertVecMap(msg.editions)});
}
Q_EMIT swarmLoaded(id,
QString(accountId.c_str()),
QString(conversationId.c_str()),
vec);
}),
exportable_callback<ConversationSignal::MessagesFound>(
[this](uint32_t id,
@ -279,13 +289,53 @@ public:
QString(conversationId.c_str()),
convertVecMap(messages));
}),
exportable_callback<ConversationSignal::MessageReceived>(
exportable_callback<ConversationSignal::SwarmMessageReceived>(
[this](const std::string& accountId,
const std::string& conversationId,
const std::map<std::string, std::string>& message) {
Q_EMIT messageReceived(QString(accountId.c_str()),
const libjami::SwarmMessage& message) {
::SwarmMessage msg = {message.id.c_str(),
message.type.c_str(),
message.linearizedParent.c_str(),
convertMap(message.body),
convertVecMap(message.reactions),
convertVecMap(message.editions)};
Q_EMIT swarmMessageReceived(QString(accountId.c_str()),
QString(conversationId.c_str()),
msg);
}),
exportable_callback<ConversationSignal::SwarmMessageUpdated>(
[this](const std::string& accountId,
const std::string& conversationId,
const libjami::SwarmMessage& message) {
::SwarmMessage msg = {message.id.c_str(),
message.type.c_str(),
message.linearizedParent.c_str(),
convertMap(message.body),
convertVecMap(message.reactions),
convertVecMap(message.editions)};
Q_EMIT swarmMessageUpdated(QString(accountId.c_str()),
QString(conversationId.c_str()),
msg);
}),
exportable_callback<ConversationSignal::ReactionAdded>(
[this](const std::string& accountId,
const std::string& conversationId,
const std::string& messageId,
const std::map<std::string, std::string>& reaction) {
Q_EMIT reactionAdded(QString(accountId.c_str()),
QString(conversationId.c_str()),
QString(messageId.c_str()),
convertMap(reaction));
}),
exportable_callback<ConversationSignal::ReactionRemoved>(
[this](const std::string& accountId,
const std::string& conversationId,
const std::string& messageId,
const std::string& reactionId) {
Q_EMIT reactionRemoved(QString(accountId.c_str()),
QString(conversationId.c_str()),
convertMap(message));
QString(messageId.c_str()),
QString(reactionId.c_str()));
}),
exportable_callback<ConversationSignal::ConversationProfileUpdated>(
[this](const std::string& accountId,
@ -970,25 +1020,15 @@ public Q_SLOTS: // METHODS
flags);
}
uint32_t loadConversationMessages(const QString& accountId,
const QString& conversationId,
const QString& fromId,
const int size)
uint32_t loadConversation(const QString& accountId,
const QString& conversationId,
const QString& fromId,
const int size)
{
return libjami::loadConversationMessages(accountId.toStdString(),
conversationId.toStdString(),
fromId.toStdString(),
size);
}
uint32_t loadConversationUntil(const QString& accountId,
const QString& conversationId,
const QString& fromId,
const QString& toId)
{
return libjami::loadConversationUntil(accountId.toStdString(),
conversationId.toStdString(),
fromId.toStdString(),
toId.toStdString());
return libjami::loadConversation(accountId.toStdString(),
conversationId.toStdString(),
fromId.toStdString(),
size);
}
void setDefaultModerator(const QString& accountID, const QString& peerURI, const bool& state)
@ -1070,6 +1110,11 @@ public Q_SLOTS: // METHODS
convertMap(prefs));
}
void clearCache(const QString& accountId, const QString& conversationId)
{
return libjami::clearCache(accountId.toStdString(), conversationId.toStdString());
}
uint32_t countInteractions(const QString& accountId,
const QString& conversationId,
const QString& toId,
@ -1169,9 +1214,24 @@ Q_SIGNALS: // SIGNALS
const QString& accountId,
const QString& conversationId,
const VectorMapStringString& messages);
void messageReceived(const QString& accountId,
void swarmLoaded(uint32_t requestId,
const QString& accountId,
const QString& conversationId,
const VectorSwarmMessage& messages);
void swarmMessageReceived(const QString& accountId,
const QString& conversationId,
const SwarmMessage& message);
void swarmMessageUpdated(const QString& accountId,
const QString& conversationId,
const SwarmMessage& message);
void reactionAdded(const QString& accountId,
const QString& conversationId,
const QString& messageId,
const MapStringString& message);
void reactionRemoved(const QString& accountId,
const QString& conversationId,
const MapStringString& message);
const QString& messageId,
const QString& reactionId);
void messagesFound(uint32_t requestId,
const QString& accountId,
const QString& conversationId,

View file

@ -72,6 +72,17 @@ struct DataTransferInfo
QString mimetype;
};
struct SwarmMessage
{
QString id;
QString type;
QString linearizedParent;
MapStringString body;
VectorMapStringString reactions;
VectorMapStringString editions;
};
typedef QVector<SwarmMessage> VectorSwarmMessage;
struct Message
{
QString from;

View file

@ -86,8 +86,8 @@ Item {
// Add some emoji reactions (one from current account uri, one from another uri)
emojiReactions.reactions = {
"currentAccountUsername": ["🌭"],
"notCurrentAccountUri": ["🌮"]
"currentAccountUsername": [{"commitId":"hotdog", "body":"🌭"}],
"notCurrentAccountUri": [{"commitId":"tacos", "body":"🌮"}]
};
var optionsPopup = getOptionsPopup(true, getId(), "test", 0, "test");