wip: chat request payment card

This commit is contained in:
Emil Sawicki 2024-11-08 16:50:35 +01:00
parent 300545a3b0
commit df348629c4
11 changed files with 308 additions and 3 deletions

View File

@ -17,6 +17,9 @@ SplitView {
QtObject { QtObject {
id: d id: d
readonly property var exampleAlbum: [ModelsData.banners.coinbase, ModelsData.icons.status]
readonly property var requestPaymentModel: RequestPaymentModel {}
readonly property var messagesModel: ListModel { readonly property var messagesModel: ListModel {
ListElement { ListElement {
timestamp: 1656937930123 timestamp: 1656937930123
@ -152,6 +155,28 @@ SplitView {
trustIndicator: StatusContactVerificationIcons.TrustedType.None trustIndicator: StatusContactVerificationIcons.TrustedType.None
outgoingStatus: StatusMessage.OutgoingStatus.Delivered outgoingStatus: StatusMessage.OutgoingStatus.Delivered
} }
ListElement {
timestamp: 1667937830123
senderId: "zq123456790"
senderDisplayName: "Alice"
contentType: StatusMessage.ContentType.Image
message: "This message contains images"
isContact: true
isAReply: false
trustIndicator: StatusContactVerificationIcons.TrustedType.None
outgoingStatus: StatusMessage.OutgoingStatus.Delivered
}
ListElement {
timestamp: 1667937830123
senderId: "zq123456790"
senderDisplayName: "Alice"
contentType: StatusMessage.ContentType.Attachment
message: "This message contains attachments"
isContact: true
isAReply: false
trustIndicator: StatusContactVerificationIcons.TrustedType.None
outgoingStatus: StatusMessage.OutgoingStatus.Delivered
}
} }
readonly property var colorHash: ListModel { readonly property var colorHash: ListModel {
ListElement { colorId: 13; segmentLength: 5 } ListElement { colorId: 13; segmentLength: 5 }
@ -202,6 +227,11 @@ SplitView {
colorId: index colorId: index
colorHash: d.colorHash colorHash: d.colorHash
} }
album: model.contentType === StatusMessage.ContentType.Image
|| model.contentType === StatusMessage.ContentType.Attachment ? d.exampleAlbum : []
albumCount: model.contentType === StatusMessage.ContentType.Image
|| model.contentType === StatusMessage.ContentType.Attachment ? d.exampleAlbum.length : 0
requestPaymentModel: model.contentType === StatusMessage.ContentType.Attachment ? d.requestPaymentModel : null
} }
replyDetails { replyDetails {
@ -222,6 +252,8 @@ SplitView {
onReplyMessageClicked: logs.logEvent("StatusMessage::replyMessageClicked") onReplyMessageClicked: logs.logEvent("StatusMessage::replyMessageClicked")
onResendClicked: logs.logEvent("StatusMessage::resendClicked") onResendClicked: logs.logEvent("StatusMessage::resendClicked")
onLinkActivated: logs.logEvent("StatusMessage::linkActivated" + link) onLinkActivated: logs.logEvent("StatusMessage::linkActivated" + link)
onRequestPaymentClicked: logs.logEvent("StatusMessage::requestPaymentActivated")
onImageClicked: logs.logEvent("StatusMessage::imageClicked")
} }
} }
} }

View File

