1
0
mirror of https://github.com/qTox/qTox.git synced 2024-03-22 14:00:36 +08:00

Merge pull request #6310

Mick Sayson (1):
      feat(chatlog): Add SystemMessages to SessionChatLog
This commit is contained in:
Anthony Bilinski 2021-10-24 05:35:22 -07:00
commit 0d3be5aab3
No known key found for this signature in database
GPG Key ID: 2AA8E0DA1B31FB3C
13 changed files with 278 additions and 29 deletions

View File

@ -212,6 +212,13 @@ std::vector<IChatLog::DateChatLogIdxPair> ChatHistory::getDateIdxs(const QDate&
} }
} }
void ChatHistory::addSystemMessage(const SystemMessage& message)
{
// FIXME: #6221 Insert into history
sessionChatLog.addSystemMessage(message);
}
void ChatHistory::onFileUpdated(const ToxPk& sender, const ToxFile& file) void ChatHistory::onFileUpdated(const ToxPk& sender, const ToxFile& file)
{ {
if (canUseHistory()) { if (canUseHistory()) {

View File

@ -42,12 +42,14 @@ public:
ChatLogIdx getFirstIdx() const override; ChatLogIdx getFirstIdx() const override;
ChatLogIdx getNextIdx() const override; ChatLogIdx getNextIdx() const override;
std::vector<DateChatLogIdxPair> getDateIdxs(const QDate& startDate, size_t maxDates) const override; std::vector<DateChatLogIdxPair> getDateIdxs(const QDate& startDate, size_t maxDates) const override;
void addSystemMessage(const SystemMessage& message) override;
public slots: public slots:
void onFileUpdated(const ToxPk& sender, const ToxFile& file); void onFileUpdated(const ToxPk& sender, const ToxFile& file);
void onFileTransferRemotePausedUnpaused(const ToxPk& sender, const ToxFile& file, bool paused); void onFileTransferRemotePausedUnpaused(const ToxPk& sender, const ToxFile& file, bool paused);
void onFileTransferBrokenUnbroken(const ToxPk& sender, const ToxFile& file, bool broken); void onFileTransferBrokenUnbroken(const ToxPk& sender, const ToxFile& file, bool broken);
private slots: private slots:
void onMessageReceived(const ToxPk& sender, const Message& message); void onMessageReceived(const ToxPk& sender, const Message& message);
void onMessageSent(DispatchedMessageId id, const Message& message); void onMessageSent(DispatchedMessageId id, const Message& message);

View File

@ -50,6 +50,11 @@ ChatLogItem::ChatLogItem(ToxPk sender_, const QString& displayName, ChatLogMessa
ChatLogItemDeleter<ChatLogMessage>::doDelete)) ChatLogItemDeleter<ChatLogMessage>::doDelete))
{} {}
ChatLogItem::ChatLogItem(SystemMessage systemMessage)
: contentType(ContentType::systemMessage)
, content(new SystemMessage(std::move(systemMessage)), ChatLogItemDeleter<SystemMessage>::doDelete)
{}
ChatLogItem::ChatLogItem(ToxPk sender_, const QString& displayName_, ContentType contentType_, ContentPtr content_) ChatLogItem::ChatLogItem(ToxPk sender_, const QString& displayName_, ContentType contentType_, ContentPtr content_)
: sender(std::move(sender_)) : sender(std::move(sender_))
, displayName(displayName_) , displayName(displayName_)
@ -91,6 +96,19 @@ const ChatLogMessage& ChatLogItem::getContentAsMessage() const
return *static_cast<ChatLogMessage*>(content.get()); return *static_cast<ChatLogMessage*>(content.get());
} }
SystemMessage& ChatLogItem::getContentAsSystemMessage()
{
assert(contentType == ContentType::systemMessage);
return *static_cast<SystemMessage*>(content.get());
}
const SystemMessage& ChatLogItem::getContentAsSystemMessage() const
{
assert(contentType == ContentType::systemMessage);
return *static_cast<SystemMessage*>(content.get());
}
QDateTime ChatLogItem::getTimestamp() const QDateTime ChatLogItem::getTimestamp() const
{ {
switch (contentType) { switch (contentType) {
@ -102,6 +120,10 @@ QDateTime ChatLogItem::getTimestamp() const
const auto& file = getContentAsFile(); const auto& file = getContentAsFile();
return file.timestamp; return file.timestamp;
} }
case ChatLogItem::ContentType::systemMessage: {
const auto& systemMessage = getContentAsSystemMessage();
return systemMessage.timestamp;
}
} }
assert(false); assert(false);

