diff --git a/storybook/pages/ChatInputLinksPreviewAreaPage.qml b/storybook/pages/ChatInputLinksPreviewAreaPage.qml
index adde059d23..7c9adbba35 100644
--- a/storybook/pages/ChatInputLinksPreviewAreaPage.qml
+++ b/storybook/pages/ChatInputLinksPreviewAreaPage.qml
@@ -32,6 +32,7 @@ SplitView {
width: parent.width
imagePreviewArray: ["https://picsum.photos/200/300?random=1", "https://picsum.photos/200/300?random=1"]
linkPreviewModel: showLinkPreviewSettings ? emptyModel : mockedLinkPreviewModel
+ paymentRequestModel: mockedPaymentRequestModel
showLinkPreviewSettings: !linkPreviewEnabledSwitch.checked
visible: hasContent
@@ -82,6 +83,10 @@ SplitView {
LinkPreviewModel {
id: mockedLinkPreviewModel
}
+
+ PaymentRequestModel {
+ id: mockedPaymentRequestModel
+ }
}
// category: Panels
diff --git a/storybook/pages/LinksMessageViewPage.qml b/storybook/pages/LinksMessageViewPage.qml
index cf5c540548..d2585a1706 100644
--- a/storybook/pages/LinksMessageViewPage.qml
+++ b/storybook/pages/LinksMessageViewPage.qml
@@ -13,6 +13,10 @@ SplitView {
id: mockedLinkPreviewModel
}
+ PaymentRequestModel {
+ id: mockedPaymentRequestModel
+ }
+
Pane {
id: messageViewWrapper
SplitView.fillWidth: true
@@ -27,6 +31,10 @@ SplitView {
playAnimations: true
linkPreviewModel: mockedLinkPreviewModel
gifLinks: [ "https://media.tenor.com/qN_ytiwLh24AAAAC/cold.gif" ]
+ paymentRequestModel: mockedPaymentRequestModel
+ areTestNetworksEnabled: false
+
+ senderName: "Alice"
gifUnfurlingEnabled: false
canAskToUnfurlGifs: true
@@ -70,6 +78,11 @@ SplitView {
checked: linksMessageView.isOnline
onToggled: linksMessageView.isOnline = !linksMessageView.isOnline
}
+ CheckBox {
+ text: qsTr("Testnet enabled")
+ checked: linksMessageView.areTestNetworksEnabled
+ onToggled: linksMessageView.areTestNetworksEnabled = !linksMessageView.areTestNetworksEnabled
+ }
}
}
}
diff --git a/storybook/pages/StatusMessagePage.qml b/storybook/pages/StatusMessagePage.qml
index 8481b6cd65..ddf8e45234 100644
--- a/storybook/pages/StatusMessagePage.qml
+++ b/storybook/pages/StatusMessagePage.qml
@@ -17,6 +17,8 @@ SplitView {
QtObject {
id: d
+ readonly property var exampleAlbum: [ModelsData.banners.coinbase, ModelsData.icons.status]
+
readonly property var messagesModel: ListModel {
ListElement {
timestamp: 1656937930123
@@ -152,6 +154,17 @@ SplitView {
trustIndicator: StatusContactVerificationIcons.TrustedType.None
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
+ }
}
readonly property var colorHash: ListModel {
ListElement { colorId: 13; segmentLength: 5 }
@@ -202,6 +215,8 @@ SplitView {
colorId: index
colorHash: d.colorHash
}
+ album: model.contentType === StatusMessage.ContentType.Image ? d.exampleAlbum : []
+ albumCount: model.contentType === StatusMessage.ContentType.Image ? d.exampleAlbum.length : 0
}
replyDetails {
@@ -222,6 +237,7 @@ SplitView {
onReplyMessageClicked: logs.logEvent("StatusMessage::replyMessageClicked")
onResendClicked: logs.logEvent("StatusMessage::resendClicked")
onLinkActivated: logs.logEvent("StatusMessage::linkActivated" + link)
+ onImageClicked: logs.logEvent("StatusMessage::imageClicked")
}
}
}
diff --git a/storybook/qmlTests/tests/tst_StatusMessage.qml b/storybook/qmlTests/tests/tst_StatusMessage.qml
index af66388891..09c388b447 100644
--- a/storybook/qmlTests/tests/tst_StatusMessage.qml
+++ b/storybook/qmlTests/tests/tst_StatusMessage.qml
@@ -35,7 +35,7 @@ Item {
property StatusMessage controlUnderTest: null
TestCase {
- name: "TokenSelectorView"
+ name: "StatusMessage"
when: windowShown
function init() {
diff --git a/storybook/src/Models/PaymentRequestModel.qml b/storybook/src/Models/PaymentRequestModel.qml
new file mode 100644
index 0000000000..2e6128ae19
--- /dev/null
+++ b/storybook/src/Models/PaymentRequestModel.qml
@@ -0,0 +1,18 @@
+import QtQuick 2.15
+
+ListModel {
+ id: root
+
+ ListElement {
+ symbol: "WBTC"
+ amount: "0.00017"
+ address: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240"
+ chainId: 1 // main
+ }
+ ListElement {
+ symbol: "ETH"
+ amount: "12345.6789"
+ address: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881"
+ chainId: 10 // Opti
+ }
+}
diff --git a/storybook/src/Models/qmldir b/storybook/src/Models/qmldir
index 04d623fc7e..d72574bb7b 100644
--- a/storybook/src/Models/qmldir
+++ b/storybook/src/Models/qmldir
@@ -23,6 +23,7 @@ TokensBySymbolModel 1.0 TokensBySymbolModel.qml
CommunitiesModel 1.0 CommunitiesModel.qml
OnRampProvidersModel 1.0 OnRampProvidersModel.qml
SwapTransactionRoutes 1.0 SwapTransactionRoutes.qml
+PaymentRequestModel 1.0 PaymentRequestModel.qml
singleton ModelsData 1.0 ModelsData.qml
singleton NetworksModel 1.0 NetworksModel.qml
diff --git a/ui/StatusQ/src/StatusQ/Components/StatusMessage.qml b/ui/StatusQ/src/StatusQ/Components/StatusMessage.qml
index 77d9650bc1..3258579d69 100644
--- a/ui/StatusQ/src/StatusQ/Components/StatusMessage.qml
+++ b/ui/StatusQ/src/StatusQ/Components/StatusMessage.qml
@@ -49,6 +49,7 @@ Control {
property string messageAttachments: ""
property var reactionIcons: []
property var linkPreviewModel
+ property var paymentRequestModel
property var gifLinks
property string messageId: ""
@@ -312,6 +313,7 @@ Control {
anchors.right: parent.right
visible: active
sourceComponent: StatusTextMessage {
+ objectName: "StatusMessage_textMessage"
messageDetails: root.messageDetails
isEdited: root.isEdited
allowShowMore: !root.isInPinnedPopup
@@ -326,6 +328,7 @@ Control {
Loader {
active: true
sourceComponent: StatusMessageImageAlbum {
+ objectName: "StatusMessage_imageAlbum"
width: messageLayout.width
album: root.messageDetails.albumCount > 0 ? root.messageDetails.album : [root.messageDetails.messageContent]
albumCount: root.messageDetails.albumCount > 0 ? root.messageDetails.albumCount : 1
@@ -375,7 +378,8 @@ Control {
Layout.preferredHeight: implicitHeight
active: !root.editMode &&
((!!root.linkPreviewModel && root.linkPreviewModel.count > 0)
- || (!!root.gifLinks && root.gifLinks.length > 0))
+ || (!!root.gifLinks && root.gifLinks.length > 0)
+ || (!!root.paymentRequestModel && root.paymentRequestModel.ModelCount.count > 0))
visible: active
}
Loader {
diff --git a/ui/StatusQ/src/StatusQ/Components/private/statusMessage/StatusMessageImageAlbum.qml b/ui/StatusQ/src/StatusQ/Components/private/statusMessage/StatusMessageImageAlbum.qml
index 24bdb9728e..a6159859fa 100644
--- a/ui/StatusQ/src/StatusQ/Components/private/statusMessage/StatusMessageImageAlbum.qml
+++ b/ui/StatusQ/src/StatusQ/Components/private/statusMessage/StatusMessageImageAlbum.qml
@@ -24,6 +24,7 @@ RowLayout {
delegate: Loader {
active: true
+ objectName: "album_image_loader_" + index
readonly property bool imageLoaded: index < root.album.length
readonly property string imagePath: imageLoaded ? root.album[index] : ""
sourceComponent: imageLoaded ? imageComponent : imagePlaceholderComponent
diff --git a/ui/StatusQ/src/assets.qrc b/ui/StatusQ/src/assets.qrc
index eb1dc51287..5b4a8438d7 100644
--- a/ui/StatusQ/src/assets.qrc
+++ b/ui/StatusQ/src/assets.qrc
@@ -8043,6 +8043,7 @@
assets/png/chat/chat@2x.png
assets/png/chat/chat@3x.png
assets/png/chat/wave.png
+ assets/png/chat/request_payment_banner.png
assets/png/keycard/authenticate.png
assets/png/keycard/biometrics-fail.png
assets/png/keycard/biometrics-success.png
diff --git a/ui/StatusQ/src/assets/png/chat/request_payment_banner.png b/ui/StatusQ/src/assets/png/chat/request_payment_banner.png
new file mode 100644
index 0000000000..9714b5a1c5
Binary files /dev/null and b/ui/StatusQ/src/assets/png/chat/request_payment_banner.png differ
diff --git a/ui/app/AppLayouts/Chat/views/ChatColumnView.qml b/ui/app/AppLayouts/Chat/views/ChatColumnView.qml
index ff76d33239..9f9c7394b5 100644
--- a/ui/app/AppLayouts/Chat/views/ChatColumnView.qml
+++ b/ui/app/AppLayouts/Chat/views/ChatColumnView.qml
@@ -200,6 +200,14 @@ Item {
// Call later to make sure activeUsersStore and activeMessagesStore bindings are updated
Qt.callLater(d.restoreInputState, preservedText)
}
+
+ function formatBalance(amount, symbol) {
+ let asset = ModelUtils.getByKey(WalletStore.RootStore.tokensStore.flatTokensModel, "symbol", symbol)
+ if (!asset)
+ return "0"
+ const num = AmountsArithmetic.toNumber(amount, asset.decimals)
+ return root.rootStore.currencyStore.formatCurrencyAmount(num, symbol, {noSynbol: true})
+ }
}
EmptyChatPanel {
@@ -238,11 +246,13 @@ Item {
utilsStore: root.utilsStore
rootStore: root.rootStore
contactsStore: root.contactsStore
+ formatBalance: d.formatBalance
emojiPopup: root.emojiPopup
stickersPopup: root.stickersPopup
stickersLoaded: root.stickersLoaded
isBlocked: model.blocked
sendViaPersonalChatEnabled: root.sendViaPersonalChatEnabled
+ areTestNetworksEnabled: root.areTestNetworksEnabled
onOpenStickerPackPopup: {
root.openStickerPackPopup(stickerPackId)
}
@@ -294,6 +304,8 @@ Item {
sharedStore: root.sharedRootStore
linkPreviewModel: !!d.activeChatContentModule ? d.activeChatContentModule.inputAreaModule.linkPreviewModel : null
+ paymentRequestModel: !!d.activeChatContentModule ? d.activeChatContentModule.inputAreaModule.paymentRequestModel : null
+ formatBalance: d.formatBalance
urlsList: d.urlsList
askToEnableLinkPreview: {
if(!d.activeChatContentModule || !d.activeChatContentModule.inputAreaModule || !d.activeChatContentModule.inputAreaModule.preservedProperties)
@@ -391,6 +403,7 @@ Item {
d.activeChatContentModule.inputAreaModule.setLinkPreviewEnabledForCurrentMessage(false)
}
onDismissLinkPreview: (index) => d.activeChatContentModule.inputAreaModule.removeLinkPreviewData(index)
+ onRemovePaymentRequestPreview: (index) => d.activeChatContentModule.inputAreaModule.removePaymentRequestPreviewData(index)
}
ChatPermissionQualificationPanel {
diff --git a/ui/app/AppLayouts/Chat/views/ChatContentView.qml b/ui/app/AppLayouts/Chat/views/ChatContentView.qml
index b638956c81..cfd60e6305 100644
--- a/ui/app/AppLayouts/Chat/views/ChatContentView.qml
+++ b/ui/app/AppLayouts/Chat/views/ChatContentView.qml
@@ -38,8 +38,10 @@ ColumnLayout {
property ContactsStore contactsStore
property string chatId
property int chatType: Constants.chatType.unknown
+ property var formatBalance
readonly property alias chatMessagesLoader: chatMessagesLoader
+ property bool areTestNetworksEnabled
property var emojiPopup
property var stickersPopup
@@ -94,6 +96,7 @@ ColumnLayout {
rootStore: root.rootStore
contactsStore: root.contactsStore
messageStore: root.messageStore
+ formatBalance: root.formatBalance
emojiPopup: root.emojiPopup
stickersPopup: root.stickersPopup
usersStore: root.usersStore
@@ -103,6 +106,7 @@ ColumnLayout {
isChatBlocked: root.isBlocked || !root.isUserAllowedToSendMessage
channelEmoji: !chatContentModule ? "" : (chatContentModule.chatDetails.emoji || "")
sendViaPersonalChatEnabled: root.sendViaPersonalChatEnabled
+ areTestNetworksEnabled: root.areTestNetworksEnabled
onShowReplyArea: (messageId, senderId) => {
root.showReplyArea(messageId)
}
diff --git a/ui/app/AppLayouts/Chat/views/ChatMessagesView.qml b/ui/app/AppLayouts/Chat/views/ChatMessagesView.qml
index 575ed4edab..2f0e5a164b 100644
--- a/ui/app/AppLayouts/Chat/views/ChatMessagesView.qml
+++ b/ui/app/AppLayouts/Chat/views/ChatMessagesView.qml
@@ -39,9 +39,11 @@ Item {
property UsersStore usersStore
property ContactsStore contactsStore
property string channelEmoji
+ property var formatBalance
property var emojiPopup
property var stickersPopup
+ property bool areTestNetworksEnabled
property string chatId: ""
property bool stickersLoaded: false
@@ -283,10 +285,12 @@ Item {
stickersPopup: root.stickersPopup
chatLogView: ListView.view
chatContentModule: root.chatContentModule
+ formatBalance: root.formatBalance
isChatBlocked: root.isChatBlocked
sendViaPersonalChatEnabled: root.sendViaPersonalChatEnabled
+ areTestNetworksEnabled: root.areTestNetworksEnabled
chatId: root.chatId
messageId: model.id
@@ -326,6 +330,7 @@ Item {
deletedByContactColorHash: model.deletedByContactColorHash
linkPreviewModel: model.linkPreviewModel
links: model.links
+ paymentRequestModel: model.paymentRequestModel
messageAttachments: model.messageAttachments
transactionParams: model.transactionParameters
hasMention: model.mentioned
diff --git a/ui/app/mainui/AppMain.qml b/ui/app/mainui/AppMain.qml
index 7461975633..9dcbfb088e 100644
--- a/ui/app/mainui/AppMain.qml
+++ b/ui/app/mainui/AppMain.qml
@@ -708,6 +708,21 @@ Item {
sendModal.open(address)
}
+ function onPaymentRequestClicked(receiverAddress: string, symbol: string, amount: string, chainId: int) {
+ if (!!symbol) {
+ sendModal.preSelectedHoldingID = symbol
+ sendModal.preSelectedHoldingType = Constants.TokenType.ERC20
+ }
+ if (!!amount) {
+ sendModal.preDefinedRawAmountToSend = amount
+ }
+ if (!!chainId) {
+ sendModal.preSelectedChainId = chainId
+ }
+
+ sendModal.open(receiverAddress)
+ }
+
function onSwitchToCommunity(communityId: string) {
appMain.communitiesStore.setActiveCommunity(communityId)
}
@@ -1875,6 +1890,7 @@ Item {
property int preSelectedHoldingType: Constants.TokenType.Unknown
property int preSelectedSendType: Constants.SendType.Unknown
property string preDefinedAmountToSend
+ property string preDefinedRawAmountToSend
property int preSelectedChainId: 0
property bool onlyAssets: false
@@ -1903,6 +1919,7 @@ Item {
sendModal.preSelectedAccountAddress = ""
sendModal.preSelectedRecipient = undefined
sendModal.preDefinedAmountToSend = ""
+ sendModal.preDefinedRawAmountToSend = ""
sendModal.preSelectedChainId = 0
sendModal.stickersPackId = ""
@@ -1929,6 +1946,9 @@ Item {
if (sendModal.preDefinedAmountToSend != "") {
item.preDefinedAmountToSend = sendModal.preDefinedAmountToSend
}
+ if (sendModal.preDefinedRawAmountToSend != "") {
+ item.preDefinedRawAmountToSend = sendModal.preDefinedRawAmountToSend
+ }
if (!!sendModal.preSelectedChainId) {
item.preSelectedChainId = sendModal.preSelectedChainId
}
diff --git a/ui/imports/shared/controls/chat/ChatInputLinksPreviewArea.qml b/ui/imports/shared/controls/chat/ChatInputLinksPreviewArea.qml
index 9da7811cd7..8192552dba 100644
--- a/ui/imports/shared/controls/chat/ChatInputLinksPreviewArea.qml
+++ b/ui/imports/shared/controls/chat/ChatInputLinksPreviewArea.qml
@@ -32,15 +32,25 @@ Control {
*/
required property var linkPreviewModel
required property bool showLinkPreviewSettings
+ /*
+ Expected roles:
+ string symbol
+ string amount
+ */
+ required property var paymentRequestModel
+
+ property var formatBalance: null
readonly property alias hoveredUrl: d.hoveredUrl
- readonly property bool hasContent: imagePreviewArray.length > 0 || showLinkPreviewSettings || linkPreviewRepeater.count > 0
+ readonly property bool hasContent: imagePreviewArray.length > 0 || showLinkPreviewSettings || linkPreviewRepeater.count > 0 || paymentRequestRepeater.count > 0
signal imageRemoved(int index)
signal imageClicked(var chatImage)
signal linkReload(string link)
signal linkClicked(string link)
+ signal removePaymentRequestPreview(int index)
+
signal enableLinkPreview()
signal enableLinkPreviewForThisMessage()
signal disableLinkPreview()
@@ -96,6 +106,21 @@ Control {
onImageRemoved: root.imageRemoved(index)
visible: !!imagePreviewArray && imagePreviewArray.length > 0
}
+ Repeater {
+ id: paymentRequestRepeater
+ model: root.paymentRequestModel
+ delegate: PaymentRequestMiniCardDelegate {
+ required property var model
+
+ amount: {
+ if (!root.formatBalance)
+ return model.amount
+ return root.formatBalance(model.amount, model.symbol)
+ }
+ symbol: model.symbol
+ onClose: root.removePaymentRequestPreview(model.index)
+ }
+ }
Repeater {
id: linkPreviewRepeater
model: d.filteredModel
diff --git a/ui/imports/shared/controls/chat/PaymentRequestMiniCardDelegate.qml b/ui/imports/shared/controls/chat/PaymentRequestMiniCardDelegate.qml
new file mode 100644
index 0000000000..e226e85bf5
--- /dev/null
+++ b/ui/imports/shared/controls/chat/PaymentRequestMiniCardDelegate.qml
@@ -0,0 +1,112 @@
+import QtQuick 2.15
+import QtQuick.Layouts 1.15
+
+import StatusQ.Core.Theme 0.1
+import StatusQ.Components 0.1
+import StatusQ.Controls 0.1
+import StatusQ.Core 0.1
+
+import utils 1.0
+
+CalloutCard {
+ id: root
+
+ required property string amount
+ required property string symbol
+
+ readonly property bool containsMouse: mouseArea.hovered || closeButton.hovered
+
+ signal close()
+
+ implicitWidth:260
+ implicitHeight: 64
+ verticalPadding: 15
+ horizontalPadding: 12
+ borderColor: Theme.palette.directColor7
+ backgroundColor: root.containsMouse ? Theme.palette.directColor7 : Theme.palette.background
+
+ contentItem: Item {
+ implicitHeight: layout.implicitHeight
+ implicitWidth: layout.implicitWidth
+
+ RowLayout {
+ id: layout
+ anchors.fill: parent
+ spacing: 16
+
+ StatusRoundIcon {
+ id: favIcon
+ Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
+ Layout.preferredWidth: 36
+ Layout.preferredHeight: 36
+ asset.width: 24
+ asset.height: 24
+ asset.bgColor: Theme.palette.directColor7
+ asset.bgHeight: 36
+ asset.bgWidth: 36
+ asset.color: Theme.palette.primaryColor1
+ asset.name: Theme.svg("send")
+
+ StatusSmartIdenticon {
+ anchors.bottom: parent.bottom
+ anchors.horizontalCenter: parent.right
+ asset.width: 16
+ asset.height: 16
+ asset.bgColor: root.containsMouse ? Theme.palette.transparent : Theme.palette.background
+ asset.bgHeight: 20
+ asset.bgWidth: 20
+ asset.isImage: true
+ asset.name: Constants.tokenIcon(root.symbol)
+ }
+ }
+
+ ColumnLayout {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ StatusBaseText {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ text: qsTr("Payment request")
+ font.pixelSize: Theme.additionalTextSize
+ font.weight: Font.Medium
+ }
+ RowLayout {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ StatusBaseText {
+ Layout.maximumWidth: parent.width * 0.8
+ Layout.fillHeight: true
+ font.pixelSize: Theme.tertiaryTextFontSize
+ color: Theme.palette.baseColor1
+ verticalAlignment: Text.AlignVCenter
+ elide: Text.ElideRight
+ text: root.amount
+ }
+ StatusBaseText {
+ Layout.fillHeight: true
+ font.pixelSize: Theme.tertiaryTextFontSize
+ color: Theme.palette.baseColor1
+ verticalAlignment: Text.AlignVCenter
+ text: root.symbol
+ }
+ }
+ }
+
+ StatusFlatButton {
+ id: closeButton
+ icon.name: "close"
+ size: StatusBaseButton.Size.Small
+ hoverColor: Theme.palette.directColor8
+ textColor: Theme.palette.directColor1
+ onClicked: root.close()
+ }
+ }
+ }
+
+ HoverHandler {
+ id: mouseArea
+ target: background
+ cursorShape: Qt.PointingHandCursor
+ }
+}
diff --git a/ui/imports/shared/controls/chat/qmldir b/ui/imports/shared/controls/chat/qmldir
index 3acd3d2689..b422a4d1ac 100644
--- a/ui/imports/shared/controls/chat/qmldir
+++ b/ui/imports/shared/controls/chat/qmldir
@@ -10,6 +10,7 @@ LinkPreviewCard 1.0 LinkPreviewCard.qml
LinkPreviewMiniCard 1.0 LinkPreviewMiniCard.qml
LinkPreviewSettingsCard 1.0 LinkPreviewSettingsCard.qml
LinkPreviewSettingsCardMenu 1.0 LinkPreviewSettingsCardMenu.qml
+PaymentRequestMiniCardDelegate 1.0 PaymentRequestMiniCardDelegate.qml
MessageMouseArea 1.0 MessageMouseArea.qml
MessageReactionsRow 1.0 MessageReactionsRow.qml
ProfileHeader 1.0 ProfileHeader.qml
diff --git a/ui/imports/shared/controls/delegates/PaymentRequestCardDelegate.qml b/ui/imports/shared/controls/delegates/PaymentRequestCardDelegate.qml
new file mode 100644
index 0000000000..c5a507e754
--- /dev/null
+++ b/ui/imports/shared/controls/delegates/PaymentRequestCardDelegate.qml
@@ -0,0 +1,155 @@
+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 StatusQ.Controls 0.1
+
+import shared.controls.chat 1.0
+
+import utils 1.0
+
+CalloutCard {
+ id: root
+
+ required property string amount
+ required property string symbol
+ required property string address
+
+ required property bool areTestNetworksEnabled
+
+ property string senderName
+ property string senderThumbnailImage
+ property int senderColorId
+
+ 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
+ readonly property bool highlight: (root.highlight || root.hovered) && isAvailable
+ readonly property bool isAvailable: !root.areTestNetworksEnabled
+ }
+
+ 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.width: symbolImage.width
+ asset.height: symbolImage.height
+ asset.isImage: !!root.senderThumbnailImage
+ asset.name: root.senderThumbnailImage
+ asset.isLetterIdenticon: root.senderThumbnailImage === ""
+ asset.color: Theme.palette.userCustomizationColors[root.senderColorId]
+ asset.charactersLen: 2
+ 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)
+ }
+ }
+
+ StatusToolTip {
+ text: qsTr("Not available in the testnet mode")
+ visible: !d.isAvailable && root.hovered
+ y: -height
+ }
+
+ MouseArea {
+ id: ma
+ anchors.fill: root
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ acceptedButtons: Qt.LeftButton | Qt.RightButton
+ onClicked: {
+ if (!d.isAvailable)
+ return
+ root.clicked(mouse)
+ }
+ }
+}
diff --git a/ui/imports/shared/controls/delegates/qmldir b/ui/imports/shared/controls/delegates/qmldir
index 45e2a69f02..8e630eb00e 100644
--- a/ui/imports/shared/controls/delegates/qmldir
+++ b/ui/imports/shared/controls/delegates/qmldir
@@ -2,4 +2,5 @@ ContactListItemDelegate 1.0 ContactListItemDelegate.qml
LinkPreviewCardDelegate 1.0 LinkPreviewCardDelegate.qml
LinkPreviewGifDelegate 1.0 LinkPreviewGifDelegate.qml
LinkPreviewMiniCardDelegate 1.0 LinkPreviewMiniCardDelegate.qml
+PaymentRequestCardDelegate 1.0 PaymentRequestCardDelegate.qml
InfoCard 1.0 InfoCard.qml
diff --git a/ui/imports/shared/popups/send/SendModal.qml b/ui/imports/shared/popups/send/SendModal.qml
index d2981d7714..46973a7315 100644
--- a/ui/imports/shared/popups/send/SendModal.qml
+++ b/ui/imports/shared/popups/send/SendModal.qml
@@ -41,6 +41,7 @@ StatusDialog {
property alias preSelectedRecipientType: recipientInputLoader.selectedRecipientType
property string preDefinedAmountToSend
+ property string preDefinedRawAmountToSend
property int preSelectedChainId: 0
property string stickersPackId
@@ -99,8 +100,8 @@ StatusDialog {
return Constants.NoError
}
- readonly property double maxFiatBalance: isSelectedHoldingValidAsset ? selectedHolding.currencyBalance : 0
- readonly property double maxCryptoBalance: isSelectedHoldingValidAsset ? selectedHolding.currentBalance : 0
+ readonly property double maxFiatBalance: isSelectedHoldingValidAsset && !!selectedHolding.currencyBalance ? selectedHolding.currencyBalance : 0
+ readonly property double maxCryptoBalance: isSelectedHoldingValidAsset && !!selectedHolding.currencyBalance ? selectedHolding.currentBalance : 0
readonly property double maxInputBalance: amountToSend.fiatMode ? maxFiatBalance : maxCryptoBalance
readonly property string tokenSymbol: !!d.selectedHolding && !!d.selectedHolding.symbol ? d.selectedHolding.symbol: ""
@@ -234,13 +235,24 @@ StatusDialog {
if (popup.preSelectedHoldingType === Constants.TokenType.Native
|| popup.preSelectedHoldingType === Constants.TokenType.ERC20) {
- const entry = SQUtils.ModelUtils.getByKey(
+ let iconSource = ""
+ let entry = SQUtils.ModelUtils.getByKey(
assetsAdaptor.outputAssetsModel, "tokensKey",
popup.preSelectedHoldingID)
+
+ if (entry) {
+ iconSource = entry.iconSource
+ } else {
+ entry = SQUtils.ModelUtils.getByKey(
+ popup.store.walletAssetStore.renamedTokensBySymbolModel, "tokensKey",
+ popup.preSelectedHoldingID)
+ iconSource = Constants.tokenIcon(entry.symbol)
+ }
+
d.selectedHoldingType = Constants.TokenType.ERC20
d.selectedHolding = entry
- holdingSelector.setSelection(entry.symbol, entry.iconSource,
+ holdingSelector.setSelection(entry.symbol, iconSource,
popup.preSelectedHoldingID)
holdingSelector.selectedItem = entry
} else {
@@ -260,6 +272,7 @@ StatusDialog {
holdingSelector.currentTab = TokenSelectorPanel.Tabs.Collectibles
}
}
+
if(!!popup.preDefinedAmountToSend) {
// TODO: At this stage the number should not be localized. However
// in many places when initializing popup the number is provided
@@ -267,9 +280,12 @@ StatusDialog {
// number consistently. Only the displaying component should apply
// final localized formatting.
const delocalized = popup.preDefinedAmountToSend.replace(LocaleUtils.userInputLocale.decimalPoint, ".")
-
amountToSend.setValue(delocalized)
}
+
+ if (!!popup.preDefinedRawAmountToSend) {
+ amountToSend.setRawValue(popup.preDefinedRawAmountToSend)
+ }
if (!!popup.preSelectedChainId) {
popup.preDefinedAmountToSend = popup.preDefinedAmountToSend.replace(Qt.locale().decimalPoint, '.')
diff --git a/ui/imports/shared/popups/send/views/AmountToSend.qml b/ui/imports/shared/popups/send/views/AmountToSend.qml
index b1b67b6c53..5c62aab902 100644
--- a/ui/imports/shared/popups/send/views/AmountToSend.qml
+++ b/ui/imports/shared/popups/send/views/AmountToSend.qml
@@ -107,6 +107,22 @@ Control {
textField.text = d.localize(trimmed)
}
+ function setRawValue(valueString) {
+ if (!valueString)
+ valueString = "0"
+
+ if (d.fiatMode) {
+ setValue(valueString)
+ return
+ }
+
+ const divisor = SQUtils.AmountsArithmetic.fromExponent(root.multiplierIndex)
+ const stringNumber = SQUtils.AmountsArithmetic.div(SQUtils.AmountsArithmetic.fromString(valueString), divisor).toFixed(root.multiplierIndex)
+ const trimmed = d.removeDecimalTrailingZeros(stringNumber)
+
+ textField.text = d.localize(trimmed)
+ }
+
function clear() {
textField.clear()
}
diff --git a/ui/imports/shared/status/StatusChatInput.qml b/ui/imports/shared/status/StatusChatInput.qml
index 735fbedb23..e2b05dfa43 100644
--- a/ui/imports/shared/status/StatusChatInput.qml
+++ b/ui/imports/shared/status/StatusChatInput.qml
@@ -37,6 +37,7 @@ Rectangle {
signal disableLinkPreview()
signal dismissLinkPreviewSettings()
signal dismissLinkPreview(int index)
+ signal removePaymentRequestPreview(int index)
property var usersModel
property SharedStores.RootStore sharedStore
@@ -67,6 +68,9 @@ Rectangle {
property var fileUrlsAndSources: []
property var linkPreviewModel: null
+ property var paymentRequestModel: null
+
+ property var formatBalance: null
property var urlsList: []
@@ -1224,6 +1228,8 @@ Rectangle {
topPadding: 12
imagePreviewArray: control.fileUrlsAndSources
linkPreviewModel: control.linkPreviewModel
+ paymentRequestModel: control.paymentRequestModel
+ formatBalance: control.formatBalance
showLinkPreviewSettings: control.askToEnableLinkPreview
onImageRemoved: (index) => {
//Just do a copy and replace the whole thing because it's a plain JS array and thre's no signal when a single item is removed
@@ -1242,6 +1248,7 @@ Rectangle {
onDisableLinkPreview: () => control.disableLinkPreview()
onDismissLinkPreviewSettings: () => control.dismissLinkPreviewSettings()
onDismissLinkPreview: (index) => control.dismissLinkPreview(index)
+ onRemovePaymentRequestPreview: (index) => control.removePaymentRequestPreview(index)
}
RowLayout {
diff --git a/ui/imports/shared/views/chat/LinksMessageView.qml b/ui/imports/shared/views/chat/LinksMessageView.qml
index 8d7787d4b9..e623ad9ce5 100644
--- a/ui/imports/shared/views/chat/LinksMessageView.qml
+++ b/ui/imports/shared/views/chat/LinksMessageView.qml
@@ -6,6 +6,7 @@ import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
+import StatusQ.Core.Utils 0.1 as SQUtils
import shared.controls 1.0
import shared.panels 1.0
@@ -25,15 +26,26 @@ Flow {
required property var linkPreviewModel
required property var gifLinks
+ required property var paymentRequestModel
+
required property bool gifUnfurlingEnabled
required property bool canAskToUnfurlGifs
+ required property bool areTestNetworksEnabled
+
+ property var formatBalance: null
+
+ property string senderName
+ property string senderThumbnailImage
+ property int senderColorId
+
readonly property alias hoveredLink: linksRepeater.hoveredUrl
property string highlightLink: ""
signal imageClicked(var image, var mouse, string imageSource, string url)
signal openContextMenu(var item, string url, string domain)
signal setNeverAskAboutUnfurlingAgain(bool neverAskAgain)
+ signal paymentRequestClicked(int index)
function resetLocalAskAboutUnfurling() {
d.localAskAboutUnfurling = true
@@ -56,9 +68,30 @@ Flow {
sourceComponent: enableLinkComponent
}
+ Repeater {
+ id: paymentRequestRepeater
+ model: root.paymentRequestModel
+ delegate: PaymentRequestCardDelegate {
+ required property var model
+ objectName: "PaymentRequestDelegate_" + model.index
+ areTestNetworksEnabled: root.areTestNetworksEnabled
+ amount: {
+ if (!root.formatBalance)
+ return model.amount
+ return root.formatBalance(model.amount, model.symbol)
+ }
+ symbol: model.symbol
+ address: model.receiver
+ senderName: root.senderName
+ senderThumbnailImage: root.senderThumbnailImage
+ senderColorId: root.senderColorId
+ onClicked: root.paymentRequestClicked(model.index)
+ }
+ }
+
Repeater {
id: tempRepeater
- visible: root.canAskToUnfurlGifs
+ visible: root.cankToUnfurlGifs
model: root.gifUnfurlingEnabled ? gifLinks : []
delegate: LinkPreviewGifDelegate {
diff --git a/ui/imports/shared/views/chat/MessageView.qml b/ui/imports/shared/views/chat/MessageView.qml
index 5632709078..be295f677d 100644
--- a/ui/imports/shared/views/chat/MessageView.qml
+++ b/ui/imports/shared/views/chat/MessageView.qml
@@ -70,9 +70,11 @@ Loader {
property string messagePinnedBy: ""
property var reactionsModel: []
property var linkPreviewModel
+ property var paymentRequestModel
property string messageAttachments: ""
property var transactionParams
property var emojiReactionsModel
+ property var formatBalance
// These 2 properties can be dropped when the new unfurling flow supports GIFs
property var links
@@ -137,6 +139,8 @@ Loader {
property bool sendViaPersonalChatEnabled
+ property bool areTestNetworksEnabled
+
property bool stickersLoaded: false
property string sticker
property int stickerPack: -1
@@ -720,6 +724,7 @@ Loader {
resendError: root.resendError
reactionsModel: root.reactionsModel
linkPreviewModel: root.linkPreviewModel
+ paymentRequestModel: root.paymentRequestModel
gifLinks: root.gifLinks
showHeader: root.shouldRepeatHeader || dateGroupLabel.visible || isAReply ||
@@ -973,9 +978,15 @@ Loader {
linkPreviewModel: root.linkPreviewModel
gifLinks: root.gifLinks
+ senderName: root.senderDisplayName
+ senderThumbnailImage: root.senderIcon || ""
+ senderColorId: Utils.colorIdForPubkey(root.senderId)
+ paymentRequestModel: root.paymentRequestModel
playAnimations: root.Window.active && root.messageStore.isChatActive
isOnline: root.rootStore.mainModuleInst.isOnline
highlightLink: delegate.hoveredLink
+ areTestNetworksEnabled: root.areTestNetworksEnabled
+ formatBalance: root.formatBalance
onImageClicked: (image, mouse, imageSource, url) => {
d.onImageClicked(image, mouse, imageSource, url)
}
@@ -986,6 +997,10 @@ Loader {
gifUnfurlingEnabled: root.sharedRootStore.gifUnfurlingEnabled
canAskToUnfurlGifs: !root.sharedRootStore.neverAskAboutUnfurlingAgain
onSetNeverAskAboutUnfurlingAgain: root.sharedRootStore.setNeverAskAboutUnfurlingAgain(neverAskAgain)
+ onPaymentRequestClicked: (index) => {
+ const request = StatusQUtils.ModelUtils.get(paymentRequestModel, index)
+ Global.paymentRequestClicked(request.receiver, request.symbol, request.amount, request.chainId)
+ }
Component.onCompleted: {
root.messageStore.messageModule.forceLinkPreviewsLocalData(root.messageId)
diff --git a/ui/imports/utils/Global.qml b/ui/imports/utils/Global.qml
index fe3d7e6722..ad82200e97 100644
--- a/ui/imports/utils/Global.qml
+++ b/ui/imports/utils/Global.qml
@@ -70,6 +70,7 @@ QtObject {
signal appSectionBySectionTypeChanged(int sectionType, int subsection, int subSubsection, var data)
signal openSendModal(string address)
+ signal paymentRequestClicked(string receiverAddress, string symbol, string amount, int chainId)
signal switchToCommunity(string communityId)
signal switchToCommunitySettings(string communityId)
signal switchToCommunityChannelsView(string communityId)