@ -35,7 +35,7 @@ Item {
property StatusMessage controlUnderTest: null property StatusMessage controlUnderTest: null
TestCase { TestCase {
name: "TokenSelectorView" name: "StatusMessage"
when: windowShown when: windowShown
function init() { function init() {
@ -86,5 +86,70 @@ Item {
compare(actualLinkCount, data.validAddressEnsCount, "TextEdit should contain a link %1".arg(data.messageText)) compare(actualLinkCount, data.validAddressEnsCount, "TextEdit should contain a link %1".arg(data.messageText))
} }
function test_attachment_empty() {
verify(!!controlUnderTest)
controlUnderTest.messageDetails.contentType = StatusMessage.ContentType.Attachment
controlUnderTest.messageDetails.messageText = ""
waitForRendering(controlUnderTest)
const statusTextMessage = findChild(controlUnderTest, "StatusMessage_textMessage")
verify(!statusTextMessage)
const imageAlbum = findChild(controlUnderTest, "StatusMessage_imageAlbum")
verify(!!imageAlbum)
compare(imageAlbum.albumCount, 0)
const image = findChild(imageAlbum, "album_image_loader_0")
verify(!image)
const requestPaymentItem = findChild(controlUnderTest, "StatusMessage_requestPaymentDelegate_0")
verify(!requestPaymentItem)
}
function test_attachment_only_text() {
verify(!!controlUnderTest)
controlUnderTest.messageDetails.contentType = StatusMessage.ContentType.Attachment
controlUnderTest.messageDetails.messageText = "test message"
waitForRendering(controlUnderTest)
const statusTextMessage = findChild(controlUnderTest, "StatusMessage_textMessage")
verify(!!statusTextMessage)
verify(statusTextMessage.textField.text.indexOf("test message") > 0)
const imageAlbum = findChild(controlUnderTest, "StatusMessage_imageAlbum")
verify(!!imageAlbum)
compare(imageAlbum.albumCount, 0)
const image = findChild(imageAlbum, "album_image_loader_0")
verify(!image)
const requestPaymentItem = findChild(controlUnderTest, "StatusMessage_requestPaymentDelegate_0")
verify(!requestPaymentItem)
}
function test_attachment_multiple_attachments() {
verify(!!controlUnderTest)
controlUnderTest.messageDetails.contentType = StatusMessage.ContentType.Attachment
controlUnderTest.messageDetails.messageText = "test message with attachments"
controlUnderTest.messageDetails.album = [ "image0", "image1", "image2" ]
controlUnderTest.messageDetails.albumCount = 3
controlUnderTest.messageDetails.requestPaymentModel = [
{amount: "0.1", currency: "ETH", address: "0x1234567890abcdef1234567890abcdef12345678", chainId: 1},
{amount: "0.2", currency: "DAI", address: "0xAbCdEf1234567890abcdef1234567890AbCdEf12", chainId: 10},
]
waitForRendering(controlUnderTest)
const statusTextMessage = findChild(controlUnderTest, "StatusMessage_textMessage")
verify(!!statusTextMessage)
verify(statusTextMessage.textField.text.indexOf("test message with attachments") > 0)
const imageAlbum = findChild(controlUnderTest, "StatusMessage_imageAlbum")
verify(!!imageAlbum)
compare(imageAlbum.albumCount, 3)
for (let i = 0 ; i < 3 ; i++) {
const image = findChild(imageAlbum, "album_image_loader_"+i)
verify(!!image)
}
const requestPaymentItem0 = findChild(controlUnderTest, "StatusMessage_requestPaymentDelegate_0")
verify(!!requestPaymentItem0)
const requestPaymentItem1 = findChild(controlUnderTest, "StatusMessage_requestPaymentDelegate_1")
verify(!!requestPaymentItem1)
const requestPaymentItem2 = findChild(controlUnderTest, "StatusMessage_requestPaymentDelegate_2")
verify(!requestPaymentItem2)
}
} }
} }

View File

