1
0
Fork 0
mirror of https://git.jami.net/savoirfairelinux/jami-client-qt.git synced 2025-08-03 06:15:39 +02:00

MessageListView: fix message bubble UI

+ redesign the "Scroll to end of conversation"
+ redesign the "bubble / screen ratio"
+ redesign the reply bubbles: color, shape(in & out a message sequence), "In reply To" message
+ change the configurable color of the main user's bubbles
+ show the display name when replying to a transfer message
+ fix incorrectly loaded reply message data by synchronizing to messages loaded request
+ fix reply to an internet link
+ redesign the call bubbles (the new design need to be applied but in another patch)

GitLab: #959
GitLab: #967

Change-Id: Id646ff875644425b03367838b5b46f2242294563
This commit is contained in:
Franck Laurent 2023-02-13 16:49:04 -05:00 committed by Sébastien Blin
parent 84d625c1b4
commit d5064040d7
12 changed files with 266 additions and 118 deletions

View file

@ -61,12 +61,10 @@ SBSMessageBase {
if (ConfId === "" && Duration === 0) {
// If missed, we can add a darker pattern
return isOutgoing ?
Qt.darker(JamiTheme.messageOutBgColor, 1.5) :
Qt.lighter(CurrentConversation.color, 1.5) :
Qt.darker(JamiTheme.messageInBgColor, 1.5)
}
return isOutgoing ?
JamiTheme.messageOutBgColor :
CurrentConversation.isCoreDialog ? JamiTheme.messageInBgColor : Qt.lighter(CurrentConversation.color, 1.5)
return isOutgoing ? CurrentConversation.color : JamiTheme.messageInBgColor
}
innerContent.children: [
@ -88,8 +86,13 @@ SBSMessageBase {
return Body
}
horizontalAlignment: Qt.AlignHCenter
font.pointSize: JamiTheme.contactEventPointSize
font.pixelSize: JamiTheme.emojiBubbleSize
font.hintingPreference: Font.PreferNoHinting
font.bold: true
renderType: Text.NativeRendering
textFormat: Text.MarkdownText
color: UtilsAdapter.luma(bubble.color) ?
JamiTheme.chatviewTextColorLight :
JamiTheme.chatviewTextColorDark
@ -115,4 +118,4 @@ SBSMessageBase {
opacity: 0
Behavior on opacity { NumberAnimation { duration: 100 } }
Component.onCompleted: opacity = 1
}
}

View file

@ -26,9 +26,10 @@ Rectangle {
id: root
property bool out: true
property int type: MsgSeq.single
property bool isReply: false
Rectangle {
id: mask
visible: type !== MsgSeq.single
visible: type !== MsgSeq.single && !isReply
z: -1
radius: 5
color: root.color
@ -40,4 +41,34 @@ Rectangle {
bottomMargin: type === MsgSeq.last ? root.height /2 : 0
}
}
Rectangle {
id: maskReply
visible: isReply
z: -1
radius: 5
color: root.color
anchors {
fill: parent
leftMargin: out ? 0 : root.width/2
rightMargin: !out ? 0 : root.width/2
topMargin: 0
bottomMargin: root.height /2
}
}
Rectangle {
id: maskReplyFirst
visible: isReply && type === MsgSeq.first
z: -2
radius: 5
color: root.color
anchors {
fill: parent
leftMargin: out ? root.width/2 : 0
rightMargin: out ? 0 : root.width/2
topMargin: root.width/5
bottomMargin: 0
}
}
}

View file

