feat: display message outgoing state (#15450)
* chore: storybook page * feat: propagate outgoing status to StatusMessageHeader * feat: improve message outgoing status UI * fix: lock message `delivered` state
This commit is contained in:
parent
2f1240602f
commit
8b80cfb9e3
|
@ -546,6 +546,8 @@ QtObject:
|
|||
let ind = self.findIndexForMessageId(messageId)
|
||||
if(ind == -1):
|
||||
return
|
||||
if self.items[ind].outgoingStatus == PARSED_TEXT_OUTGOING_STATUS_DELIVERED:
|
||||
return
|
||||
self.items[ind].outgoingStatus = status
|
||||
let index = self.createIndex(ind, 0, nil)
|
||||
defer: index.delete
|
||||
|
|
|
@ -29,6 +29,7 @@ SplitView {
|
|||
isContact: true
|
||||
isAReply: false
|
||||
trustIndicator: StatusContactVerificationIcons.TrustedType.Verified
|
||||
outgoingStatus: StatusMessage.OutgoingStatus.Delivered
|
||||
}
|
||||
ListElement {
|
||||
timestamp: 1657937930135
|
||||
|
@ -39,6 +40,7 @@ SplitView {
|
|||
isContact: false
|
||||
isAReply: false
|
||||
trustIndicator: StatusContactVerificationIcons.TrustedType.Untrustworthy
|
||||
outgoingStatus: StatusMessage.OutgoingStatus.Delivered
|
||||
}
|
||||
ListElement {
|
||||
timestamp: 1667937930159
|
||||
|
@ -49,6 +51,7 @@ SplitView {
|
|||
isContact: true
|
||||
isAReply: true
|
||||
trustIndicator: StatusContactVerificationIcons.TrustedType.None
|
||||
outgoingStatus: StatusMessage.OutgoingStatus.Delivered
|
||||
}
|
||||
ListElement {
|
||||
timestamp: 1667937930489
|
||||
|
@ -59,6 +62,71 @@ SplitView {
|
|||
isContact: true
|
||||
isAReply: true
|
||||
trustIndicator: StatusContactVerificationIcons.TrustedType.None
|
||||
outgoingStatus: StatusMessage.OutgoingStatus.Delivered
|
||||
}
|
||||
ListElement {
|
||||
timestamp: 1719769718000
|
||||
senderId: "zq123456790"
|
||||
senderDisplayName: "Alice"
|
||||
contentType: StatusMessage.ContentType.Text
|
||||
message: "Sending message"
|
||||
isAReply: false
|
||||
isContact: true
|
||||
amISender: true
|
||||
trustIndicator: StatusContactVerificationIcons.TrustedType.None
|
||||
outgoingStatus: StatusMessage.OutgoingStatus.Sending
|
||||
}
|
||||
ListElement {
|
||||
timestamp: 1719769718000
|
||||
senderId: "zq123456790"
|
||||
senderDisplayName: "Alice"
|
||||
contentType: StatusMessage.ContentType.Text
|
||||
message: "Sent message"
|
||||
isAReply: false
|
||||
isContact: true
|
||||
amISender: true
|
||||
trustIndicator: StatusContactVerificationIcons.TrustedType.None
|
||||
outgoingStatus: StatusMessage.OutgoingStatus.Sent
|
||||
resendError: ""
|
||||
}
|
||||
ListElement {
|
||||
timestamp: 1719769718000
|
||||
senderId: "zq123456790"
|
||||
senderDisplayName: "Alice"
|
||||
contentType: StatusMessage.ContentType.Text
|
||||
message: "Delivered message"
|
||||
isAReply: false
|
||||
isContact: true
|
||||
amISender: true
|
||||
trustIndicator: StatusContactVerificationIcons.TrustedType.None
|
||||
outgoingStatus: StatusMessage.OutgoingStatus.Delivered
|
||||
resendError: ""
|
||||
}
|
||||
ListElement {
|
||||
timestamp: 1719769718000
|
||||
senderId: "zq123456790"
|
||||
senderDisplayName: "Alice"
|
||||
contentType: StatusMessage.ContentType.Text
|
||||
message: "Expired message"
|
||||
isAReply: false
|
||||
isContact: true
|
||||
amISender: true
|
||||
trustIndicator: StatusContactVerificationIcons.TrustedType.None
|
||||
outgoingStatus: StatusMessage.OutgoingStatus.Expired
|
||||
resendError: ""
|
||||
}
|
||||
ListElement {
|
||||
timestamp: 1719769718000
|
||||
senderId: "zq123456790"
|
||||
senderDisplayName: "Alice"
|
||||
contentType: StatusMessage.ContentType.Text
|
||||
message: "Message with resend error"
|
||||
isAReply: false
|
||||
isContact: true
|
||||
amISender: true
|
||||
trustIndicator: StatusContactVerificationIcons.TrustedType.None
|
||||
outgoingStatus: StatusMessage.OutgoingStatus.Expired
|
||||
resendError: "can't send message on Tuesday"
|
||||
}
|
||||
}
|
||||
readonly property var colorHash: ListModel {
|
||||
|
@ -90,10 +158,15 @@ SplitView {
|
|||
delegate: StatusMessage {
|
||||
width: ListView.view.width
|
||||
timestamp: model.timestamp
|
||||
isAReply: model.isAReply
|
||||
outgoingStatus: model.outgoingStatus
|
||||
resendError: model.outgoingStatus === StatusMessage.OutgoingStatus.Expired ? model.resendError : ""
|
||||
|
||||
messageDetails {
|
||||
readonly property bool isEnsVerified: model.senderDisplayName.endsWith(".eth")
|
||||
messageText: model.message
|
||||
contentType: model.contentType
|
||||
amISender: model.amISender
|
||||
sender.id: isEnsVerified ? "" : model.senderId
|
||||
sender.displayName: model.senderDisplayName
|
||||
sender.isContact: model.isContact
|
||||
|
@ -106,7 +179,6 @@ SplitView {
|
|||
}
|
||||
}
|
||||
|
||||
isAReply: model.isAReply
|
||||
replyDetails {
|
||||
amISender: true
|
||||
sender.id: "0xdeadbeef"
|
||||
|
@ -123,6 +195,7 @@ SplitView {
|
|||
onProfilePictureClicked: logs.logEvent("StatusMessage::profilePictureClicked")
|
||||
onReplyProfileClicked: logs.logEvent("StatusMessage::replyProfileClicked")
|
||||
onReplyMessageClicked: logs.logEvent("StatusMessage::replyMessageClicked")
|
||||
onResendClicked: logs.logEvent("StatusMessage::resendClicked")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,14 @@ Control {
|
|||
BridgeMessage = 18
|
||||
}
|
||||
|
||||
enum OutgoingStatus {
|
||||
Unknown = 0,
|
||||
Sending,
|
||||
Sent,
|
||||
Delivered,
|
||||
Expired
|
||||
}
|
||||
|
||||
property list<Item> quickActions
|
||||
property var statusChatInput
|
||||
property alias linksComponent: linksLoader.sourceComponent
|
||||
|
@ -51,9 +59,8 @@ Control {
|
|||
property bool hasMention: false
|
||||
property bool isPinned: false
|
||||
property string pinnedBy: ""
|
||||
property bool hasExpired: false
|
||||
property bool isSending: false
|
||||
property string resendError: ""
|
||||
property int outgoingStatus: StatusMessage.OutgointStatus.Unknown
|
||||
property double timestamp: 0
|
||||
property var reactionsModel: []
|
||||
|
||||
|
@ -111,6 +118,7 @@ Control {
|
|||
}
|
||||
|
||||
hoverEnabled: (!root.isActiveMessage && !root.disableHover)
|
||||
opacity: outgoingStatus === StatusMessage.OutgoingStatus.Sending ? 0.5 : 1.0
|
||||
background: Rectangle {
|
||||
color: {
|
||||
if (root.overrideBackground)
|
||||
|
@ -254,14 +262,14 @@ Control {
|
|||
sender: root.messageDetails.sender
|
||||
amISender: root.messageDetails.amISender
|
||||
messageOriginInfo: root.messageDetails.messageOriginInfo
|
||||
showResendButton: root.hasExpired && root.messageDetails.amISender && !editMode && !root.isInPinnedPopup
|
||||
showSendingLoader: root.isSending && root.messageDetails.amISender && !editMode
|
||||
resendError: root.messageDetails.amISender && !editMode ? root.resendError : ""
|
||||
resendError: root.messageDetails.amISender ? root.resendError : ""
|
||||
onClicked: root.senderNameClicked(sender, mouse)
|
||||
onResendClicked: root.resendClicked()
|
||||
timestamp: root.timestamp
|
||||
showFullTimestamp: root.isInPinnedPopup
|
||||
displayNameClickable: root.profileClickable
|
||||
outgoingStatus: root.outgoingStatus
|
||||
showOutgointStatusLabel: root.hovered && !root.isInPinnedPopup
|
||||
}
|
||||
}
|
||||
Loader {
|
||||
|
|
|
@ -18,9 +18,6 @@ Item {
|
|||
property double timestamp: 0
|
||||
|
||||
property string tertiaryDetail: sender.id
|
||||
property string resendText: qsTr("Resend")
|
||||
property bool showResendButton: false
|
||||
property bool showSendingLoader: false
|
||||
property string resendError: ""
|
||||
property bool isContact: sender.isContact
|
||||
property int trustIndicator: sender.trustIndicator
|
||||
|
@ -28,6 +25,8 @@ Item {
|
|||
property bool displayNameClickable: true
|
||||
property string messageOriginInfo: ""
|
||||
property bool showFullTimestamp
|
||||
property int outgoingStatus: StatusMessage.OutgoingStatus.Unknown
|
||||
property bool showOutgointStatusLabel: false
|
||||
|
||||
signal clicked(var sender, var mouse)
|
||||
signal resendClicked()
|
||||
|
@ -35,6 +34,13 @@ Item {
|
|||
implicitHeight: layout.implicitHeight
|
||||
implicitWidth: layout.implicitWidth
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
readonly property bool expired: root.outgoingStatus === StatusMessage.OutgoingStatus.Expired
|
||||
readonly property color outgoingStatusColor: expired ? Theme.palette.warningColor1 : Theme.palette.baseColor1
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: layout
|
||||
spacing: 4
|
||||
|
@ -133,56 +139,12 @@ Item {
|
|||
}
|
||||
|
||||
StatusTimeStampLabel {
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
id: timestampText
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
timestamp: root.timestamp
|
||||
showFullTimestamp: root.showFullTimestamp
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: resendButtonLoader
|
||||
active: showResendButton && !!timestampText.text
|
||||
asynchronous: true
|
||||
sourceComponent: StatusBaseText {
|
||||
id: resendButton
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: Theme.palette.dangerColor1
|
||||
font.pixelSize: Theme.tertiaryTextFontSize
|
||||
text: root.resendText
|
||||
MouseArea {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
anchors.fill: parent
|
||||
onClicked: root.resendClicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: resendErrorTextLoader
|
||||
active: resendError && !!timestampText.text
|
||||
asynchronous: true
|
||||
sourceComponent: StatusBaseText {
|
||||
id: resendErrorText
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: Theme.palette.baseColor1
|
||||
font.pixelSize: Theme.tertiaryTextFontSize
|
||||
text: qsTr("Failed to resend: %1").arg(resendError) // TODO replace this with the required design
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: sendingInProgressLoader
|
||||
active: showSendingLoader && !!timestampText.text
|
||||
asynchronous: true
|
||||
sourceComponent: StatusBaseText {
|
||||
id: sendingInProgress
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: Theme.palette.baseColor1
|
||||
font.pixelSize: Theme.tertiaryTextFontSize
|
||||
text: qsTr("Sending...") // TODO replace this with the required design
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: dotComponent
|
||||
StatusBaseText {
|
||||
|
@ -194,6 +156,79 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: deliveryStatusLoader
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
active: root.outgoingStatus !== StatusMessage.OutgoingStatus.Unknown
|
||||
asynchronous: true
|
||||
sourceComponent: RowLayout {
|
||||
spacing: 0
|
||||
StatusIcon {
|
||||
Layout.preferredHeight: 15
|
||||
Layout.preferredWidth: 15
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
color: d.outgoingStatusColor
|
||||
icon: {
|
||||
if (root.resendError != "")
|
||||
return "tiny/tiny-exclamation"
|
||||
switch (root.outgoingStatus) {
|
||||
case StatusMessage.OutgoingStatus.Delivered:
|
||||
return "tiny/message/delivered"
|
||||
case StatusMessage.OutgoingStatus.Sent:
|
||||
return "tiny/message/sent"
|
||||
case StatusMessage.OutgoingStatus.Sending:
|
||||
return "tiny/pending"
|
||||
case StatusMessage.OutgoingStatus.Expired:
|
||||
return "tiny/tiny-exclamation"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
Loader {
|
||||
active: root.showOutgointStatusLabel
|
||||
asynchronous: true
|
||||
sourceComponent: StatusBaseText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
color: d.outgoingStatusColor
|
||||
font.pixelSize: Theme.asideTextFontSize
|
||||
text: {
|
||||
if (root.resendError != "")
|
||||
return qsTr("Failed to resend: %1").arg(root.resendError)
|
||||
switch (root.outgoingStatus) {
|
||||
case StatusMessage.OutgoingStatus.Delivered:
|
||||
return qsTr("Delivered")
|
||||
case StatusMessage.OutgoingStatus.Sent:
|
||||
return qsTr("Sent")
|
||||
case StatusMessage.OutgoingStatus.Sending:
|
||||
return qsTr("Sending")
|
||||
case StatusMessage.OutgoingStatus.Expired:
|
||||
return qsTr("Sending failed")
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: resendButtonLoader
|
||||
active: root.showOutgointStatusLabel && d.expired
|
||||
asynchronous: true
|
||||
sourceComponent: StatusButton {
|
||||
Layout.fillHeight: true
|
||||
verticalPadding: 1
|
||||
horizontalPadding: 5
|
||||
size: StatusBaseButton.Tiny
|
||||
type: StatusBaseButton.Warning
|
||||
font.pixelSize: 9
|
||||
text: qsTr("Resend")
|
||||
onClicked: root.resendClicked()
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
|
|
@ -124,7 +124,7 @@ Loader {
|
|||
property string deletedByContactIcon: ""
|
||||
property string deletedByContactColorHash: ""
|
||||
|
||||
property bool shouldRepeatHeader: d.getShouldRepeatHeader(messageTimestamp, prevMessageTimestamp, messageOutgoingStatus)
|
||||
property bool shouldRepeatHeader: d.shouldRepeatHeader
|
||||
|
||||
property bool hasMention: false
|
||||
|
||||
|
@ -143,9 +143,6 @@ Loader {
|
|||
property bool isMessage: isEmoji || isImage || isSticker || isText || isAudio
|
||||
|| messageContentType === Constants.messageContentType.communityInviteType || messageContentType === Constants.messageContentType.transactionType
|
||||
|
||||
readonly property bool isExpired: d.getIsExpired(messageTimestamp, messageOutgoingStatus)
|
||||
readonly property bool isSending: messageOutgoingStatus === Constants.sending && !isExpired
|
||||
|
||||
function openProfileContextMenu(sender, mouse, isReply = false) {
|
||||
if (isReply && !quotedMessageFrom) {
|
||||
// The responseTo message was deleted
|
||||
|
@ -274,8 +271,8 @@ Loader {
|
|||
readonly property bool canPost: root.chatContentModule.chatDetails.canPost
|
||||
readonly property bool canView: canPost || root.chatContentModule.chatDetails.canView
|
||||
|
||||
function nextMessageHasHeader() {
|
||||
if(!root.nextMessageAsJsonObj) {
|
||||
function getNextMessageHasHeader() {
|
||||
if (!root.nextMessageAsJsonObj) {
|
||||
return false
|
||||
}
|
||||
return root.senderId !== root.nextMessageAsJsonObj.senderId ||
|
||||
|
@ -289,7 +286,22 @@ Loader {
|
|||
}
|
||||
|
||||
function getIsExpired(messageTimeStamp, messageOutgoingStatus) {
|
||||
return (messageOutgoingStatus === Constants.sending && (Math.floor(messageTimeStamp) + 180000) < Date.now()) || messageOutgoingStatus === Constants.expired
|
||||
return (messageOutgoingStatus === Constants.messageOutgoingStatus.sending && (Math.floor(messageTimeStamp) + 180000) < Date.now())
|
||||
|| messageOutgoingStatus === Constants.expired
|
||||
}
|
||||
|
||||
property bool isExpired: false
|
||||
property bool shouldRepeatHeader: false
|
||||
property bool nextMessageHasHeader: false
|
||||
|
||||
Component.onCompleted: {
|
||||
onTimeChanged()
|
||||
}
|
||||
|
||||
function onTimeChanged() {
|
||||
isExpired = getIsExpired(root.messageTimestamp, root.messageOutgoingStatus)
|
||||
shouldRepeatHeader = getShouldRepeatHeader(root.messageTimestamp, root.prevMessageTimestamp, root.messageOutgoingStatus)
|
||||
nextMessageHasHeader = getNextMessageHasHeader()
|
||||
}
|
||||
|
||||
function convertContentType(value) {
|
||||
|
@ -332,6 +344,17 @@ Loader {
|
|||
}
|
||||
}
|
||||
|
||||
function convertOutgoingStatus(value) {
|
||||
switch (value) {
|
||||
case Constants.messageOutgoingStatus.sending:
|
||||
return StatusMessage.OutgoingStatus.Sending
|
||||
case Constants.messageOutgoingStatus.sent:
|
||||
return StatusMessage.OutgoingStatus.Sent
|
||||
case Constants.messageOutgoingStatus.delivered:
|
||||
return StatusMessage.OutgoingStatus.Delivered
|
||||
}
|
||||
}
|
||||
|
||||
function addReactionClicked(mouseArea, mouse) {
|
||||
if (!d.addReactionAllowed)
|
||||
return
|
||||
|
@ -357,6 +380,13 @@ Loader {
|
|||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: StatusSharedUpdateTimer
|
||||
onTriggered: {
|
||||
d.onTimeChanged()
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: gapComponent
|
||||
GapComponent {
|
||||
|
@ -646,8 +676,9 @@ Loader {
|
|||
return ProfileUtils.displayName(contact.localNickname, contact.name, contact.displayName, contact.alias)
|
||||
}
|
||||
isInPinnedPopup: root.isInPinnedPopup
|
||||
hasExpired: root.isExpired
|
||||
isSending: root.isSending
|
||||
outgoingStatus: d.isExpired ? StatusMessage.OutgoingStatus.Expired
|
||||
: d.convertOutgoingStatus(messageOutgoingStatus)
|
||||
|
||||
resendError: root.resendError
|
||||
reactionsModel: root.reactionsModel
|
||||
linkPreviewModel: root.linkPreviewModel
|
||||
|
@ -663,7 +694,7 @@ Loader {
|
|||
root.senderId !== root.prevMessageSenderId || root.prevMessageDeleted
|
||||
isActiveMessage: d.isMessageActive
|
||||
topPadding: showHeader ? Style.current.halfPadding : 0
|
||||
bottomPadding: showHeader && d.nextMessageHasHeader() ? Style.current.halfPadding : 2
|
||||
bottomPadding: showHeader && d.nextMessageHasHeader ? Style.current.halfPadding : 2
|
||||
disableHover: root.disableHover ||
|
||||
(delegate.hideQuickActions && !d.addReactionAllowed) ||
|
||||
(root.chatLogView && root.chatLogView.moving) ||
|
||||
|
|
|
@ -2,6 +2,7 @@ pragma Singleton
|
|||
|
||||
import QtQuick 2.13
|
||||
|
||||
import StatusQ.Components 0.1
|
||||
import StatusQ.Controls.Validators 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
|
||||
|
@ -1202,11 +1203,13 @@ QtObject {
|
|||
}
|
||||
|
||||
// Message outgoing status
|
||||
readonly property QtObject messageOutgoingStatus: QtObject {
|
||||
readonly property string sending: "sending"
|
||||
readonly property string sent: "sent"
|
||||
readonly property string delivered: "delivered"
|
||||
readonly property string expired: "expired"
|
||||
readonly property string failedResending: "failedResending"
|
||||
}
|
||||
|
||||
readonly property QtObject appTranslatableConstants: QtObject {
|
||||
readonly property string loginAccountsListAddNewUser: "LOGIN-ACCOUNTS-LIST-ADD-NEW-USER"
|
||||
|
|
Loading…
Reference in New Issue