@ -7,6 +7,8 @@ import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1 import StatusQ.Core.Utils 0.1
import StatusQ.Controls 0.1 import StatusQ.Controls 0.1
import shared.controls.chat 1.0
import "./private/statusMessage" import "./private/statusMessage"
Control { Control {
@ -26,7 +28,8 @@ Control {
SystemMessageMutualEventSent = 15, SystemMessageMutualEventSent = 15,
SystemMessageMutualEventAccepted = 16, SystemMessageMutualEventAccepted = 16,
SystemMessageMutualEventRemoved = 17, SystemMessageMutualEventRemoved = 17,
BridgeMessage = 18 BridgeMessage = 18,
Attachment = 19
} }
enum OutgoingStatus { enum OutgoingStatus {
@ -89,6 +92,7 @@ Control {
signal addReactionClicked(var sender, var mouse) signal addReactionClicked(var sender, var mouse)
signal toggleReactionClicked(int emojiId) signal toggleReactionClicked(int emojiId)
signal imageClicked(var image, var mouse, var imageSource) signal imageClicked(var image, var mouse, var imageSource)
signal requestPaymentClicked(var symbol, var amount, var address, var chainId)
signal stickerClicked() signal stickerClicked()
signal resendClicked() signal resendClicked()
@ -299,7 +303,7 @@ Control {
} }
} }
Loader { Loader {
active: root.messageDetails.contentType === StatusMessage.ContentType.Image && !editMode active: root.messageDetails.contentType === StatusMessage.ContentType.Image && !editMode
visible: active visible: active
Layout.fillWidth: true Layout.fillWidth: true
@ -312,6 +316,7 @@ Control {
anchors.right: parent.right anchors.right: parent.right
visible: active visible: active
sourceComponent: StatusTextMessage { sourceComponent: StatusTextMessage {
objectName: "StatusMessage_textMessage"
messageDetails: root.messageDetails messageDetails: root.messageDetails
isEdited: root.isEdited isEdited: root.isEdited
allowShowMore: !root.isInPinnedPopup allowShowMore: !root.isInPinnedPopup
@ -326,6 +331,7 @@ Control {
Loader { Loader {
active: true active: true
sourceComponent: StatusMessageImageAlbum { sourceComponent: StatusMessageImageAlbum {
objectName: "StatusMessage_imageAlbum"
width: messageLayout.width width: messageLayout.width
album: root.messageDetails.albumCount > 0 ? root.messageDetails.album : [root.messageDetails.messageContent] album: root.messageDetails.albumCount > 0 ? root.messageDetails.album : [root.messageDetails.messageContent]
albumCount: root.messageDetails.albumCount > 0 ? root.messageDetails.albumCount : 1 albumCount: root.messageDetails.albumCount > 0 ? root.messageDetails.albumCount : 1
@ -337,6 +343,68 @@ Control {
} }
} }
Loader {
active: root.messageDetails.contentType === StatusMessage.ContentType.Attachment && !editMode
visible: active
Layout.fillWidth: true
sourceComponent: Column {
id: attachmentsColumn
spacing: 8
Loader {
active: root.messageDetails.messageText !== ""
anchors.left: parent.left
anchors.right: parent.right
visible: active
sourceComponent: StatusTextMessage {
objectName: "StatusMessage_textMessage"
messageDetails: root.messageDetails
isEdited: root.isEdited
allowShowMore: !root.isInPinnedPopup
textField.anchors.rightMargin: root.isInPinnedPopup ? Theme.xlPadding : 0 // margin for the "Unpin" floating button
highlightedLink: root.highlightedLink
onLinkActivated: {
root.linkActivated(link);
}
}
}
Flow {
width: messageLayout.width
height: childrenRect.height + Theme.smallPadding
Loader {
active: true
sourceComponent: StatusMessageImageAlbum {
objectName: "StatusMessage_imageAlbum"
album: root.messageDetails.albumCount > 0 ? root.messageDetails.album : [root.messageDetails.messageContent]
albumCount: root.messageDetails.albumCount > 0 ? root.messageDetails.albumCount : 0
imageWidth: Math.min(messageLayout.width / root.messageDetails.albumCount - 9 * (root.messageDetails.albumCount - 1), 144)
shapeType: StatusImageMessage.ShapeType.LEFT_ROUNDED
onImageClicked: root.imageClicked(image, mouse, imageSource)
}
}
Loader {
active: true
sourceComponent: RowLayout {
Repeater {
model: root.messageDetails.requestPaymentModel
delegate: RequestPaymentCardDelegate {
objectName: "StatusMessage_requestPaymentDelegate_" + model.index
required property var model
amount: model.amount
symbol: model.symbol
address: model.address
senderName: root.messageDetails.sender.displayName
senderImageAssetSettings: root.messageDetails.sender.profileImage.assetSettings
onClicked: root.requestPaymentClicked(model.symbol, model.amount, model.address, model.chainId)
}
}
}
}
}
}
}
Loader { Loader {
active: root.messageAttachments && !editMode active: root.messageAttachments && !editMode
visible: active visible: active

View File

@ -15,4 +15,5 @@ QtObject {
property bool messageDeleted: false property bool messageDeleted: false
property var album: [] property var album: []
property int albumCount: 0 property int albumCount: 0
property var requestPaymentModel: null
} }

View File

@ -24,6 +24,7 @@ RowLayout {
delegate: Loader { delegate: Loader {
active: true active: true
objectName: "album_image_loader_" + index
readonly property bool imageLoaded: index < root.album.length readonly property bool imageLoaded: index < root.album.length
readonly property string imagePath: imageLoaded ? root.album[index] : "" readonly property string imagePath: imageLoaded ? root.album[index] : ""
sourceComponent: imageLoaded ? imageComponent : imagePlaceholderComponent sourceComponent: imageLoaded ? imageComponent : imagePlaceholderComponent

View File

@ -8043,6 +8043,7 @@
<file>assets/png/chat/chat@2x.png</file> <file>assets/png/chat/chat@2x.png</file>
<file>assets/png/chat/chat@3x.png</file> <file>assets/png/chat/chat@3x.png</file>
<file>assets/png/chat/wave.png</file> <file>assets/png/chat/wave.png</file>
<file>assets/png/chat/request_payment_banner.png</file>
<file>assets/png/keycard/authenticate.png</file> <file>assets/png/keycard/authenticate.png</file>
<file>assets/png/keycard/biometrics-fail.png</file> <file>assets/png/keycard/biometrics-fail.png</file>
<file>assets/png/keycard/biometrics-success.png</file> <file>assets/png/keycard/biometrics-success.png</file>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -0,0 +1,133 @@
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Window 2.15
import StatusQ.Core 0.1
import QtGraphicalEffects 1.15
import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1
import utils 1.0
CalloutCard {
id: root
required property string amount
required property string symbol
required property string address
property string senderName
property var senderImageAssetSettings
property bool highlight: false
signal clicked(var mouse)
implicitHeight: 187
implicitWidth: 305 + 2 * borderWidth
borderWidth: 2
hoverEnabled: true
dropShadow: d.highlight
borderColor: d.highlight ? Theme.palette.background : Theme.palette.border
padding: 12
Behavior on borderColor {
ColorAnimation { duration: 200 }
}
QtObject {
id: d
property real bannerImageMargins: 1 / Screen.devicePixelRatio // image size isn't pixel perfect..
property bool highlight: root.highlight || root.hovered
property string bannerImageSource: ""
}
contentItem: ColumnLayout {
spacing: 4
Rectangle {
Layout.fillHeight: true
Layout.fillWidth: true
radius: 8
color: Theme.palette.primaryColor3
clip: true
border.width: 1
border.color: Theme.palette.primaryColor2
StatusImage {
anchors.fill: parent
asynchronous: true
source: Theme.png("chat/request_payment_banner")
}
Row {
id: iconRow
spacing: -8
anchors.centerIn: parent
StatusRoundedImage {
id: symbolImage
anchors.verticalCenter: parent.verticalCenter
image.source: Constants.tokenIcon(root.symbol)
width: 44
height: width
image.layer.enabled: true
image.layer.effect: OpacityMask {
id: mask
invert: true
maskSource: Item {
width: mask.width + 2
height: mask.height + 2
Rectangle {
anchors.centerIn: parent
anchors.horizontalCenterOffset: symbolImage.width + iconRow.spacing - 2
width: parent.width
height: width
radius: width / 2
}
}
}
}
StatusSmartIdenticon {
width: symbolImage.width
height: symbolImage.height
asset: root.senderImageAssetSettings
name: root.senderName
}
}
}
Item {
Layout.fillWidth: true
Layout.preferredHeight: 4
}
StatusBaseText {
Layout.fillWidth: true
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
text: qsTr("Send %1 %2 to %3").arg(root.amount).arg(root.symbol).arg(Utils.compactAddress(root.address.toLowerCase(), 4))
font.pixelSize: Theme.additionalTextSize
font.weight: Font.Medium
}
StatusBaseText {
Layout.fillWidth: true
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
font.pixelSize: Theme.tertiaryTextFontSize
color: Theme.palette.baseColor1
verticalAlignment: Text.AlignVCenter
text: qsTr("Requested by %1").arg(root.senderName)
}
}
MouseArea {
anchors.fill: root
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: root.clicked(mouse)
}
}

View File

@ -10,6 +10,7 @@ LinkPreviewCard 1.0 LinkPreviewCard.qml
LinkPreviewMiniCard 1.0 LinkPreviewMiniCard.qml LinkPreviewMiniCard 1.0 LinkPreviewMiniCard.qml
LinkPreviewSettingsCard 1.0 LinkPreviewSettingsCard.qml LinkPreviewSettingsCard 1.0 LinkPreviewSettingsCard.qml
LinkPreviewSettingsCardMenu 1.0 LinkPreviewSettingsCardMenu.qml LinkPreviewSettingsCardMenu 1.0 LinkPreviewSettingsCardMenu.qml
RequestPaymentCardDelegate 1.0 RequestPaymentCardDelegate.qml
RequestPaymentMiniCardDelegate 1.0 RequestPaymentMiniCardDelegate.qml RequestPaymentMiniCardDelegate 1.0 RequestPaymentMiniCardDelegate.qml
MessageMouseArea 1.0 MessageMouseArea.qml MessageMouseArea 1.0 MessageMouseArea.qml
MessageReactionsRow 1.0 MessageReactionsRow.qml MessageReactionsRow 1.0 MessageReactionsRow.qml

View File

@ -350,6 +350,8 @@ Loader {
return StatusMessage.ContentType.SystemMessageMutualEventAccepted; return StatusMessage.ContentType.SystemMessageMutualEventAccepted;
case Constants.messageContentType.systemMessageMutualEventRemoved: case Constants.messageContentType.systemMessageMutualEventRemoved:
return StatusMessage.ContentType.SystemMessageMutualEventRemoved; return StatusMessage.ContentType.SystemMessageMutualEventRemoved;
case Constants.messageContentType.attachmentType:
return StatusMessage.ContentType.Attachment;
case Constants.messageContentType.fetchMoreMessagesButton: case Constants.messageContentType.fetchMoreMessagesButton:
case Constants.messageContentType.chatIdentifier: case Constants.messageContentType.chatIdentifier:
case Constants.messageContentType.unknownContentType: case Constants.messageContentType.unknownContentType:

View File

@ -476,6 +476,7 @@ QtObject {
readonly property int systemMessageMutualEventAccepted: 16 readonly property int systemMessageMutualEventAccepted: 16
readonly property int systemMessageMutualEventRemoved: 17 readonly property int systemMessageMutualEventRemoved: 17
readonly property int bridgeMessageType: 18 readonly property int bridgeMessageType: 18
readonly property int attachmentType: 19
} }
readonly property QtObject messageModelRoles: QtObject { readonly property QtObject messageModelRoles: QtObject {