View File

@ -22,6 +22,7 @@
#include "src/core/toxfile.h" #include "src/core/toxfile.h"
#include "src/core/toxpk.h" #include "src/core/toxpk.h"
#include "src/model/message.h" #include "src/model/message.h"
#include "src/model/systemmessage.h"
#include "src/persistence/history.h" #include "src/persistence/history.h"
#include <memory> #include <memory>
@ -48,16 +49,20 @@ public:
{ {
message, message,
fileTransfer, fileTransfer,
systemMessage,
}; };
ChatLogItem(ToxPk sender, const QString& displayName, ChatLogFile file); ChatLogItem(ToxPk sender, const QString& displayName, ChatLogFile file);
ChatLogItem(ToxPk sender, const QString& displayName, ChatLogMessage message); ChatLogItem(ToxPk sender, const QString& displayName, ChatLogMessage message);
ChatLogItem(SystemMessage message);
const ToxPk& getSender() const; const ToxPk& getSender() const;
ContentType getContentType() const; ContentType getContentType() const;
ChatLogFile& getContentAsFile(); ChatLogFile& getContentAsFile();
const ChatLogFile& getContentAsFile() const; const ChatLogFile& getContentAsFile() const;
ChatLogMessage& getContentAsMessage(); ChatLogMessage& getContentAsMessage();
const ChatLogMessage& getContentAsMessage() const; const ChatLogMessage& getContentAsMessage() const;
SystemMessage& getContentAsSystemMessage();
const SystemMessage& getContentAsSystemMessage() const;
QDateTime getTimestamp() const; QDateTime getTimestamp() const;
void setDisplayName(QString name); void setDisplayName(QString name);
const QString& getDisplayName() const; const QString& getDisplayName() const;

View File

@ -28,9 +28,9 @@
#include "src/model/chatlogitem.h" #include "src/model/chatlogitem.h"
#include "src/model/friend.h" #include "src/model/friend.h"
#include "src/model/group.h" #include "src/model/group.h"
#include "src/persistence/history.h" #include "src/model/systemmessage.h"
#include "util/strongtype.h"
#include "src/widget/searchtypes.h" #include "src/widget/searchtypes.h"
#include "util/strongtype.h"
#include <cassert> #include <cassert>
@ -137,6 +137,12 @@ public:
virtual std::vector<DateChatLogIdxPair> getDateIdxs(const QDate& startDate, virtual std::vector<DateChatLogIdxPair> getDateIdxs(const QDate& startDate,
size_t maxDates) const = 0; size_t maxDates) const = 0;
/**
* @brief Inserts a system message at the end of the chat
* @param[in] message systemMessage to insert
*/
virtual void addSystemMessage(const SystemMessage& message) = 0;
signals: signals:
void itemUpdated(ChatLogIdx idx); void itemUpdated(ChatLogIdx idx);
}; };

View File

