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:
parent
2bbd9637da
commit
32b76c8da4
34 changed files with 753 additions and 882 deletions
2
daemon
2
daemon
|
@ -1 +1 @@
|
|||
Subproject commit 317b7317dcda4afb733ddb9bd5b450d4635941ae
|
||||
Subproject commit 8468f15927ec7c83a5fca671bac1a4112883b8c9
|
|
@ -137,4 +137,11 @@ AppSettingsManager::loadTranslations()
|
|||
}
|
||||
|
||||
Q_EMIT retranslate();
|
||||
loadHistory();
|
||||
}
|
||||
|
||||
void
|
||||
AppSettingsManager::loadHistory()
|
||||
{
|
||||
Q_EMIT reloadHistory();
|
||||
}
|
||||
|
|
|
@ -140,9 +140,11 @@ public:
|
|||
QString getLanguage();
|
||||
|
||||
void loadTranslations();
|
||||
void loadHistory();
|
||||
|
||||
Q_SIGNALS:
|
||||
void retranslate();
|
||||
void reloadHistory();
|
||||
|
||||
private:
|
||||
QSettings* settings_;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -158,7 +158,7 @@ private:
|
|||
MapStringString contentDrafts_;
|
||||
MapStringString lastConferences_;
|
||||
|
||||
conversation::Info invalid {};
|
||||
conversation::Info invalid {"", nullptr};
|
||||
|
||||
bool debugMode_ {false};
|
||||
bool muteDaemon_ {true};
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -328,9 +328,8 @@ Rectangle {
|
|||
}
|
||||
|
||||
onHeightChanged: {
|
||||
if (loader.item != null) {
|
||||
if (loader.item)
|
||||
Qt.callLater(loader.item.scrollToBottom);
|
||||
}
|
||||
}
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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 = "",
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -252,6 +252,7 @@ public:
|
|||
* Get notifications count across accounts
|
||||
*/
|
||||
int notificationsCount() const;
|
||||
void reloadHistory();
|
||||
/**
|
||||
* Retrieve account's avatar
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>");
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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");
|
||||
|
|
Loading…
Add table
Reference in a new issue