@ -19,92 +19,89 @@ import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import net.jami.Models 1.1
import net.jami.Adapters 1.1
import net.jami.Constants 1.1
Item {
id: root
anchors.right: isOutgoing ? parent.right : undefined
visible: ReplyTo !== ""
width: visible ? replyToRow.width : 0
height: replyToRow.height + replyToRow.anchors.topMargin
width: body.width
height: Math.min(JamiTheme.sbsMessageBaseMaximumReplyHeight, body.height)
clip: true
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 !== "")
MessagesAdapter.loadConversationUntil(ReplyTo)
if (ReplyTo !== "") {
// Store the request Id for later filtering.
requestId = MessagesAdapter.loadConversationUntil(ReplyTo)
}
}
MouseArea {
Connections {
target: MessagesAdapter
z: 2
anchors.fill: parent
RowLayout {
id: replyToRow
anchors.top: parent.top
anchors.topMargin: JamiTheme.preferredMarginSize / 2
property bool isSelf: ReplyToAuthor === CurrentAccount.uri || ReplyToAuthor === ""
Label {
id: replyTo
text: JamiStrings.inReplyTo
color: UtilsAdapter.luma(bubble.color) ?
JamiTheme.chatviewTextColorLight :
JamiTheme.chatviewTextColorDark
font.pointSize: JamiTheme.textFontSize
font.kerning: true
font.bold: true
Layout.leftMargin: JamiTheme.preferredMarginSize
}
Avatar {
id: avatarReply
Layout.preferredWidth: JamiTheme.avatarReadReceiptSize
Layout.preferredHeight: JamiTheme.avatarReadReceiptSize
showPresenceIndicator: false
imageId: {
if (replyToRow.isSelf)
return CurrentAccount.id
return ReplyToAuthor
}
mode: replyToRow.isSelf ? Avatar.Mode.Account : Avatar.Mode.Contact
}
Text {
id: body
Layout.maximumWidth: JamiTheme.preferredFieldWidth - JamiTheme.preferredMarginSize
Layout.rightMargin: JamiTheme.preferredMarginSize
TextMetrics {
id: metrics
elide: Text.ElideRight
elideWidth: JamiTheme.preferredFieldWidth - JamiTheme.preferredMarginSize
text: ReplyToBody === "" && ReplyToAuthor !== "" ? "*(Deleted Message)*" : ReplyToBody
}
textFormat: Text.MarkdownText
text: metrics.elidedText
color: UtilsAdapter.luma(bubble.color) ?
JamiTheme.chatviewTextColorLight :
JamiTheme.chatviewTextColorDark
font.pointSize: JamiTheme.textFontSize
font.kerning: true
font.bold: true
function onMoreMessagesLoaded(loadingRequestId) {
// Filter for the request Id we're waiting for (now the message is loaded).
if (requestId === loadingRequestId) {
requestId = -1
replyTransferName = MessagesAdapter.dataForInteraction(ReplyTo, MessageList.TransferName)
}
}
}
onClicked: function(mouse) {
CurrentConversation.scrollToMsg(ReplyTo)
TextEdit {
id: body
text: replyTransferName ?
replyTransferName :
(ReplyToBody === "" && ReplyToAuthor !== "") ?
JamiStrings.deleteReplyMessage :
(ReplyToBody ? ReplyToBody : "")
width: Math.min(JamiTheme.sbsMessageBaseMaximumReplyWidth, implicitWidth)
horizontalAlignment: Text.AlignLeft
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
selectByMouse: true
font.pixelSize: IsEmojiOnly? JamiTheme.chatviewEmojiSize : JamiTheme.emojiBubbleSize
font.hintingPreference: Font.PreferNoHinting
renderType: Text.NativeRendering
textFormat: Text.MarkdownText
readOnly: true
color: getBaseColor()
function getBaseColor() {
var baseColor
if (IsEmojiOnly) {
if (JamiTheme.darkTheme)
baseColor = JamiTheme.chatviewTextColorLight
else
baseColor = JamiTheme.chatviewTextColorDark
} else {
if (UtilsAdapter.luma(replyBubble.color))
baseColor = JamiTheme.chatviewTextColorLight
else
baseColor = JamiTheme.chatviewTextColorDark
}
return baseColor
}
}
Rectangle {
anchors.fill: parent
visible: body.height > JamiTheme.sbsMessageBaseMaximumReplyHeight
gradient: Gradient {
GradientStop {position: 0.66 ; color: "transparent"}
GradientStop {position: 1 ; color: replyBubble.color}
}
}
}

View file

@ -58,6 +58,10 @@ Control {
width: listView.width
height: mainColumnLayout.implicitHeight
property real textContentWidth
property real textContentHeight
property bool isReply: ReplyTo !== ""
// If the ListView attached properties are not available,
// then the root delegate is likely a Loader.
readonly property ListView listView: ListView.view ?
@ -89,6 +93,7 @@ Control {
Item {
id: usernameblock
Layout.preferredHeight: (seq === MsgSeq.first || seq === MsgSeq.single) ? 10 : 0
visible: !isReply
Label {
id: username
@ -104,11 +109,104 @@ Control {
}
Item {
id: replyItem
property bool isSelf: ReplyToAuthor === CurrentAccount.uri
visible: root.isReply
width: parent.width
Layout.fillWidth: true
Layout.preferredHeight: childrenRect.height
Layout.topMargin: JamiTheme.sbsMessageBaseReplyTopMargin
Layout.leftMargin: isOutgoing ? undefined : JamiTheme.sbsMessageBaseReplyMargin
Layout.rightMargin: !isOutgoing ? undefined : JamiTheme.sbsMessageBaseReplyMargin
transform: Translate { y: JamiTheme.sbsMessageBaseReplyBottomMargin }
ColumnLayout {
width: parent.width
spacing: 2
RowLayout{
id: replyToLayout
Layout.alignment: isOutgoing ? Qt.AlignRight : Qt.AlignLeft
property var replyUserName: UtilsAdapter.getBestNameForUri(CurrentAccount.id, ReplyToAuthor)
Label {
id: replyTo
text: isOutgoing ? JamiStrings.inReplyTo : UtilsAdapter.getBestNameForUri(CurrentAccount.id, Author) + JamiStrings.repliedTo
color: JamiTheme.messageReplyColor
font.pointSize: JamiTheme.textFontSize
font.kerning: true
font.bold: true
}
Avatar {
id: avatarReply
visible: !replyItem.isSelf
Layout.preferredWidth: JamiTheme.avatarReadReceiptSize
Layout.preferredHeight: JamiTheme.avatarReadReceiptSize
showPresenceIndicator: false
imageId: {
if (replyItem.isSelf)
return CurrentAccount.id
return ReplyToAuthor
}
mode: replyItem.isSelf ? Avatar.Mode.Account : Avatar.Mode.Contact
}
Label {
id: replyToUserName
text: replyItem.isSelf ? JamiStrings.inReplyToMe : replyToLayout.replyUserName
color: JamiTheme.messageReplyColor
font.pointSize: JamiTheme.textFontSize
font.kerning: true
font.bold: true
}
}
Rectangle {
id: replyBubble
z: -2
color: replyItem.isSelf ? Qt.lighter(CurrentConversation.color, 1.15) : Qt.lighter(JamiTheme.messageInBgColor, 1.05)
radius: msgRadius
Layout.preferredWidth: replyToRow.width + 2*JamiTheme.preferredMarginSize
Layout.preferredHeight: replyToRow.height + 2*JamiTheme.preferredMarginSize
Layout.alignment: isOutgoing ? Qt.AlignRight : Qt.AlignLeft
// place actual content here
ReplyToRow {
id: replyToRow
anchors.centerIn: parent
}
MouseArea {
z: 2
anchors.fill: parent
onClicked: function(mouse) {
CurrentConversation.scrollToMsg(ReplyTo)
}
}
}
}
}
RowLayout {
id: msgRowlayout
Layout.preferredHeight: innerContent.height + root.extraHeight
Layout.topMargin: (seq === MsgSeq.first || seq === MsgSeq.single) ? 6 : 0
Layout.topMargin: ((seq === MsgSeq.first || seq === MsgSeq.single) && !root.isReply) ? 6 : 0
Item {
id: avatarBlock
@ -151,9 +249,6 @@ Control {
width: parent.width
visible: true
// place actual content here
ReplyToRow {}
}
Item {
@ -232,33 +327,37 @@ Control {
MessageBubble {
id: bubble
property bool isEdited: PreviousBodies.length !== 0
visible: !IsEmojiOnly
z:-1
out: isOutgoing
type: seq
isReply: root.isReply
function getBaseColor() {
var baseColor = isOutgoing ? JamiTheme.messageOutBgColor
: CurrentConversation.isCoreDialog ?
JamiTheme.messageInBgColor :
Qt.lighter(CurrentConversation.color, 1.5)
var baseColor = isOutgoing ? CurrentConversation.color : JamiTheme.messageInBgColor
if (Id === MessagesAdapter.replyToId || Id === MessagesAdapter.editId) {
// If we are replying to or editing the message
return Qt.darker(baseColor, 1.5)
}
return baseColor
}
color: getBaseColor()
radius: msgRadius
anchors.right: isOutgoing ? parent.right : undefined
anchors.top: parent.top
width: innerContent.childrenRect.width
width: Type === Interaction.Type.TEXT && !isEdited ? root.textContentWidth : innerContent.childrenRect.width
height: innerContent.childrenRect.height + (visible ? root.extraHeight : 0)
}
Rectangle {
id: bg
color: bubble.getBaseColor()
anchors.fill: parent
visible: false
}

View file

@ -45,6 +45,9 @@ SBSMessageBase {
formattedDay: MessagesAdapter.getFormattedDay(Timestamp)
extraHeight: extraContent.active && !isRemoteImage ? msgRadius : -isRemoteImage
textHovered: textHoverhandler.hovered
textContentWidth: textEditId.width
textContentHeight: textEditId.height
innerContent.children: [
TextEdit {
@ -67,12 +70,12 @@ SBSMessageBase {
width: {
if (extraContent.active)
Math.max(extraContent.width,
Math.min(implicitWidth - avatarBlockWidth,
Math.min((2/3)*root.maxMsgWidth,implicitWidth - avatarBlockWidth,
extraContent.minSize) - senderMargin )
else if (isEmojiOnly)
Math.min(implicitWidth, innerContent.width - senderMargin - (innerContent.width - senderMargin) % (JamiTheme.chatviewEmojiSize + 2))
Math.min((2/3)*root.maxMsgWidth,implicitWidth, innerContent.width - senderMargin - (innerContent.width - senderMargin) % (JamiTheme.chatviewEmojiSize + 2))
else
Math.min(implicitWidth, innerContent.width - senderMargin)
Math.min((2/3)*root.maxMsgWidth,implicitWidth, innerContent.width - senderMargin)
}
wrapMode: Label.WrapAtWordBoundaryOrAnywhere

View file

@ -361,7 +361,7 @@ Item {
property string search: qsTr("Search")
// Chatview footer
property string jumpToLatest: qsTr("Jump to latest")
property string scrollToEnd: qsTr("Scroll to end of conversation")
property string typeIndicatorSingle: qsTr("{} is typing…")
property string typeIndicatorPlural: qsTr("{} are typing…")
property string typeIndicatorMax: qsTr("Several people are typing…")
@ -787,6 +787,8 @@ Item {
property string remove: qsTr("Remove")
property string replyTo: qsTr("Reply to")
property string inReplyTo: qsTr("In reply to")
property string repliedTo: qsTr(" replied to")
property string inReplyToMe: qsTr("Me")
property string reply: qsTr("Reply")
property string writeTo: qsTr("Write to %1")
property string edit: qsTr("Edit")
@ -848,6 +850,7 @@ Item {
//message options
property string deleteMessage: qsTr("Delete message")
property string deleteReplyMessage: qsTr("*(Deleted Message)*")
property string editMessage: qsTr("Edit message")
}

View file

@ -201,8 +201,9 @@ Item {
property real chatviewFontSize: calcSize(15)
property real chatviewEmojiSize: calcSize(60)
property color timestampColor: darkTheme ? "#bbb" : "#777"
property color messageReplyColor: darkTheme ? "#bbb" : "#A7A7A7"
property color messageOutTxtColor: "#000000"
property color messageInBgColor: darkTheme ? "#28b1ed" : "#e5e5e5"
property color messageInBgColor: "#e5e5e5"
property color messageOutBgColor: darkTheme? "#616161" : "#005699"
property color messageInTxtColor: "#FFFFFF"
property color fileOutTimestampColor: darkTheme ? "#eee" : "#555"
@ -441,6 +442,12 @@ Item {
// SBSMessageBase
property int sbsMessageBasePreferredPadding: 12
property int sbsMessageBaseMaximumReplyWidth: baseZoom * 300
property int sbsMessageBaseMaximumReplyHeight: baseZoom * 40
property int sbsMessageBaseReplyBottomMargin: baseZoom * 10
property int sbsMessageBaseReplyMargin: 45
property int sbsMessageBaseReplyTopMargin: 6
// MessageBar
property int messageBarMarginSize: 10

View file

@ -31,6 +31,7 @@ import "../../commoncomponents"
JamiListView {
id: root
function getDistanceToBottom() {
const scrollDiff = ScrollBar.vertical.position -
(1.0 - ScrollBar.vertical.size)
@ -107,7 +108,7 @@ JamiListView {
function isFirst() {
if (!nItem) return true
else {
if (item.showTime) {
if (item.showTime || item.isReply ) {
return true
} else if (nItem.author !== item.author) {
return true
@ -119,7 +120,7 @@ JamiListView {
function isLast() {
if (!pItem) return true
else {
if (pItem.showTime) {
if (pItem.showTime || pItem.isReply) {
return true
} else if (pItem.author !== item.author) {
return true
@ -202,12 +203,10 @@ JamiListView {
model: MessagesAdapter.messageListModel
delegate: DelegateChooser {
id: delegateChooser
role: "Type"
DelegateChoice {
id: delegateChoice
roleValue: Interaction.Type.TEXT
TextMessageDelegate {
@ -257,8 +256,10 @@ JamiListView {
}
}
}
}
onAtYBeginningChanged: loadMoreMsgsIfNeeded()
Connections {
@ -271,7 +272,7 @@ JamiListView {
}
}
function onMoreMessagesLoaded() {
function onMoreMessagesLoaded(loadingRequestId) {
if (root.contentHeight < root.height || root.atYBeginning) {
root.loadMoreMsgsIfNeeded()
}

View file

@ -21,6 +21,8 @@ import QtQuick.Controls
import Qt5Compat.GraphicalEffects
import net.jami.Constants 1.1
import net.jami.Adapters 1.1
import "../../commoncomponents"
@ -28,12 +30,10 @@ Control {
id: root
property alias activeStateTrigger: activeState.when
signal clicked
height: jumpToLatestText.contentHeight + 15
width: jumpToLatestText.contentWidth + arrowDropDown.width + 50
opacity: 0
states: State {
@ -76,38 +76,41 @@ Control {
Text {
id: jumpToLatestText
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
font.weight: Font.DemiBold
font.weight: Font.Bold
font.pointSize: JamiTheme.textFontSize + 2
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text: JamiStrings.jumpToLatest
color: JamiTheme.whiteColor
text: JamiStrings.scrollToEnd
color: UtilsAdapter.luma(CurrentConversation.color) ? JamiTheme.chatviewTextColorLight : JamiTheme.chatviewTextColorDark
}
ResponsiveImage {
id: arrowDropDown
anchors.left: jumpToLatestText.right
anchors.leftMargin: 3
anchors.right: jumpToLatestText.left
anchors.rightMargin: 3
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: 2
anchors.verticalCenterOffset: 0
containerWidth: 12
containerHeight: 12
containerWidth: jumpToLatestText.contentHeight
containerHeight: jumpToLatestText.contentHeight
rotation: -90
color: JamiTheme.whiteColor
source: JamiResources.down_triangle_arrow_black_24dp_svg
color: UtilsAdapter.luma(CurrentConversation.color) ? JamiTheme.chatviewTextColorLight : JamiTheme.chatviewTextColorDark
source: JamiResources.back_24dp_svg
}
}
}
background: Rectangle {
radius: 20
color: JamiTheme.jamiDarkBlue
color: CurrentConversation.color
MouseArea {
anchors.fill: parent

View file

@ -106,7 +106,7 @@ MessagesAdapter::loadMoreMessages()
}
}
void
int
MessagesAdapter::loadConversationUntil(const QString& to)
{
try {
@ -118,13 +118,14 @@ MessagesAdapter::loadConversationUntil(const QString& to)
const auto& convInfo = lrcInstance_->getConversationFromConvUid(convId, accountId);
if (convInfo.isSwarm()) {
auto* convModel = lrcInstance_->getCurrentConversationModel();
convModel->loadConversationUntil(convId, to);
return convModel->loadConversationUntil(convId, to);
}
}
}
} catch (const std::exception& e) {
qWarning() << e.what();
}
return 0;
}
void
@ -550,11 +551,11 @@ MessagesAdapter::onPreviewInfoReady(QString messageId, QVariantMap info)
}
void
MessagesAdapter::onConversationMessagesLoaded(uint32_t, const QString& convId)
MessagesAdapter::onConversationMessagesLoaded(uint32_t loadingRequestId, const QString& convId)
{
if (convId != lrcInstance_->get_selectedConvUid())
return;
Q_EMIT moreMessagesLoaded();
Q_EMIT moreMessagesLoaded(loadingRequestId);
}
void

View file

@ -75,13 +75,13 @@ Q_SIGNALS:
void newFilePasted(QString filePath);
void newTextPasted();
void previewInformationToQML(QString messageId, QStringList previewInformation);
void moreMessagesLoaded();
void moreMessagesLoaded(qint32 loadingRequestId);
void timestampUpdated();
protected:
Q_INVOKABLE bool isDocument(const interaction::Type& type);
Q_INVOKABLE void loadMoreMessages();
Q_INVOKABLE void loadConversationUntil(const QString& to);
Q_INVOKABLE qint32 loadConversationUntil(const QString& to);
Q_INVOKABLE void connectConversationModel();
Q_INVOKABLE void sendConversationRequest();
Q_INVOKABLE void removeConversation(const QString& convUid);

View file

@ -485,8 +485,8 @@ MessageListModel::dataForItem(item_t item, int, int role) const
return QVariant("");
auto linkified = data(repliedMsg, Role::Linkified).toString();
if (!linkified.isEmpty())
return QVariant(linkified.replace("\n", " "));
return QVariant(data(repliedMsg, Role::Body).toString().replace("\n", " "));
return QVariant(linkified);
return QVariant(data(repliedMsg, Role::Body).toString());
}
case Role::TotalSize:
return QVariant(item.second.commit["totalSize"].toInt());