ephemeral messages

This commit is contained in:
Gaelle Braud 2025-07-03 17:58:27 +02:00
parent 430e6916bd
commit b1ab4224ef
15 changed files with 1002 additions and 851 deletions

View file

@ -71,10 +71,12 @@ QQuickFramebufferObject::Renderer *PreviewManager::subscribe(const CameraGui *ca
App::postModelBlock([&renderer, isFirst = (itCandidate == mCandidates.begin()),
name = itCandidate->first->getQmlName()]() {
renderer =
(QQuickFramebufferObject::Renderer *)CoreModel::getInstance()->getCore()->createNativePreviewWindowId();
(QQuickFramebufferObject::Renderer *)CoreModel::getInstance()->getCore()->createNativePreviewWindowId(
nullptr);
if (!renderer) { // TODO debug
renderer =
(QQuickFramebufferObject::Renderer *)CoreModel::getInstance()->getCore()->createNativePreviewWindowId();
(QQuickFramebufferObject::Renderer *)CoreModel::getInstance()->getCore()->createNativePreviewWindowId(
nullptr);
}
if (isFirst) {
lDebug() << "[PreviewManager] " << name << " Set Native Preview Id with " << renderer;

View file

@ -120,6 +120,13 @@ ChatMessageCore::ChatMessageCore(const std::shared_ptr<linphone::ChatMessage> &c
!chatroom->hasCapability((int)linphone::ChatRoom::Capabilities::OneToOne);
mIsRead = chatmessage->isRead();
mMessageState = LinphoneEnums::fromLinphone(chatmessage->getState());
mIsEphemeral = chatmessage->isEphemeral();
if (mIsEphemeral) {
auto now = QDateTime::currentDateTime();
mEphemeralDuration = chatmessage->getEphemeralExpireTime() == 0
? chatmessage->getEphemeralLifetime()
: now.secsTo(QDateTime::fromSecsSinceEpoch(chatmessage->getEphemeralExpireTime()));
}
mMessageId = Utils::coreStringToAppString(chatmessage->getMessageId());
for (auto content : chatmessage->getContents()) {
auto contentCore = ChatMessageContentCore::create(content, mChatMessageModel);
@ -182,14 +189,17 @@ ChatMessageCore::~ChatMessageCore() {
void ChatMessageCore::setSelf(QSharedPointer<ChatMessageCore> me) {
mChatMessageModelConnection = SafeConnection<ChatMessageCore, ChatMessageModel>::create(me, mChatMessageModel);
mChatMessageModelConnection->makeConnectToCore(&ChatMessageCore::lDelete, [this] {
mChatMessageModelConnection->invokeToModel([this] { mChatMessageModel->deleteMessageFromChatRoom(); });
mChatMessageModelConnection->invokeToModel([this] { mChatMessageModel->deleteMessageFromChatRoom(true); });
});
mChatMessageModelConnection->makeConnectToModel(&ChatMessageModel::messageDeleted, [this]() {
//: Deleted
Utils::showInformationPopup(tr("info_toast_deleted_title"),
//: The message has been deleted
tr("info_toast_deleted_message"), true);
emit deleted();
mChatMessageModelConnection->makeConnectToModel(&ChatMessageModel::messageDeleted, [this](bool deletedByUser) {
mChatMessageModelConnection->invokeToCore([this, deletedByUser] {
//: Deleted
if (deletedByUser)
Utils::showInformationPopup(tr("info_toast_deleted_title"),
//: The message has been deleted
tr("info_toast_deleted_message"), true);
emit deleted();
});
});
mChatMessageModelConnection->makeConnectToCore(&ChatMessageCore::lMarkAsRead, [this] {
mChatMessageModelConnection->invokeToModel([this] { mChatMessageModel->markAsRead(); });
@ -308,10 +318,13 @@ void ChatMessageCore::setSelf(QSharedPointer<ChatMessageCore> me) {
auto imdnStatusList = computeDeliveryStatus(message);
mChatMessageModelConnection->invokeToCore([this, imdnStatusList] { setImdnStatusList(imdnStatusList); });
});
mChatMessageModelConnection->makeConnectToModel(&ChatMessageModel::ephemeralMessageTimerStarted,
[this](const std::shared_ptr<linphone::ChatMessage> &message) {});
mChatMessageModelConnection->makeConnectToModel(&ChatMessageModel::ephemeralMessageDeleted,
[this](const std::shared_ptr<linphone::ChatMessage> &message) {});
mChatMessageModelConnection->makeConnectToModel(
&ChatMessageModel::ephemeralMessageTimeUpdated,
[this](const std::shared_ptr<linphone::ChatMessage> &message, int expireTime) {
auto now = QDateTime::currentDateTime();
int duration = now.secsTo(QDateTime::fromSecsSinceEpoch(expireTime));
mChatMessageModelConnection->invokeToCore([this, duration] { setEphemeralDuration(duration); });
});
}
QList<ImdnStatus> ChatMessageCore::computeDeliveryStatus(const std::shared_ptr<linphone::ChatMessage> &message) {
@ -405,6 +418,21 @@ bool ChatMessageCore::isFromChatGroup() const {
return mIsFromChatGroup;
}
bool ChatMessageCore::isEphemeral() const {
return mIsEphemeral;
}
int ChatMessageCore::getEphemeralDuration() const {
return mEphemeralDuration;
}
void ChatMessageCore::setEphemeralDuration(int duration) {
if (mEphemeralDuration != duration) {
mEphemeralDuration = duration;
emit ephemeralDurationChanged(duration);
}
}
bool ChatMessageCore::hasFileContent() const {
return mHasFileContent;
}

View file

@ -89,6 +89,9 @@ class ChatMessageCore : public QObject, public AbstractObject {
messageStateChanged)
Q_PROPERTY(bool isRemoteMessage READ isRemoteMessage CONSTANT)
Q_PROPERTY(bool isFromChatGroup READ isFromChatGroup CONSTANT)
Q_PROPERTY(bool isEphemeral READ isEphemeral CONSTANT)
Q_PROPERTY(
int ephemeralDuration READ getEphemeralDuration WRITE setEphemeralDuration NOTIFY ephemeralDurationChanged)
Q_PROPERTY(bool isRead READ isRead WRITE setIsRead NOTIFY isReadChanged)
Q_PROPERTY(QString ownReaction READ getOwnReaction WRITE setOwnReaction NOTIFY messageReactionChanged)
Q_PROPERTY(QStringList imdnStatusListAsString READ getImdnStatusListLabels NOTIFY imdnStatusListChanged)
@ -130,6 +133,9 @@ public:
bool isRemoteMessage() const;
bool isFromChatGroup() const;
bool isEphemeral() const;
int getEphemeralDuration() const;
void setEphemeralDuration(int duration);
bool hasFileContent() const;
@ -169,6 +175,7 @@ signals:
void imdnStatusListChanged();
void messageReactionChanged();
void singletonReactionMapChanged();
void ephemeralDurationChanged(int duration);
void lDelete();
void deleted();
@ -205,6 +212,8 @@ private:
bool mHasFileContent = false;
bool mIsCalendarInvite = false;
bool mIsVoiceRecording = false;
bool mIsEphemeral = false;
int mEphemeralDuration = 0;
bool mIsOutgoing = false;
QString mTotalReactionsLabel;

View file

@ -58,6 +58,21 @@ QSharedPointer<ChatCore> EventLogList::getChatCore() const {
return mChatCore;
}
void EventLogList::connectItem(const QSharedPointer<EventLogCore> item) {
auto message = item->getChatMessageCore();
if (message) {
connect(message.get(), &ChatMessageCore::deleted, this, [this, item] {
emit mChatCore->lUpdateLastMessage();
remove(item);
});
connect(message.get(), &ChatMessageCore::ephemeralDurationChanged, this, [this, item](int duration) {
int i;
get(item.get(), &i);
emit dataChanged(index(i), index(i));
});
}
}
void EventLogList::setChatCore(QSharedPointer<ChatCore> core) {
if (mChatCore != core) {
if (mChatCore) disconnect(mChatCore.get(), &ChatCore::eventListChanged, this, nullptr);
@ -104,12 +119,7 @@ void EventLogList::setSelf(QSharedPointer<EventLogList> me) {
if (!mChatCore) return;
auto events = mChatCore->getEventLogList();
for (auto &event : events) {
auto message = event->getChatMessageCore();
if (message)
connect(message.get(), &ChatMessageCore::deleted, this, [this, message, event] {
emit mChatCore->lUpdateLastMessage();
remove(event);
});
connectItem(event);
}
resetData<EventLogCore>(events);
});

View file

@ -27,6 +27,7 @@
#include <QLocale>
class EventLogGui;
class EventLogCore;
class ChatCore;
class ChatGui;
// =============================================================================
@ -43,6 +44,8 @@ public:
void setChatCore(QSharedPointer<ChatCore> core);
void setChatGui(ChatGui *chat);
void connectItem(const QSharedPointer<EventLogCore> item);
int findFirstUnreadIndex();
void setSelf(QSharedPointer<EventLogList> me);

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -323,7 +323,7 @@ void ChatModel::onEphemeralMessageTimerStarted(const std::shared_ptr<linphone::C
void ChatModel::onEphemeralMessageDeleted(const std::shared_ptr<linphone::ChatRoom> &chatRoom,
const std::shared_ptr<const linphone::EventLog> &eventLog) {
emit onEphemeralMessageDeleted(chatRoom, eventLog);
emit ephemeralMessageDeleted(chatRoom, eventLog);
}
void ChatModel::onConferenceAddressGeneration(const std::shared_ptr<linphone::ChatRoom> &chatRoom) {

View file

@ -34,6 +34,16 @@ ChatMessageModel::ChatMessageModel(const std::shared_ptr<linphone::ChatMessage>
: ::Listener<linphone::ChatMessage, linphone::ChatMessageListener>(chatMessage, parent) {
// lDebug() << "[ChatMessageModel] new" << this << " / SDKModel=" << chatMessage.get();
mustBeInLinphoneThread(getClassName());
mEphemeralTimer.setInterval(60);
mEphemeralTimer.setSingleShot(false);
if (mMonitor->getEphemeralExpireTime() != 0) mEphemeralTimer.start();
connect(&mEphemeralTimer, &QTimer::timeout, this,
[this] { emit ephemeralMessageTimeUpdated(mMonitor, mMonitor->getEphemeralExpireTime()); });
connect(this, &ChatMessageModel::ephemeralMessageTimerStarted, this, [this] { mEphemeralTimer.start(); });
connect(this, &ChatMessageModel::ephemeralMessageDeleted, this, [this] {
mEphemeralTimer.stop();
deleteMessageFromChatRoom(false);
});
}
ChatMessageModel::~ChatMessageModel() {
@ -85,11 +95,11 @@ void ChatMessageModel::markAsRead() {
emit CoreModel::getInstance()->messageReadInChatRoom(mMonitor->getChatRoom());
}
void ChatMessageModel::deleteMessageFromChatRoom() {
void ChatMessageModel::deleteMessageFromChatRoom(bool deletedByUser) {
auto chatRoom = mMonitor->getChatRoom();
if (chatRoom) {
chatRoom->deleteMessage(mMonitor);
emit messageDeleted();
emit messageDeleted(deletedByUser);
}
}

View file

@ -51,7 +51,7 @@ public:
bool isRead() const;
void markAsRead();
void deleteMessageFromChatRoom();
void deleteMessageFromChatRoom(bool deletedByUser);
void sendReaction(const QString &reaction);
@ -64,7 +64,7 @@ public:
QString getOwnReaction() const;
signals:
void messageDeleted();
void messageDeleted(bool deletedByUser);
void messageRead();
void msgStateChanged(const std::shared_ptr<linphone::ChatMessage> &message, linphone::ChatMessage::State state);
@ -94,9 +94,11 @@ signals:
const std::shared_ptr<const linphone::ParticipantImdnState> &state);
void ephemeralMessageTimerStarted(const std::shared_ptr<linphone::ChatMessage> &message);
void ephemeralMessageDeleted(const std::shared_ptr<linphone::ChatMessage> &message);
void ephemeralMessageTimeUpdated(const std::shared_ptr<linphone::ChatMessage> &message, int expireTime);
private:
linphone::ChatMessage::State mMessageState;
QTimer mEphemeralTimer;
DECLARE_ABSTRACT_OBJECT

View file

@ -74,7 +74,7 @@ signals:
private:
DECLARE_ABSTRACT_OBJECT
void onEofReached(const std::shared_ptr<linphone::Player> &player);
void onEofReached(const std::shared_ptr<linphone::Player> &player) override;
};
#endif // SOUND_PLAYER_H_

View file

@ -1011,7 +1011,7 @@ template <typename T>
quint32 QExifImageHeader::calculateSize(const QMap<T, QExifValue> &values) const {
quint32 size = sizeof(quint16);
foreach (const QExifValue &value, values)
for (const QExifValue &value : values)
size += sizeOf(value);
return size;
@ -1387,7 +1387,7 @@ QExifImageHeader::readIfdValues(QDataStream &stream, int startPos, const QList<E
// This needs to be non-const so it works with gcc3
QList<ExifIfdHeader> headers_ = headers;
foreach (const ExifIfdHeader &header, headers_)
for (const ExifIfdHeader &header : headers_)
values[T(header.tag)] = readIfdValue(stream, startPos, header);
return values;
@ -1491,7 +1491,7 @@ QExifImageHeader::writeExifHeader(QDataStream &stream, quint16 tag, const QExifV
switch (value.type()) {
case QExifValue::Byte:
if (value.count() <= 4) {
foreach (quint8 byte, value.toByteVector())
for (quint8 byte : value.toByteVector())
stream << byte;
for (int j = value.count(); j < 4; j++)
stream << quint8(0);
@ -1526,7 +1526,7 @@ QExifImageHeader::writeExifHeader(QDataStream &stream, quint16 tag, const QExifV
break;
case QExifValue::Short:
if (value.count() <= 2) {
foreach (quint16 shrt, value.toShortVector())
for (quint16 shrt : value.toShortVector())
stream << shrt;
for (int j = value.count(); j < 2; j++)
stream << quint16(0);
@ -1588,7 +1588,7 @@ void QExifImageHeader::writeExifValue(QDataStream &stream, const QExifValue &val
switch (value.type()) {
case QExifValue::Byte:
if (value.count() > 4)
foreach (quint8 byte, value.toByteVector())
for (quint8 byte : value.toByteVector())
stream << byte;
break;
case QExifValue::Undefined:
@ -1603,27 +1603,27 @@ void QExifImageHeader::writeExifValue(QDataStream &stream, const QExifValue &val
break;
case QExifValue::Short:
if (value.count() > 2)
foreach (quint16 shrt, value.toShortVector())
for (quint16 shrt : value.toShortVector())
stream << shrt;
break;
case QExifValue::Long:
if (value.count() > 1)
foreach (quint32 lng, value.toLongVector())
for (quint32 lng : value.toLongVector())
stream << lng;
break;
case QExifValue::SignedLong:
if (value.count() > 1)
foreach (qint32 lng, value.toSignedLongVector())
for (qint32 lng : value.toSignedLongVector())
stream << lng;
break;
case QExifValue::Rational:
if (value.count() > 0)
foreach (QExifURational rational, value.toRationalVector())
for (QExifURational rational : value.toRationalVector())
stream << rational;
break;
case QExifValue::SignedRational:
if (value.count() > 0)
foreach (QExifSRational rational, value.toSignedRationalVector())
for (QExifSRational rational : value.toSignedRationalVector())
stream << rational;
break;
default:

View file

@ -356,9 +356,21 @@ QString Utils::formatTime(const QDateTime &date) {
}
QString Utils::formatDuration(int durationMs) {
QTime duration(0, 0);
duration = duration.addMSecs(durationMs);
return duration.hour() > 0 ? duration.toString("hh:mm:ss") : duration.toString("mm:ss");
auto now = QDateTime::currentDateTime();
auto end = now.addMSecs(durationMs);
auto daysTo = now.daysTo(end);
if (daysTo > 0) {
//: Tomorrow
if (daysTo == 1) return tr("duration_tomorrow");
else {
//: %1 jour(s)
return tr("duration_number_of_days", "", daysTo);
}
} else {
QTime duration(0, 0);
duration = duration.addMSecs(durationMs);
return duration.hour() > 0 ? duration.toString("hh:mm:ss") : duration.toString("mm:ss");
}
}
QString Utils::formatDateElapsedTime(const QDateTime &date) {

View file

@ -204,30 +204,60 @@ Control.Control {
}
}
RowLayout {
Layout.fillWidth: false
Layout.preferredHeight: childrenRect.height
Layout.alignment: mainItem.isRemoteMessage ? Qt.AlignLeft : Qt.AlignRight
Text {
text: UtilsCpp.formatDate(mainItem.chatMessage.core.timestamp, true, false, "dd/MM")
color: DefaultStyle.main2_500main
font {
pixelSize: Typography.p3.pixelSize
weight: Typography.p3.weight
layoutDirection: mainItem.isRemoteMessage ? Qt.RightToLeft : Qt.LeftToRight
spacing: Math.round(7 * DefaultStyle.dp)
RowLayout {
spacing: Math.round(3 * DefaultStyle.dp)
Layout.preferredHeight: childrenRect.height
Text {
id: ephemeralTime
visible: mainItem.chatMessage.core.isEphemeral
color: DefaultStyle.main2_500main
text: UtilsCpp.formatDuration(mainItem.chatMessage.core.ephemeralDuration * 1000)
font {
pixelSize: Typography.p3.pixelSize
weight: Typography.p3.weight
}
}
EffectImage {
visible: mainItem.chatMessage.core.isEphemeral
imageSource: AppIcons.clockCountDown
colorizationColor: DefaultStyle.main2_500main
Layout.preferredWidth: visible ? 14 * DefaultStyle.dp : 0
Layout.preferredHeight: visible ? 14 * DefaultStyle.dp : 0
}
}
EffectImage {
visible: !mainItem.isRemoteMessage
Layout.preferredWidth: visible ? 14 * DefaultStyle.dp : 0
Layout.preferredHeight: 14 * DefaultStyle.dp
colorizationColor: DefaultStyle.main1_500_main
imageSource: mainItem.msgState === LinphoneEnums.ChatMessageState.StateDelivered
? AppIcons.envelope
: mainItem.msgState === LinphoneEnums.ChatMessageState.StateDeliveredToUser
? AppIcons.check
: mainItem.msgState === LinphoneEnums.ChatMessageState.StateNotDelivered
? AppIcons.warningCircle
: mainItem.msgState === LinphoneEnums.ChatMessageState.StateDisplayed
? AppIcons.checks
: ""
RowLayout {
spacing: mainItem.isRemoteMessage ? 0 : Math.round(5 * DefaultStyle.dp)
Layout.alignment: Qt.AlignVCenter
Layout.preferredHeight: childrenRect.height
Text {
Layout.alignment: Qt.AlignVCenter
text: UtilsCpp.formatDate(mainItem.chatMessage.core.timestamp, true, false, "dd/MM")
color: DefaultStyle.main2_500main
font {
pixelSize: Typography.p3.pixelSize
weight: Typography.p3.weight
}
}
EffectImage {
visible: !mainItem.isRemoteMessage
Layout.preferredWidth: visible ? 14 * DefaultStyle.dp : 0
Layout.preferredHeight: visible ? 14 * DefaultStyle.dp : 0
Layout.alignment: Qt.AlignVCenter
colorizationColor: DefaultStyle.main1_500_main
imageSource: mainItem.msgState === LinphoneEnums.ChatMessageState.StateDelivered
? AppIcons.envelope
: mainItem.msgState === LinphoneEnums.ChatMessageState.StateDeliveredToUser
? AppIcons.check
: mainItem.msgState === LinphoneEnums.ChatMessageState.StateNotDelivered
? AppIcons.warningCircle
: mainItem.msgState === LinphoneEnums.ChatMessageState.StateDisplayed
? AppIcons.checks
: ""
}
}
}
}