From 50445bccf1c8dd5abf7b4dfbe4ca82ebaeedb58a Mon Sep 17 00:00:00 2001 From: Black Hat Date: Thu, 9 May 2019 21:18:04 +0800 Subject: [PATCH] Better message bubbles. --- .../Component/Timeline/FileDelegate.qml | 22 +++++-- .../Component/Timeline/ImageDelegate.qml | 15 +---- .../Component/Timeline/MessageDelegate.qml | 54 ++++++++++++++- imports/Spectral/Panel/RoomPanel.qml | 6 +- src/messageeventmodel.cpp | 65 +++++++++++++------ src/messageeventmodel.h | 16 +++-- 6 files changed, 134 insertions(+), 44 deletions(-) diff --git a/imports/Spectral/Component/Timeline/FileDelegate.qml b/imports/Spectral/Component/Timeline/FileDelegate.qml index 8dbaa75..f956370 100644 --- a/imports/Spectral/Component/Timeline/FileDelegate.qml +++ b/imports/Spectral/Component/Timeline/FileDelegate.qml @@ -15,7 +15,7 @@ import Spectral.Font 0.1 import Spectral.Effect 2.0 ColumnLayout { - readonly property bool avatarVisible: !sentByMe && (aboveAuthor !== author || aboveSection !== section || aboveEventType === "state" || aboveEventType === "emote" || aboveEventType === "other") + readonly property bool avatarVisible: !sentByMe && showAuthor readonly property bool sentByMe: author === currentRoom.localUser property bool openOnFinished: false @@ -39,8 +39,6 @@ ColumnLayout { } RowLayout { - Layout.alignment: sentByMe ? Qt.AlignRight : Qt.AlignLeft - z: -5 id: messageRow @@ -111,7 +109,7 @@ ColumnLayout { } Label { - text: progressInfo.active ? (progressInfo.progress + "/" + progressInfo.total) : content.info ? content.info.size : "Unknown" + text: progressInfo.active ? (humanSize(progressInfo.progress) + "/" + humanSize(progressInfo.total)) : humanSize(content.info ? content.info.size : 0) color: MPalette.lighter } } @@ -195,4 +193,20 @@ ColumnLayout { if (Qt.openUrlExternally(progressInfo.localPath)) return; if (Qt.openUrlExternally(progressInfo.localDir)) return; } + + + function humanSize(bytes) + { + if (!bytes) + return qsTr("Unknown", "Unknown attachment size") + if (bytes < 4000) + return qsTr("%1 bytes").arg(bytes) + bytes = Math.round(bytes / 100) / 10 + if (bytes < 2000) + return qsTr("%1 KB").arg(bytes) + bytes = Math.round(bytes / 100) / 10 + if (bytes < 2000) + return qsTr("%1 MB").arg(bytes) + return qsTr("%1 GB").arg(Math.round(bytes / 100) / 10) + } } diff --git a/imports/Spectral/Component/Timeline/ImageDelegate.qml b/imports/Spectral/Component/Timeline/ImageDelegate.qml index 43e8fa2..2d4e472 100644 --- a/imports/Spectral/Component/Timeline/ImageDelegate.qml +++ b/imports/Spectral/Component/Timeline/ImageDelegate.qml @@ -15,7 +15,7 @@ import Spectral.Effect 2.0 import Spectral.Font 0.1 ColumnLayout { - readonly property bool avatarVisible: !sentByMe && (aboveAuthor !== author || aboveSection !== section || aboveEventType === "state" || aboveEventType === "emote" || aboveEventType === "other") + readonly property bool avatarVisible: !sentByMe && showAuthor readonly property bool sentByMe: author === currentRoom.localUser property bool openOnFinished: false @@ -44,8 +44,6 @@ ColumnLayout { } RowLayout { - Layout.alignment: sentByMe ? Qt.AlignRight : Qt.AlignLeft - z: -5 id: messageRow @@ -121,17 +119,6 @@ ColumnLayout { } } - Rectangle { - anchors.fill: parent - - color: "transparent" - radius: 24 - antialiasing: true - - border.width: 4 - border.color: MPalette.background - } - Rectangle { anchors.fill: parent diff --git a/imports/Spectral/Component/Timeline/MessageDelegate.qml b/imports/Spectral/Component/Timeline/MessageDelegate.qml index 9a5a77f..e324727 100644 --- a/imports/Spectral/Component/Timeline/MessageDelegate.qml +++ b/imports/Spectral/Component/Timeline/MessageDelegate.qml @@ -12,7 +12,7 @@ import Spectral.Menu.Timeline 2.0 import Spectral.Effect 2.0 RowLayout { - readonly property bool avatarVisible: !sentByMe && (aboveAuthor !== author || aboveSection !== section || aboveEventType === "state" || aboveEventType === "emote" || aboveEventType === "other") + readonly property bool avatarVisible: !sentByMe && showAuthor readonly property bool sentByMe: author === currentRoom.localUser readonly property bool darkBackground: !sentByMe readonly property bool replyVisible: replyEventId || false @@ -68,6 +68,58 @@ RowLayout { radius: 18 antialiasing: true + Rectangle { + anchors.top: parent.top + anchors.left: parent.left + + width: parent.width / 2 + height: parent.height / 2 + + visible: !sentByMe && (bubbleShape == 3 || bubbleShape == 2) + + color: sentByMe ? MPalette.background : eventType === "notice" ? MPalette.primary : MPalette.accent + radius: 2 + } + + Rectangle { + anchors.top: parent.top + anchors.right: parent.right + + width: parent.width / 2 + height: parent.height / 2 + + visible: sentByMe && (bubbleShape == 3 || bubbleShape == 2) + + color: sentByMe ? MPalette.background : eventType === "notice" ? MPalette.primary : MPalette.accent + radius: 2 + } + + Rectangle { + anchors.bottom: parent.bottom + anchors.left: parent.left + + width: parent.width / 2 + height: parent.height / 2 + + visible: !sentByMe && (bubbleShape == 1 || bubbleShape == 2) + + color: sentByMe ? MPalette.background : eventType === "notice" ? MPalette.primary : MPalette.accent + radius: 2 + } + + Rectangle { + anchors.bottom: parent.bottom + anchors.right: parent.right + + width: parent.width / 2 + height: parent.height / 2 + + visible: sentByMe && (bubbleShape == 1 || bubbleShape == 2) + + color: sentByMe ? MPalette.background : eventType === "notice" ? MPalette.primary : MPalette.accent + radius: 2 + } + AutoMouseArea { anchors.fill: parent diff --git a/imports/Spectral/Panel/RoomPanel.qml b/imports/Spectral/Panel/RoomPanel.qml index 78a930a..056163d 100644 --- a/imports/Spectral/Panel/RoomPanel.qml +++ b/imports/Spectral/Panel/RoomPanel.qml @@ -105,7 +105,7 @@ Item { id: messageListView - spacing: 4 + spacing: 2 displayMarginBeginning: 100 displayMarginEnd: 100 @@ -186,6 +186,8 @@ Item { DelegateChoice { roleValue: "image" delegate: ImageDelegate { + anchors.right: sentByMe ? parent.right : undefined + Layout.maximumWidth: parent.width } } @@ -193,6 +195,8 @@ Item { DelegateChoice { roleValue: "file" delegate: FileDelegate { + anchors.right: sentByMe ? parent.right : undefined + Layout.maximumWidth: parent.width } } diff --git a/src/messageeventmodel.cpp b/src/messageeventmodel.cpp index 40c15d4..cc9b202 100644 --- a/src/messageeventmodel.cpp +++ b/src/messageeventmodel.cpp @@ -18,14 +18,10 @@ QHash MessageEventModel::roleNames() const { QHash roles = QAbstractItemModel::roleNames(); roles[EventTypeRole] = "eventType"; roles[MessageRole] = "message"; - roles[AboveEventTypeRole] = "aboveEventType"; roles[EventIdRole] = "eventId"; roles[TimeRole] = "time"; - roles[AboveTimeRole] = "aboveTime"; roles[SectionRole] = "section"; - roles[AboveSectionRole] = "aboveSection"; roles[AuthorRole] = "author"; - roles[AboveAuthorRole] = "aboveAuthor"; roles[ContentRole] = "content"; roles[ContentTypeRole] = "contentType"; roles[HighlightRole] = "highlight"; @@ -38,6 +34,9 @@ QHash MessageEventModel::roleNames() const { roles[ReplyAuthorRole] = "replyAuthor"; roles[ReplyDisplayRole] = "replyDisplay"; roles[UserMarkerRole] = "userMarker"; + roles[ShowTimestampRole] = "showTimestamp"; + roles[ShowAuthorRole] = "showAuthor"; + roles[BubbleShapeRole] = "bubbleShape"; return roles; } @@ -84,9 +83,9 @@ void MessageEventModel::setRoom(SpectralRoom* room) { if (biggest < m_currentRoom->maxTimelineIndex()) { auto rowBelowInserted = m_currentRoom->maxTimelineIndex() - biggest + timelineBaseIndex() - 1; - refreshEventRoles(rowBelowInserted, - {AboveEventTypeRole, AboveAuthorRole, - AboveSectionRole, AboveTimeRole}); + refreshEventRoles( + rowBelowInserted, + {ShowTimestampRole, ShowAuthorRole, BubbleShapeRole}); } for (auto i = m_currentRoom->maxTimelineIndex() - biggest; i <= m_currentRoom->maxTimelineIndex() - lowest; ++i) @@ -117,8 +116,7 @@ void MessageEventModel::setRoom(SpectralRoom* room) { refreshEventRoles(timelineBaseIndex() + 1, {ReadMarkerRole}); if (timelineBaseIndex() > 0) // Refresh below, see #312 refreshEventRoles(timelineBaseIndex() - 1, - {AboveEventTypeRole, AboveAuthorRole, - AboveSectionRole, AboveTimeRole}); + {ShowTimestampRole, ShowAuthorRole, BubbleShapeRole}); }); connect(m_currentRoom, &Room::pendingEventChanged, this, &MessageEventModel::refreshRow); @@ -417,23 +415,50 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const { return {}; } - if (role == AboveEventTypeRole || role == AboveSectionRole || - role == AboveAuthorRole || role == AboveTimeRole) + if (role == ShowTimestampRole || role == ShowAuthorRole) for (auto r = row + 1; r < rowCount(); ++r) { auto i = index(r); - if (data(i, SpecialMarksRole) != EventStatus::Hidden) + if (data(i, SpecialMarksRole) != EventStatus::Hidden) { switch (role) { - case AboveEventTypeRole: - return data(i, EventTypeRole); - case AboveSectionRole: - return data(i, SectionRole); - case AboveAuthorRole: - return data(i, AuthorRole); - case AboveTimeRole: - return data(i, TimeRole); + case ShowTimestampRole: + return data(i, TimeRole) + .toDateTime() + .msecsTo(data(idx, TimeRole).toDateTime()) > 600000; + case ShowAuthorRole: + return data(i, AuthorRole) != data(idx, AuthorRole); } + } } + if (role == BubbleShapeRole) { // TODO: Convoluted logic. + int belowRow = -1; // Invalid + + for (auto r = row - 1; r >= 0; --r) { + auto i = index(r); + if (data(i, SpecialMarksRole) != EventStatus::Hidden) { + belowRow = r; + break; + } + } + + bool aboveShow, belowShow; + aboveShow = data(idx, ShowAuthorRole).toBool() || + data(idx, ShowTimestampRole).toBool(); + if (belowRow == -1) + belowShow = true; + else + belowShow = data(index(belowRow), ShowAuthorRole).toBool() || + data(index(belowRow), ShowTimestampRole).toBool(); + + if (aboveShow && belowShow) + return BubbleShapes::NoShape; + if (aboveShow && !belowShow) + return BubbleShapes::BeginShape; + if (belowShow) + return BubbleShapes::EndShape; + return BubbleShapes::MiddleShape; + } + return {}; } diff --git a/src/messageeventmodel.h b/src/messageeventmodel.h index 00b9c3a..2ae1212 100644 --- a/src/messageeventmodel.h +++ b/src/messageeventmodel.h @@ -14,14 +14,10 @@ class MessageEventModel : public QAbstractListModel { enum EventRoles { EventTypeRole = Qt::UserRole + 1, MessageRole, - AboveEventTypeRole, EventIdRole, TimeRole, - AboveTimeRole, SectionRole, - AboveSectionRole, AuthorRole, - AboveAuthorRole, ContentRole, ContentTypeRole, HighlightRole, @@ -30,13 +26,25 @@ class MessageEventModel : public QAbstractListModel { LongOperationRole, AnnotationRole, UserMarkerRole, + // For reply ReplyEventIdRole, ReplyAuthorRole, ReplyDisplayRole, + + ShowTimestampRole, + ShowAuthorRole, + BubbleShapeRole, // For debugging EventResolvedTypeRole, }; + enum BubbleShapes { + NoShape = 0, + BeginShape, + MiddleShape, + EndShape, + }; + explicit MessageEventModel(QObject* parent = nullptr); ~MessageEventModel();