@ -319,6 +319,15 @@ std::vector<IChatLog::DateChatLogIdxPair> SessionChatLog::getDateIdxs(const QDat
return ret; return ret;
} }
void SessionChatLog::addSystemMessage(const SystemMessage& message)
{
auto messageIdx = nextIdx++;
items.emplace(messageIdx, ChatLogItem(message));
emit this->itemUpdated(messageIdx);
}
void SessionChatLog::insertCompleteMessageAtIdx(ChatLogIdx idx, const ToxPk& sender, QString senderName, void SessionChatLog::insertCompleteMessageAtIdx(ChatLogIdx idx, const ToxPk& sender, QString senderName,
const ChatLogMessage& message) const ChatLogMessage& message)
{ {
@ -358,6 +367,13 @@ void SessionChatLog::insertFileAtIdx(ChatLogIdx idx, const ToxPk& sender, QStrin
items.emplace(idx, std::move(item)); items.emplace(idx, std::move(item));
} }
void SessionChatLog::insertSystemMessageAtIdx(ChatLogIdx idx, SystemMessage message)
{
auto item = ChatLogItem(std::move(message));
items.emplace(idx, std::move(item));
}
/** /**
* @brief Inserts message data into the chatlog buffer * @brief Inserts message data into the chatlog buffer
* @note Owner of SessionChatLog is in charge of attaching this to the appropriate IMessageDispatcher * @note Owner of SessionChatLog is in charge of attaching this to the appropriate IMessageDispatcher

View File

@ -44,6 +44,7 @@ public:
ChatLogIdx getFirstIdx() const override; ChatLogIdx getFirstIdx() const override;
ChatLogIdx getNextIdx() const override; ChatLogIdx getNextIdx() const override;
std::vector<DateChatLogIdxPair> getDateIdxs(const QDate& startDate, size_t maxDates) const override; std::vector<DateChatLogIdxPair> getDateIdxs(const QDate& startDate, size_t maxDates) const override;
void addSystemMessage(const SystemMessage& message) override;
void insertCompleteMessageAtIdx(ChatLogIdx idx, const ToxPk& sender, QString senderName, void insertCompleteMessageAtIdx(ChatLogIdx idx, const ToxPk& sender, QString senderName,
const ChatLogMessage& message); const ChatLogMessage& message);
@ -52,6 +53,7 @@ public:
void insertBrokenMessageAtIdx(ChatLogIdx idx, const ToxPk& sender, QString senderName, void insertBrokenMessageAtIdx(ChatLogIdx idx, const ToxPk& sender, QString senderName,
const ChatLogMessage& message); const ChatLogMessage& message);
void insertFileAtIdx(ChatLogIdx idx, const ToxPk& sender, QString senderName, const ChatLogFile& file); void insertFileAtIdx(ChatLogIdx idx, const ToxPk& sender, QString senderName, const ChatLogFile& file);
void insertSystemMessageAtIdx(ChatLogIdx idx, SystemMessage message);
public slots: public slots:
void onMessageReceived(const ToxPk& sender, const Message& message); void onMessageReceived(const ToxPk& sender, const Message& message);

114
src/model/systemmessage.h Normal file
View File

@ -0,0 +1,114 @@
/*
Copyright © 2015-2021 by The qTox Project Contributors
This file is part of qTox, a Qt-based graphical interface for Tox.
qTox is libre software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
qTox is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with qTox. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QObject>
#include <QDateTime>
#include <QString>
#include <array>
enum class SystemMessageType
{
// DO NOT CHANGE ORDER
// These values are saved directly to the DB and read back, changing the
// order will break persistence!
fileSendFailed = 0,
userJoinedGroup,
userLeftGroup,
peerNameChanged,
peerStateChange,
titleChanged,
cleared,
unexpectedCallEnd,
outgoingCall,
incomingCall,
callEnd,
messageSendFailed,
};
struct SystemMessage
{
using Args = std::array<QString, 4>;
SystemMessageType messageType;
QDateTime timestamp;
Args args;
QString toString() const
{
QString translated;
size_t numArgs = 0;
switch (messageType) {
case SystemMessageType::fileSendFailed:
translated = QObject::tr("Failed to send file \"%1\"");
numArgs = 1;
break;
case SystemMessageType::userJoinedGroup:
translated = QObject::tr("%1 has joined the group");
numArgs = 1;
break;
case SystemMessageType::userLeftGroup:
translated = QObject::tr("%1 has left the group");
numArgs = 1;
break;
case SystemMessageType::peerNameChanged:
translated = QObject::tr("%1 is now known as %2");
numArgs = 2;
break;
case SystemMessageType::titleChanged:
translated = QObject::tr("%1 has set the title to %2");
numArgs = 2;
break;
case SystemMessageType::cleared:
translated = QObject::tr("Cleared");
break;
case SystemMessageType::unexpectedCallEnd:
translated = QObject::tr("Call with %1 ended unexpectedly. %2");
numArgs = 2;
break;
case SystemMessageType::callEnd:
translated = QObject::tr("Call with %1 ended. %2");
numArgs = 2;
break;
case SystemMessageType::peerStateChange:
translated = QObject::tr("%1 is now %2", "e.g. \"Dubslow is now online\"");
numArgs = 2;
break;
case SystemMessageType::outgoingCall:
translated = QObject::tr("Calling %1");
numArgs = 1;
break;
case SystemMessageType::incomingCall:
translated = QObject::tr("%1 calling");
numArgs = 1;
break;
case SystemMessageType::messageSendFailed:
translated = QObject::tr("Message failed to send");
break;
}
for (size_t i = 0; i < numArgs; ++i) {
translated = translated.arg(args[i]);
}
return translated;
}
};

View File

@ -289,9 +289,11 @@ void ChatForm::onAvInvite(uint32_t friendId, bool video)
} }
QString displayedName = f->getDisplayedName(); QString displayedName = f->getDisplayedName();
insertChatMessage(ChatMessage::createChatInfoMessage(tr("%1 calling").arg(displayedName),
ChatMessage::INFO, SystemMessage systemMessage;
QDateTime::currentDateTime())); systemMessage.messageType = SystemMessageType::incomingCall;
systemMessage.args = {displayedName};
chatLog.addSystemMessage(systemMessage);
auto testedFlag = video ? Settings::AutoAcceptCall::Video : Settings::AutoAcceptCall::Audio; auto testedFlag = video ? Settings::AutoAcceptCall::Video : Settings::AutoAcceptCall::Audio;
// AutoAcceptCall is set for this friend // AutoAcceptCall is set for this friend
@ -349,8 +351,8 @@ void ChatForm::onAvEnd(uint32_t friendId, bool error)
void ChatForm::showOutgoingCall(bool video) void ChatForm::showOutgoingCall(bool video)
{ {
headWidget->showOutgoingCall(video); headWidget->showOutgoingCall(video);
addSystemInfoMessage(tr("Calling %1").arg(f->getDisplayedName()), ChatMessage::INFO, addSystemInfoMessage(QDateTime::currentDateTime(), SystemMessageType::outgoingCall,
QDateTime::currentDateTime()); {f->getDisplayedName()});
emit outgoingNotification(); emit outgoingNotification();
emit updateFriendActivity(*f); emit updateFriendActivity(*f);
} }
@ -444,10 +446,8 @@ void ChatForm::onFriendStatusChanged(const ToxPk& friendPk, Status::Status statu
if (Settings::getInstance().getStatusChangeNotificationEnabled()) { if (Settings::getInstance().getStatusChangeNotificationEnabled()) {
QString fStatus = Status::getTitle(status); QString fStatus = Status::getTitle(status);
addSystemInfoMessage(tr("%1 is now %2", "e.g. \"Dubslow is now online\"") addSystemInfoMessage(QDateTime::currentDateTime(), SystemMessageType::peerStateChange,
.arg(f->getDisplayedName()) {f->getDisplayedName(), fStatus});
.arg(fStatus),
ChatMessage::INFO, QDateTime::currentDateTime());
} }
} }
@ -652,10 +652,10 @@ void ChatForm::stopCounter(bool error)
} }
QString dhms = secondsToDHMS(timeElapsed.elapsed() / 1000); QString dhms = secondsToDHMS(timeElapsed.elapsed() / 1000);
QString name = f->getDisplayedName(); QString name = f->getDisplayedName();
QString mess = error ? tr("Call with %1 ended unexpectedly. %2") : tr("Call with %1 ended. %2"); auto messageType = error ? SystemMessageType::unexpectedCallEnd : SystemMessageType::callEnd;
// TODO: add notification once notifications are implemented // TODO: add notification once notifications are implemented
addSystemInfoMessage(mess.arg(name, dhms), ChatMessage::INFO, QDateTime::currentDateTime()); addSystemInfoMessage(QDateTime::currentDateTime(), messageType, {name, dhms});
callDurationTimer->stop(); callDurationTimer->stop();
callDuration->setText(""); callDuration->setText("");
callDuration->hide(); callDuration->hide();

View File

@ -209,6 +209,32 @@ ChatLogIdx firstItemAfterDate(QDate date, const IChatLog& chatLog)
return chatLog.getNextIdx(); return chatLog.getNextIdx();
} }
} }
/**
* @return Chat message message type (info/warning) for the given system message
* @param[in] systemMessage
*/
ChatMessage::SystemMessageType getChatMessageType(const SystemMessage& systemMessage)
{
switch (systemMessage.messageType) {
case SystemMessageType::fileSendFailed:
case SystemMessageType::messageSendFailed:
case SystemMessageType::unexpectedCallEnd:
return ChatMessage::ERROR;
case SystemMessageType::userJoinedGroup:
case SystemMessageType::userLeftGroup:
case SystemMessageType::peerNameChanged:
case SystemMessageType::peerStateChange:
case SystemMessageType::titleChanged:
case SystemMessageType::cleared:
case SystemMessageType::outgoingCall:
case SystemMessageType::incomingCall:
case SystemMessageType::callEnd:
return ChatMessage::INFO;
}
return ChatMessage::INFO;
}
} // namespace } // namespace
GenericChatForm::GenericChatForm(const Core& _core, const Contact* contact, IChatLog& chatLog, GenericChatForm::GenericChatForm(const Core& _core, const Contact* contact, IChatLog& chatLog,
@ -407,7 +433,42 @@ QDateTime GenericChatForm::getLatestTime() const
if (chatLog.getFirstIdx() == chatLog.getNextIdx()) if (chatLog.getFirstIdx() == chatLog.getNextIdx())
return QDateTime(); return QDateTime();
return chatLog.at(chatLog.getNextIdx() - 1).getTimestamp(); const auto shouldUseTimestamp = [this] (ChatLogIdx idx) {
if (chatLog.at(idx).getContentType() != ChatLogItem::ContentType::systemMessage) {
return true;
}
const auto& message = chatLog.at(idx).getContentAsSystemMessage();
switch (message.messageType) {
case SystemMessageType::incomingCall:
case SystemMessageType::outgoingCall:
case SystemMessageType::callEnd:
case SystemMessageType::unexpectedCallEnd:
return true;
case SystemMessageType::cleared:
case SystemMessageType::titleChanged:
case SystemMessageType::peerStateChange:
case SystemMessageType::peerNameChanged:
case SystemMessageType::userLeftGroup:
case SystemMessageType::userJoinedGroup:
case SystemMessageType::fileSendFailed:
case SystemMessageType::messageSendFailed:
return false;
}
qWarning("Unexpected system message type %d", static_cast<int>(message.messageType));
return false;
};
ChatLogIdx idx = chatLog.getNextIdx();
while (idx > chatLog.getFirstIdx()) {
idx = idx - 1;
if (shouldUseTimestamp(idx)) {
return chatLog.at(idx).getTimestamp();
}
}
return QDateTime();
} }
void GenericChatForm::reloadTheme() void GenericChatForm::reloadTheme()
@ -591,10 +652,14 @@ void GenericChatForm::setColorizedNames(bool enable)
colorizeNames = enable; colorizeNames = enable;
} }
void GenericChatForm::addSystemInfoMessage(const QString& message, ChatMessage::SystemMessageType type, void GenericChatForm::addSystemInfoMessage(const QDateTime& datetime, SystemMessageType messageType,
const QDateTime& datetime) SystemMessage::Args messageArgs)
{ {
insertChatMessage(ChatMessage::createChatInfoMessage(message, type, datetime)); SystemMessage systemMessage;
systemMessage.messageType = static_cast<SystemMessageType>(messageType);
systemMessage.timestamp = datetime;
systemMessage.args = std::move(messageArgs);
chatLog.addSystemMessage(systemMessage);
} }
void GenericChatForm::addSystemDateMessage(const QDate& date) void GenericChatForm::addSystemDateMessage(const QDate& date)
@ -759,7 +824,7 @@ void GenericChatForm::clearChatArea(bool confirm, bool inform)
chatWidget->clear(); chatWidget->clear();
if (inform) if (inform)
addSystemInfoMessage(tr("Cleared"), ChatMessage::INFO, QDateTime::currentDateTime()); addSystemInfoMessage(QDateTime::currentDateTime(), SystemMessageType::cleared, {});
messages.clear(); messages.clear();
} }
@ -1070,6 +1135,17 @@ void GenericChatForm::renderItem(const ChatLogItem& item, bool hideName, bool co
renderFile(item.getDisplayName(), file.file, isSelf, item.getTimestamp(), chatMessage); renderFile(item.getDisplayName(), file.file, isSelf, item.getTimestamp(), chatMessage);
break; break;
} }
case ChatLogItem::ContentType::systemMessage: {
const auto& systemMessage = item.getContentAsSystemMessage();
auto chatMessageType = getChatMessageType(systemMessage);
chatMessage = ChatMessage::createChatInfoMessage(systemMessage.toString(), chatMessageType,
QDateTime::currentDateTime());
// Ignore caller's decision to hide the name. We show the icon in the
// slot of the sender's name so we always want it visible
hideName = false;
break;
}
} }
if (hideName) { if (hideName) {

View File

@ -76,8 +76,8 @@ public:
void setName(const QString& newName); void setName(const QString& newName);
virtual void show(ContentLayout* contentLayout); virtual void show(ContentLayout* contentLayout);
void addSystemInfoMessage(const QString& message, ChatMessage::SystemMessageType type, void addSystemInfoMessage(const QDateTime& datetime, SystemMessageType messageType,
const QDateTime& datetime); SystemMessage::Args messageArgs);
static QString resolveToxPk(const ToxPk& pk); static QString resolveToxPk(const ToxPk& pk);
QDateTime getLatestTime() const; QDateTime getLatestTime() const;

View File

@ -148,9 +148,8 @@ void GroupChatForm::onTitleChanged(const QString& author, const QString& title)
return; return;
} }
const QString message = tr("%1 has set the title to %2").arg(author, title);
const QDateTime curTime = QDateTime::currentDateTime(); const QDateTime curTime = QDateTime::currentDateTime();
addSystemInfoMessage(message, ChatMessage::INFO, curTime); addSystemInfoMessage(curTime, SystemMessageType::titleChanged, {author, title});
} }
void GroupChatForm::onScreenshotClicked() void GroupChatForm::onScreenshotClicked()
@ -232,19 +231,20 @@ void GroupChatForm::updateUserNames()
void GroupChatForm::onUserJoined(const ToxPk& user, const QString& name) void GroupChatForm::onUserJoined(const ToxPk& user, const QString& name)
{ {
addSystemInfoMessage(tr("%1 has joined the group").arg(name), ChatMessage::INFO, QDateTime::currentDateTime()); addSystemInfoMessage(QDateTime::currentDateTime(), SystemMessageType::userJoinedGroup, {name});
updateUserNames(); updateUserNames();
} }
void GroupChatForm::onUserLeft(const ToxPk& user, const QString& name) void GroupChatForm::onUserLeft(const ToxPk& user, const QString& name)
{ {
addSystemInfoMessage(tr("%1 has left the group").arg(name), ChatMessage::INFO, QDateTime::currentDateTime()); addSystemInfoMessage(QDateTime::currentDateTime(), SystemMessageType::userLeftGroup, {name});
updateUserNames(); updateUserNames();
} }
void GroupChatForm::onPeerNameChanged(const ToxPk& peer, const QString& oldName, const QString& newName) void GroupChatForm::onPeerNameChanged(const ToxPk& peer, const QString& oldName, const QString& newName)
{ {
addSystemInfoMessage(tr("%1 is now known as %2").arg(oldName, newName), ChatMessage::INFO, QDateTime::currentDateTime()); addSystemInfoMessage(QDateTime::currentDateTime(), SystemMessageType::peerNameChanged,
{oldName, newName});
updateUserNames(); updateUserNames();
} }

