From 4c92cb9936fd8a6cd86c44632bc9a1f992a2ef15 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Blin?=
 <sebastien.blin@savoirfairelinux.com>
Date: Fri, 23 Feb 2024 16:37:46 -0500
Subject: [PATCH] chatview: add check for last sent message

Change-Id: I233d5df05432371adf6b2bdf8e8de25bd7e65058
---
 resources/icons/Receive.svg                 | 10 ++++++
 src/app/commoncomponents/SBSMessageBase.qml | 40 ++++++++++++---------
 src/app/net/jami/Constants/JamiTheme.qml    |  1 +
 src/libclient/api/interaction.h             |  5 +++
 src/libclient/api/messagelistmodel.h        |  4 +++
 src/libclient/messagelistmodel.cpp          | 30 +++++++++++++++-
 6 files changed, 73 insertions(+), 17 deletions(-)
 create mode 100644 resources/icons/Receive.svg

diff --git a/resources/icons/Receive.svg b/resources/icons/Receive.svg
new file mode 100644
index 000000000..1be71fbd7
--- /dev/null
+++ b/resources/icons/Receive.svg
@@ -0,0 +1,10 @@
+<svg id="Receive" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="12" height="12" viewBox="0 0 12 12">
+  <defs>
+    <clipPath id="clip-path">
+      <rect id="Rectangle_429" data-name="Rectangle 429" width="12" height="12" fill="none"/>
+    </clipPath>
+  </defs>
+  <g id="Group_225" data-name="Group 225" clip-path="url(#clip-path)">
+    <path id="Path_333" data-name="Path 333" d="M6.43,8.784,3.007,5.362,4.06,4.309l2.37,2.37,4.314-4.314A5.966,5.966,0,0,0,6,0c-.032,0-.061.008-.094.01A5.98,5.98,0,0,0,.094,5.074,5.911,5.911,0,0,0,0,6a5.911,5.911,0,0,0,.094.926A5.98,5.98,0,0,0,5.906,11.99c.032,0,.061.01.094.01a6,6,0,0,0,5.533-8.32Z" fill="#60c880"/>
+  </g>
+</svg>
diff --git a/src/app/commoncomponents/SBSMessageBase.qml b/src/app/commoncomponents/SBSMessageBase.qml
index 0f61ff34f..e458ec671 100644
--- a/src/app/commoncomponents/SBSMessageBase.qml
+++ b/src/app/commoncomponents/SBSMessageBase.qml
@@ -98,7 +98,6 @@ Control {
             Layout.fillHeight: true
         }
 
-
         Label {
             id: username
 
@@ -300,10 +299,7 @@ Control {
                         anchors.verticalCenter: parent.verticalCenter
                         anchors.right: isOutgoing ? optionButtonItem.right : undefined
                         anchors.left: !isOutgoing ? optionButtonItem.left : undefined
-                        visible: CurrentAccount.type !== Profile.Type.SIP
-                                 && root.type !== Interaction.Type.CALL
-                                 && Body !== ""
-                                 && (bubbleArea.bubbleHovered || hovered || reply.hovered || bgHandler.hovered)
+                        visible: CurrentAccount.type !== Profile.Type.SIP && root.type !== Interaction.Type.CALL && Body !== "" && (bubbleArea.bubbleHovered || hovered || reply.hovered || bgHandler.hovered)
                         source: JamiResources.more_vert_24dp_svg
                         width: optionButtonItem.width / 2
                         height: optionButtonItem.height
@@ -358,10 +354,7 @@ Control {
                         anchors.rightMargin: 5
                         anchors.right: isOutgoing ? more.left : undefined
                         anchors.left: !isOutgoing ? more.right : undefined
-                        visible: CurrentAccount.type !== Profile.Type.SIP
-                                 && root.type !== Interaction.Type.CALL
-                                 && Body !== ""
-                                 && (bubbleArea.bubbleHovered || hovered || more.hovered || bgHandler.hovered)
+                        visible: CurrentAccount.type !== Profile.Type.SIP && root.type !== Interaction.Type.CALL && Body !== "" && (bubbleArea.bubbleHovered || hovered || more.hovered || bgHandler.hovered)
 
                         onClicked: {
                             MessagesAdapter.editId = "";
@@ -389,14 +382,14 @@ Control {
                     property bool bubbleHovered
                     property string imgSource
 
-                    width: (root.type === Interaction.Type.TEXT ? root.textContentWidth + ( IsEmojiOnly || root.bigMsg ? 0 : root.timeWidth + root.editedWidth): innerContent.childrenRect.width)
+                    width: (root.type === Interaction.Type.TEXT ? root.textContentWidth + (IsEmojiOnly || root.bigMsg ? 0 : root.timeWidth + root.editedWidth) : innerContent.childrenRect.width)
                     height: innerContent.childrenRect.height + (visible ? root.extraHeight : 0) + (root.bigMsg ? 15 : 0)
 
                     HoverHandler {
                         target: root
                         enabled: root.type === Interaction.Type.DATA_TRANSFER
                         onHoveredChanged: {
-                            root.hoveredLink = enabled && hovered ? bubble.imgSource : ""
+                            root.hoveredLink = enabled && hovered ? bubble.imgSource : "";
                         }
                     }
 
@@ -406,12 +399,12 @@ Control {
                         showTime: IsEmojiOnly && !(root.seq === MsgSeq.last || root.seq === MsgSeq.single) ? false : true
                         formattedTime: root.formattedTime
 
-                        timeColor: IsEmojiOnly || root.timeUnderBubble? (JamiTheme.darkTheme ? "white" : "dark") : (UtilsAdapter.luma(bubble.color) ? "white" : "dark")
+                        timeColor: IsEmojiOnly || root.timeUnderBubble ? (JamiTheme.darkTheme ? "white" : "dark") : (UtilsAdapter.luma(bubble.color) ? "white" : "dark")
                         timeLabel.opacity: 0.5
 
                         anchors.bottom: parent.bottom
                         anchors.right: IsEmojiOnly ? (isOutgoing ? parent.right : undefined) : parent.right
-                        anchors.left: ((IsEmojiOnly|| root.timeUnderBubble) && !isOutgoing) ? parent.left : undefined
+                        anchors.left: ((IsEmojiOnly || root.timeUnderBubble) && !isOutgoing) ? parent.left : undefined
                         anchors.leftMargin: (IsEmojiOnly && !isOutgoing && emojiReactions.visible) ? bubble.timePosition : 0
                         anchors.rightMargin: IsEmojiOnly ? ((isOutgoing && emojiReactions.visible) ? bubble.timePosition : 0) : (root.timeUnderBubble ? 0 : 10)
                         timeLabel.Layout.bottomMargin: {
@@ -432,7 +425,7 @@ Control {
                         anchors.left: root.bigMsg ? bubble.left : timestampItem.left
                         anchors.bottom: parent.bottom
                         anchors.bottomMargin: root.bigMsg || bubble.isDeleted ? 6 : 10
-                        anchors.leftMargin: root.bigMsg ? 10 : - timestampItem.width - 16
+                        anchors.leftMargin: root.bigMsg ? 10 : -timestampItem.width - 16
                         visible: bubble.isEdited
                         z: 1
                         ResponsiveImage {
@@ -489,7 +482,7 @@ Control {
                     borderColor: root.getBaseColor()
                     maxWidth: 2 / 3 * maxMsgWidth - JamiTheme.emojiMargins
 
-                    state: root.isOutgoing ? "anchorsRight" : (IsEmojiOnly ? "anchorsLeft" :(emojiReactions.width > bubble.width - JamiTheme.emojiMargins ? "anchorsLeft" : "anchorsRight"))
+                    state: root.isOutgoing ? "anchorsRight" : (IsEmojiOnly ? "anchorsLeft" : (emojiReactions.width > bubble.width - JamiTheme.emojiMargins ? "anchorsLeft" : "anchorsRight"))
 
                     TapHandler {
                         onTapped: {
@@ -597,7 +590,7 @@ Control {
                     radius: width / 2
                     width: 12
                     height: 12
-                    border.color: JamiTheme.tintedBlue
+                    border.color: JamiTheme.sending
                     border.width: 1
                     color: JamiTheme.transparentColor
                     visible: isOutgoing && Status === Interaction.Status.SENDING
@@ -605,6 +598,21 @@ Control {
                     anchors.bottom: parent.bottom
                 }
 
+                ResponsiveImage {
+                    id: sent
+
+                    containerHeight: 12
+                    containerWidth: 12
+
+                    width: 12
+                    height: 12
+
+                    visible: IsLastSent && !readsOne.visible
+                    anchors.bottom: parent.bottom
+
+                    source: JamiResources.receive_svg
+                }
+
                 ReadStatus {
                     id: readsOne
 
diff --git a/src/app/net/jami/Constants/JamiTheme.qml b/src/app/net/jami/Constants/JamiTheme.qml
index e976bb7de..0a228c101 100644
--- a/src/app/net/jami/Constants/JamiTheme.qml
+++ b/src/app/net/jami/Constants/JamiTheme.qml
@@ -588,6 +588,7 @@ Item {
 
     property real cornerIconSize: 40
 
+    property color sending: darkTheme ? "#8c8c8c" : "#7f7f7f"
     property color wizardIconColor: darkTheme ? "#8c8c8c" : "#7f7f7f"
 
     // InfoBox
diff --git a/src/libclient/api/interaction.h b/src/libclient/api/interaction.h
index acfbead61..7d2f68dae 100644
--- a/src/libclient/api/interaction.h
+++ b/src/libclient/api/interaction.h
@@ -413,6 +413,11 @@ struct Info
     Info& operator=(const Info& other) = delete;
     Info& operator=(Info&& other) = default;
 
+    bool sent() const
+    {
+        return status == Status::SUCCESS || status == Status::DISPLAYED || status == Status::TRANSFER_FINISHED;
+    }
+
     void init(const MapStringString& message, const QString& accountURI)
     {
         type = to_type(message["type"]);
diff --git a/src/libclient/api/messagelistmodel.h b/src/libclient/api/messagelistmodel.h
index 582b1dde9..f765702e6 100644
--- a/src/libclient/api/messagelistmodel.h
+++ b/src/libclient/api/messagelistmodel.h
@@ -57,6 +57,7 @@ struct Info;
     X(FileExtension) \
     X(Readers) \
     X(IsEmojiOnly) \
+    X(IsLastSent) \
     X(Index)
 
 namespace MessageList {
@@ -146,6 +147,9 @@ private:
     QMap<QString, QStringList> messageToReaders_;    // {"messageId": ["peer1", "peer2"]}
     QMap<QString, QSet<QString>> replyTo_;
 
+    QString lastSent_;
+    int lastSentIdx_ {-1};
+
     iterator find(const QString& msgId);
     int move(iterator it, const QString& newParentId);
     QVariant data(int idx, int role = Qt::DisplayRole) const;
diff --git a/src/libclient/messagelistmodel.cpp b/src/libclient/messagelistmodel.cpp
index e466448ab..51f93090f 100644
--- a/src/libclient/messagelistmodel.cpp
+++ b/src/libclient/messagelistmodel.cpp
@@ -146,6 +146,14 @@ MessageListModel::insert(const QString& id, const interaction::Info& interaction
     }
     beginInsertRows(QModelIndex(), index, index);
     interactions_.emplace(interactions_.cbegin() + index, id, interaction);
+    // Update last sent if the message is outgoing and successful.
+    if (interaction.sent() && index > lastSentIdx_) {
+        auto oldIdx = indexOfMessage(lastSent_);
+        lastSentIdx_ = index;
+        lastSent_ = id;
+        auto modelIndex = QAbstractListModel::index(oldIdx, 0);
+        Q_EMIT dataChanged(modelIndex, modelIndex, {Role::IsLastSent});
+    }
     endInsertRows();
     return true;
 }
@@ -160,6 +168,14 @@ MessageListModel::append(const QString& id, const interaction::Info& interaction
     }
     beginInsertRows(QModelIndex(), interactions_.size(), interactions_.size());
     interactions_.emplace_back(id, interaction);
+    // Update last sent if the message is outgoing and successful.
+    if (interaction.sent()) {
+        auto oldIdx = indexOfMessage(lastSent_);
+        lastSentIdx_ = interactions_.size() - 1;
+        lastSent_ = id;
+        auto modelIndex = QAbstractListModel::index(oldIdx, 0);
+        Q_EMIT dataChanged(modelIndex, modelIndex, {Role::IsLastSent});
+    }
     endInsertRows();
     return true;
 }
@@ -218,8 +234,18 @@ MessageListModel::updateStatus(const QString& id,
         it->second.body = newBody;
         roles.push_back(Role::Body);
     }
-    auto modelIndex = QAbstractListModel::index(indexOfMessage(id), 0);
+    // Update last sent if the message is outgoing and successful.
+    auto idx = indexOfMessage(id);
+    auto modelIndex = QAbstractListModel::index(idx, 0);
     Q_EMIT dataChanged(modelIndex, modelIndex, roles);
+    if (it->second.sent() && idx > lastSentIdx_) {
+        auto oldIdx = indexOfMessage(lastSent_);
+        lastSentIdx_ = idx;
+        lastSent_ = id;
+        Q_EMIT dataChanged(modelIndex, modelIndex, {Role::IsLastSent});
+        modelIndex = QAbstractListModel::index(oldIdx, 0);
+        Q_EMIT dataChanged(modelIndex, modelIndex, {Role::IsLastSent});
+    }
     return true;
 }
 
@@ -486,6 +512,8 @@ MessageListModel::dataForItem(const item_t& item, int, int role) const
         }
         return QVariant(item.second.body);
     }
+    case Role::IsLastSent:
+        return QVariant(item.first == lastSent_);
     case Role::Timestamp:
         return QVariant::fromValue(item.second.timestamp);
     case Role::Duration:
-- 
GitLab