View File

@ -1130,8 +1130,8 @@ void Widget::dispatchFileSendFailed(uint32_t friendId, const QString& fileName)
return; return;
} }
chatForm.value()->addSystemInfoMessage(tr("Failed to send file \"%1\"").arg(fileName), chatForm.value()->addSystemInfoMessage(QDateTime::currentDateTime(),
ChatMessage::ERROR, QDateTime::currentDateTime()); SystemMessageType::fileSendFailed, {fileName});
} }
void Widget::onRejectCall(uint32_t friendId) void Widget::onRejectCall(uint32_t friendId)
@ -2342,10 +2342,9 @@ void Widget::onGroupSendFailed(uint32_t groupnumber)
const auto& groupId = GroupList::id2Key(groupnumber); const auto& groupId = GroupList::id2Key(groupnumber);
assert(GroupList::findGroup(groupId)); assert(GroupList::findGroup(groupId));
const auto message = tr("Message failed to send");
const auto curTime = QDateTime::currentDateTime(); const auto curTime = QDateTime::currentDateTime();
auto form = groupChatForms[groupId].data(); auto form = groupChatForms[groupId].data();
form->addSystemInfoMessage(message, ChatMessage::INFO, curTime); form->addSystemInfoMessage(curTime, SystemMessageType::messageSendFailed, {});
} }
void Widget::onFriendTypingChanged(uint32_t friendnumber, bool isTyping) void Widget::onFriendTypingChanged(uint32_t friendnumber, bool isTyping)