diff --git a/src/app/global/feature_flags.nim b/src/app/global/feature_flags.nim index 40cf2bffec..b7d7682377 100644 --- a/src/app/global/feature_flags.nim +++ b/src/app/global/feature_flags.nim @@ -5,6 +5,7 @@ const DEFAULT_FLAG_DAPPS_ENABLED = false const DEFAULT_FLAG_SWAP_ENABLED = true const DEFAULT_FLAG_CONNECTOR_ENABLED* = false const DEFAULT_FLAG_SEND_VIA_PERSONAL_CHAT_ENABLED = true +const DEFAULT_FLAG_TRANSACTION_DEEP_LINK_ENABLED = false proc boolToEnv*(defaultValue: bool): string = return if defaultValue: "1" else: "0" @@ -15,6 +16,7 @@ QtObject: swapEnabled: bool connectorEnabled: bool sendViaPersonalChatEnabled: bool + transactionDeepLinkEnabled: bool proc setup(self: FeatureFlags) = self.QObject.setup() @@ -22,6 +24,7 @@ QtObject: self.swapEnabled = getEnv("FLAG_SWAP_ENABLED", boolToEnv(DEFAULT_FLAG_SWAP_ENABLED)) != "0" self.connectorEnabled = getEnv("FLAG_CONNECTOR_ENABLED", boolToEnv(DEFAULT_FLAG_CONNECTOR_ENABLED)) != "0" self.sendViaPersonalChatEnabled = getEnv("FLAG_SEND_VIA_PERSONAL_CHAT_ENABLED", boolToEnv(DEFAULT_FLAG_SEND_VIA_PERSONAL_CHAT_ENABLED)) != "0" + self.transactionDeepLinkEnabled = getEnv("FLAG_TRANSACTION_DEEP_LINK_ENABLED", boolToEnv(DEFAULT_FLAG_TRANSACTION_DEEP_LINK_ENABLED)) != "0" proc delete*(self: FeatureFlags) = self.QObject.delete() @@ -53,3 +56,9 @@ QtObject: QtProperty[bool] sendViaPersonalChatEnabled: read = getSendViaPersonalChatEnabled + + proc getTransactionDeepLinkEnabled*(self: FeatureFlags): bool {.slot.} = + return self.transactionDeepLinkEnabled + + QtProperty[bool] transactionDeepLinkEnabled: + read = getTransactionDeepLinkEnabled \ No newline at end of file diff --git a/src/app/modules/main/module.nim b/src/app/modules/main/module.nim index 7f681c93c1..c7a211aea1 100644 --- a/src/app/modules/main/module.nim +++ b/src/app/modules/main/module.nim @@ -1657,6 +1657,8 @@ method activateStatusDeepLink*[T](self: Module[T], statusDeepLink: string) = self.onStatusUrlRequested(StatusUrlAction.DisplayUserProfile, communityId="", channelId="", url="", urlData.contact.publicKey, urlData.community.shard) return + if urlData.transaction.txType >= 0: + self.view.emitShowTransactionModal(urlData.transaction.txType, urlData.transaction.asset, urlData.transaction.amount, urlData.transaction.address, urlData.transaction.chainId, urlData.transaction.toAsset) method onDeactivateChatLoader*[T](self: Module[T], sectionId: string, chatId: string) = if (sectionId.len > 0 and self.chatSectionModules.contains(sectionId)): diff --git a/src/app/modules/main/shared_urls/controller.nim b/src/app/modules/main/shared_urls/controller.nim index 0cd48aaef7..83ef7198c0 100644 --- a/src/app/modules/main/shared_urls/controller.nim +++ b/src/app/modules/main/shared_urls/controller.nim @@ -37,3 +37,7 @@ proc parseContactSharedUrl*(self: Controller, url: string): ContactUrlDataDto = proc parseSharedUrl*(self: Controller, url: string): UrlDataDto = return self.sharedUrlsService.parseSharedUrl(url) + +proc parseTransactionSharedUrl*(self: Controller, url: string): TransactionUrlDataDto = + let data = self.sharedUrlsService.parseSharedUrl(url) + return data.transaction \ No newline at end of file diff --git a/src/app/modules/main/shared_urls/io_interface.nim b/src/app/modules/main/shared_urls/io_interface.nim index 93bb9c6013..08e4997d66 100644 --- a/src/app/modules/main/shared_urls/io_interface.nim +++ b/src/app/modules/main/shared_urls/io_interface.nim @@ -27,6 +27,9 @@ method parseContactSharedUrl*(self: AccessInterface, url: string): string {.base method parseSharedUrl*(self: AccessInterface, url: string): UrlDataDto {.base.} = raise newException(ValueError, "No implementation available") +method parseTransactionSharedUrl*(self: AccessInterface, url: string): string {.base.} = + raise newException(ValueError, "No implementation available") + # This way (using concepts) is used only for the modules managed by AppController type DelegateInterface* = concept c diff --git a/src/app/modules/main/shared_urls/module.nim b/src/app/modules/main/shared_urls/module.nim index c15a146171..3d601b9955 100644 --- a/src/app/modules/main/shared_urls/module.nim +++ b/src/app/modules/main/shared_urls/module.nim @@ -63,3 +63,7 @@ method parseCommunityChannelSharedUrl*(self: Module, url: string): string = method parseContactSharedUrl*(self: Module, url: string): string = let contactData = self.controller.parseContactSharedUrl(url) return $contactData + +method parseTransactionSharedUrl*(self: Module, url: string): string = + let transactionData = self.controller.parseTransactionSharedUrl(url) + return $transactionData \ No newline at end of file diff --git a/src/app/modules/main/shared_urls/view.nim b/src/app/modules/main/shared_urls/view.nim index b321f8f721..aa1edb9212 100644 --- a/src/app/modules/main/shared_urls/view.nim +++ b/src/app/modules/main/shared_urls/view.nim @@ -25,4 +25,7 @@ QtObject: return self.delegate.parseCommunityChannelSharedUrl(url) proc parseContactSharedUrl*(self: View, url: string): string {.slot.} = - return self.delegate.parseContactSharedUrl(url) \ No newline at end of file + return self.delegate.parseContactSharedUrl(url) + + proc parseTransactionSharedUrl*(self: View, url: string): string {.slot.} = + return self.delegate.parseTransactionSharedUrl(url) \ No newline at end of file diff --git a/src/app/modules/main/view.nim b/src/app/modules/main/view.nim index 25dc778791..cd6ddf772d 100644 --- a/src/app/modules/main/view.nim +++ b/src/app/modules/main/view.nim @@ -382,3 +382,8 @@ QtObject: proc stopTokenHoldersManagement*(self: View) {.slot.} = self.delegate.stopTokenHoldersManagement() + + proc showTransactionModal*(self: View, txType: int, asset: string, amount: string, address: string, chainId: int, toAsset: string) {.signal.} + proc emitShowTransactionModal*(self: View, txType: int, asset: string, amount: string, address: string, chainId: int, toAsset: string) = + self.showTransactionModal(txType, asset, amount, address, chainId, toAsset) + diff --git a/src/app/modules/main/wallet_section/send/controller.nim b/src/app/modules/main/wallet_section/send/controller.nim index a39345e093..aa5c8b6a27 100644 --- a/src/app/modules/main/wallet_section/send/controller.nim +++ b/src/app/modules/main/wallet_section/send/controller.nim @@ -8,6 +8,7 @@ import app_service/service/currency/service as currency_service import app_service/service/currency/dto as currency_dto import app_service/service/keycard/service as keycard_service import app_service/service/network/network_item +import app_service/service/shared_urls/dto/url_data as shared_urls_dto import app/modules/shared_modules/keycard_popup/io_interface as keycard_shared_module import app/modules/shared/wallet_utils @@ -137,6 +138,9 @@ proc signMessage*(self: Controller, address: string, hashedPassword: string, has proc sendRouterTransactionsWithSignatures*(self: Controller, uuid: string, signatures: TransactionsSignatures): string = return self.transactionService.sendRouterTransactionsWithSignatures(uuid, signatures) +proc shareTransactionURL*(self: Controller, urlData: shared_urls_dto.TransactionURLDataDto): string = + return self.transactionService.shareTransactionURL(urlData) + proc areTestNetworksEnabled*(self: Controller): bool = return self.walletAccountService.areTestNetworksEnabled() diff --git a/src/app/modules/main/wallet_section/send/io_interface.nim b/src/app/modules/main/wallet_section/send/io_interface.nim index 4df1f267dd..2d159ae898 100644 --- a/src/app/modules/main/wallet_section/send/io_interface.nim +++ b/src/app/modules/main/wallet_section/send/io_interface.nim @@ -2,6 +2,7 @@ import Tables import app/modules/shared_models/currency_amount import app_service/service/transaction/dto import app_service/service/transaction/router_transactions_dto +import app_service/service/shared_urls/dto/url_data import app_service/service/network/network_item import app/modules/shared_models/collectibles_model as collectibles from app_service/service/keycard/service import KeycardEvent @@ -90,4 +91,7 @@ method getNetworkItem*(self: AccessInterface, chainId: int): NetworkItem {.base. raise newException(ValueError, "No implementation available") method getNetworkChainId*(self: AccessInterface, shortName: string): int {.base.} = + raise newException(ValueError, "No implementation available") + +method shareTransactionURL*(self: AccessInterface, urlData: TransactionURLDataDto): string {.base.} = raise newException(ValueError, "No implementation available") \ No newline at end of file diff --git a/src/app/modules/main/wallet_section/send/module.nim b/src/app/modules/main/wallet_section/send/module.nim index 137add50a1..fafc60cf99 100644 --- a/src/app/modules/main/wallet_section/send/module.nim +++ b/src/app/modules/main/wallet_section/send/module.nim @@ -14,6 +14,7 @@ import app_service/service/transaction/service as transaction_service import app_service/service/keycard/service as keycard_service import app_service/service/keycard/constants as keycard_constants import app_service/service/transaction/dto +import app_service/service/shared_urls/dto/url_data as shared_urls_dto import app/modules/shared_models/currency_amount import app_service/service/network/network_item as network_service_item @@ -410,3 +411,6 @@ method splitAndFormatAddressPrefix*(self: Module, text : string, updateInStore: method transactionSendingComplete*(self: Module, txHash: string, status: string) = self.clearTmpData(self.tmpKeepPinPass) self.view.sendtransactionSendingCompleteSignal(txHash, status) + +method shareTransactionURL*(self: Module, urlData: shared_urls_dto.TransactionURLDataDto): string = + return self.controller.shareTransactionURL(urlData) \ No newline at end of file diff --git a/src/app/modules/main/wallet_section/send/view.nim b/src/app/modules/main/wallet_section/send/view.nim index 36fab7fe29..dac5240db5 100644 --- a/src/app/modules/main/wallet_section/send/view.nim +++ b/src/app/modules/main/wallet_section/send/view.nim @@ -3,6 +3,7 @@ import NimQml, Tables, json, sequtils, strutils, stint, chronicles import ./io_interface, ./network_route_model, ./network_route_item, ./suggested_route_item, ./transaction_routes import app_service/service/network/service as network_service import app_service/service/transaction/dto as transaction_dto +import app_service/service/shared_urls/dto/url_data as shared_urls_dto import app_service/common/utils as common_utils import app_service/service/eth/utils as eth_utils @@ -287,6 +288,16 @@ QtObject: parseChainIds(disabledToChainIDs), lockedInAmountsTable) + proc shareTransactionURL*(self: View, txType: int, asset: string, amount: string, address: string, chainId: int, toAsset: string): string {.slot.} = + return self.delegate.shareTransactionURL(shared_urls_dto.TransactionURLDataDto( + txType: txType, + asset: asset, + amount: amount, + address: address, + chainId: chainId, + toAsset: toAsset + )) + proc transactionSendingComplete*(self: View, txHash: string, status: string) {.signal.} proc sendtransactionSendingCompleteSignal*(self: View, txHash: string, status: string) = self.transactionSendingComplete(txHash, status) diff --git a/src/app_service/service/shared_urls/dto/url_data.nim b/src/app_service/service/shared_urls/dto/url_data.nim index f59d2d28e5..1bc1e34183 100644 --- a/src/app_service/service/shared_urls/dto/url_data.nim +++ b/src/app_service/service/shared_urls/dto/url_data.nim @@ -25,10 +25,19 @@ type ContactUrlDataDto* = object description*: string publicKey*: string +type TransactionURLDataDto* = object + txType*: int + asset*: string + amount*: string + address*: string + chainId*: int + toAsset*: string + type UrlDataDto* = object community*: CommunityUrlDataDto channel*: CommunityChannelUrlDataDto contact*: ContactUrlDataDto + transaction*: TransactionURLDataDto notASupportedStatusLink*: bool # If this is true, it was not a supported status link, so we should open it in a browser proc getShard*(jsonObj: JsonNode): Shard = @@ -69,8 +78,18 @@ proc toContactUrlDataDto*(jsonObj: JsonNode): ContactUrlDataDto = discard jsonObj.getProp("description", result.description) discard jsonObj.getProp("publicKey", result.publicKey) +proc toTransactionUrlDataDto*(jsonObj: JsonNode): TransactionURLDataDto = + result = TransactionURLDataDto() + discard jsonObj.getProp("txType", result.txType) + discard jsonObj.getProp("asset", result.asset) + discard jsonObj.getProp("amount", result.amount) + discard jsonObj.getProp("address", result.address) + discard jsonObj.getProp("chainId", result.chainId) + discard jsonObj.getProp("toAsset", result.toAsset) + proc toUrlDataDto*(jsonObj: JsonNode): UrlDataDto = result = UrlDataDto() + result.transaction.txType = -1 var communityObj: JsonNode if (jsonObj.getProp("community", communityObj)): @@ -84,6 +103,10 @@ proc toUrlDataDto*(jsonObj: JsonNode): UrlDataDto = if (jsonObj.getProp("contact", contactObj)): result.contact = contactObj.toContactUrlDataDto() + var txObj: JsonNode + if (jsonObj.getProp("tx", txObj)): + result.transaction = txObj.toTransactionUrlDataDto() + proc toJsonNode*(communityUrlDataDto: CommunityUrlDataDto): JsonNode = var jsonObj = newJObject() jsonObj["displayName"] = %* communityUrlDataDto.displayName @@ -113,3 +136,16 @@ proc `$`*(contactUrlDataDto: ContactUrlDataDto): string = jsonObj["description"] = %* contactUrlDataDto.description jsonObj["publicKey"] = %* contactUrlDataDto.publicKey return $jsonObj + +proc `%`*(transactionURLData: TransactionURLDataDto): JsonNode = + return %* [{ + "txType": transactionURLData.txType, + "asset": transactionURLData.asset, + "amount": transactionURLData.amount, + "address": transactionURLData.address, + "chainId": transactionURLData.chainId, + "toAsset": transactionURLData.toAsset, + }] + +proc `$`*(transactionURLData: TransactionURLDataDto): string = + return $(%transactionURLData) \ No newline at end of file diff --git a/src/app_service/service/transaction/service.nim b/src/app_service/service/transaction/service.nim index b15b403e98..6c75e26b3c 100644 --- a/src/app_service/service/transaction/service.nim +++ b/src/app_service/service/transaction/service.nim @@ -19,6 +19,7 @@ import app_service/service/wallet_account/service as wallet_account_service import app_service/service/network/service as network_service import app_service/service/token/service as token_service import app_service/service/settings/service as settings_service +import app_service/service/shared_urls/dto/url_data as shared_urls_dto import ./dto as transaction_dto import ./dtoV2 import ./dto_conversion @@ -504,3 +505,14 @@ proc sendRouterTransactionsWithSignatures*(self: Service, uuid: string, signatur error "unexpected sending transactions response" return "unexpected sending transactions response" return "" + +proc shareTransactionURL*(self: Service, urlData: shared_urls_dto.TransactionURLDataDto): string = + try: + let response = transactions.shareTransactionURL(%urlData) + if response.error != nil: + error "Error sharing transaction url. Error: ", message = response.error + return "" + return response.result.getStr + except Exception as e: + error "Error sharing transaction url", message = e.msg + return "" \ No newline at end of file diff --git a/src/backend/transactions.nim b/src/backend/transactions.nim index a8f790f2b7..3edce781e9 100644 --- a/src/backend/transactions.nim +++ b/src/backend/transactions.nim @@ -1,4 +1,5 @@ import Tables, json, stint, json_serialization, stew/shims/strformat, logging +import ../app_service/common/utils import ./core as core @@ -117,4 +118,7 @@ proc sendRouterTransactionsWithSignatures*(resultOut: var JsonNode, uuid: string return prepareResponse(resultOut, response) except Exception as e: warn e.msg - return e.msg \ No newline at end of file + return e.msg + +proc shareTransactionURL*(urlData: JsonNode): RpcResponse[JsonNode] = + return callPrivateRPC("shareTransactionURL".prefix, urlData) \ No newline at end of file diff --git a/ui/app/AppLayouts/Chat/ChatLayout.qml b/ui/app/AppLayouts/Chat/ChatLayout.qml index d807cc73d3..bf217deff0 100644 --- a/ui/app/AppLayouts/Chat/ChatLayout.qml +++ b/ui/app/AppLayouts/Chat/ChatLayout.qml @@ -47,6 +47,7 @@ StackLayout { property bool communitySettingsDisabled property bool sendViaPersonalChatEnabled + property bool transactionDeepLinkEnabled property var emojiPopup property var stickersPopup @@ -163,6 +164,7 @@ StackLayout { root.sectionItemModel.memberRole === Constants.memberRole.tokenMaster hasViewOnlyPermissions: root.permissionsStore.viewOnlyPermissionsModel.count > 0 sendViaPersonalChatEnabled: root.sendViaPersonalChatEnabled + transactionDeepLinkEnabled: root.transactionDeepLinkEnabled hasUnrestrictedViewOnlyPermission: { viewOnlyUnrestrictedPermissionHelper.revision diff --git a/ui/app/AppLayouts/Chat/views/ChatColumnView.qml b/ui/app/AppLayouts/Chat/views/ChatColumnView.qml index 169b2aea8a..860bd394e5 100644 --- a/ui/app/AppLayouts/Chat/views/ChatColumnView.qml +++ b/ui/app/AppLayouts/Chat/views/ChatColumnView.qml @@ -58,6 +58,7 @@ Item { property bool amISectionAdmin: false property bool sendViaPersonalChatEnabled + property bool transactionDeepLinkEnabled signal openStickerPackPopup(string stickerPackId) @@ -243,6 +244,7 @@ Item { stickersLoaded: root.stickersLoaded isBlocked: model.blocked sendViaPersonalChatEnabled: root.sendViaPersonalChatEnabled + transactionDeepLinkEnabled: root.transactionDeepLinkEnabled onOpenStickerPackPopup: { root.openStickerPackPopup(stickerPackId) } diff --git a/ui/app/AppLayouts/Chat/views/ChatContentView.qml b/ui/app/AppLayouts/Chat/views/ChatContentView.qml index 7330130c8c..e24b66b25b 100644 --- a/ui/app/AppLayouts/Chat/views/ChatContentView.qml +++ b/ui/app/AppLayouts/Chat/views/ChatContentView.qml @@ -55,6 +55,7 @@ ColumnLayout { } property bool sendViaPersonalChatEnabled + property bool transactionDeepLinkEnabled signal showReplyArea(messageId: string) signal forceInputFocus() @@ -97,6 +98,7 @@ ColumnLayout { isChatBlocked: root.isBlocked || !root.isUserAllowedToSendMessage channelEmoji: !chatContentModule ? "" : (chatContentModule.chatDetails.emoji || "") sendViaPersonalChatEnabled: root.sendViaPersonalChatEnabled + transactionDeepLinkEnabled: root.transactionDeepLinkEnabled 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 8701b9bafc..aca82764f4 100644 --- a/ui/app/AppLayouts/Chat/views/ChatMessagesView.qml +++ b/ui/app/AppLayouts/Chat/views/ChatMessagesView.qml @@ -49,6 +49,7 @@ Item { property bool isOneToOne: false property bool sendViaPersonalChatEnabled + property bool transactionDeepLinkEnabled signal openStickerPackPopup(string stickerPackId) signal showReplyArea(string messageId, string author) @@ -284,6 +285,7 @@ Item { isChatBlocked: root.isChatBlocked sendViaPersonalChatEnabled: root.sendViaPersonalChatEnabled + transactionDeepLinkEnabled: root.transactionDeepLinkEnabled chatId: root.chatId messageId: model.id diff --git a/ui/app/AppLayouts/Chat/views/ChatView.qml b/ui/app/AppLayouts/Chat/views/ChatView.qml index 6bf897db64..00c8a39aea 100644 --- a/ui/app/AppLayouts/Chat/views/ChatView.qml +++ b/ui/app/AppLayouts/Chat/views/ChatView.qml @@ -76,6 +76,7 @@ StatusSectionLayout { property var collectiblesModel property bool sendViaPersonalChatEnabled + property bool transactionDeepLinkEnabled readonly property bool contentLocked: { if (!rootStore.chatCommunitySectionModule.isCommunity()) { @@ -244,6 +245,7 @@ StatusSectionLayout { canPost: !root.rootStore.chatCommunitySectionModule.isCommunity() || root.canPost amISectionAdmin: root.amISectionAdmin sendViaPersonalChatEnabled: root.sendViaPersonalChatEnabled + transactionDeepLinkEnabled: root.transactionDeepLinkEnabled onOpenStickerPackPopup: { Global.openPopup(statusStickerPackClickPopup, {packId: stickerPackId, store: root.stickersPopup.store} ) } diff --git a/ui/app/AppLayouts/Wallet/controls/ShareButton.qml b/ui/app/AppLayouts/Wallet/controls/ShareButton.qml new file mode 100644 index 0000000000..d001482479 --- /dev/null +++ b/ui/app/AppLayouts/Wallet/controls/ShareButton.qml @@ -0,0 +1,40 @@ +import QtQuick 2.15 + +import StatusQ.Controls 0.1 + +StatusButton { + id: root + + text: qsTr("Share") + type: StatusBaseButton.Type.Normal + size: StatusBaseButton.Size.Tiny + + horizontalPadding: 8 + verticalPadding: 3 + implicitHeight: 22 + + radius: 20 + font.pixelSize: 12 + + Timer { + id: shareStateTimer + interval: 2000 + repeat: false + } + + states: State { + name: "success" + when: shareStateTimer.running + PropertyChanges { + target: shareButton + text: qsTr("Copied") + type: StatusBaseButton.Type.Success + icon.name: "tiny/checkmark" + tooltip.text: qsTr("Copied to clipboard") + } + } + + onClicked: { + shareStateTimer.restart() + } +} diff --git a/ui/app/AppLayouts/Wallet/controls/qmldir b/ui/app/AppLayouts/Wallet/controls/qmldir index bce441eaaa..57064fd796 100644 --- a/ui/app/AppLayouts/Wallet/controls/qmldir +++ b/ui/app/AppLayouts/Wallet/controls/qmldir @@ -25,3 +25,4 @@ SwapProvidersTermsAndConditionsText 1.0 SwapProvidersTermsAndConditionsText.qml TokenSelector 1.0 TokenSelector.qml TokenSelectorButton 1.0 TokenSelectorButton.qml TokenSelectorCompactButton 1.0 TokenSelectorCompactButton.qml +ShareButton 1.0 ShareButton.qml diff --git a/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml b/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml index e871fa41c4..469de1728a 100644 --- a/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml +++ b/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml @@ -1,9 +1,10 @@ +import QtQml.Models 2.15 import QtQuick 2.15 import QtQuick.Layouts 1.15 -import QtQml.Models 2.15 import utils 1.0 +import StatusQ 0.1 import StatusQ.Controls 0.1 import StatusQ.Core 0.1 import StatusQ.Core.Backpressure 0.1 @@ -11,8 +12,8 @@ import StatusQ.Core.Theme 0.1 import StatusQ.Core.Utils 0.1 as SQUtils import StatusQ.Popups.Dialog 0.1 -import shared.popups.send.controls 1.0 import shared.controls 1.0 +import shared.popups.send.controls 1.0 import AppLayouts.Wallet.controls 1.0 import AppLayouts.Wallet.panels 1.0 @@ -27,6 +28,8 @@ StatusDialog { required property SwapModalAdaptor swapAdaptor required property int loginType + property bool transactionDeepLinkEnabled + objectName: "swapModal" implicitWidth: 556 @@ -146,6 +149,21 @@ StatusDialog { Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter text: qsTr("Swap") } + ShareButton { + id: shareButton + visible: root.transactionDeepLinkEnabled + enabled: !!root.swapInputParamsForm.fromTokensKey || !!root.swapInputParamsForm.selectedAccountAddress || !!root.swapInputParamsForm.fromTokenAmount || !!root.swapInputParamsForm.toTokenKey + + onClicked: { + const url = root.swapAdaptor.swapStore.getShareTransactionUrl(Constants.SendType.Swap, + root.swapInputParamsForm.fromTokensKey, + root.swapInputParamsForm.fromTokenAmount, + root.swapInputParamsForm.selectedAccountAddress, + root.swapInputParamsForm.selectedNetworkChainId, + root.swapInputParamsForm.toTokenKey) + ClipboardUtils.setText(url) + } + } StatusBaseText { Layout.alignment: Qt.AlignRight | Qt.AlignVCenter text: qsTr("On:") diff --git a/ui/app/AppLayouts/Wallet/stores/SwapStore.qml b/ui/app/AppLayouts/Wallet/stores/SwapStore.qml index a1e3211c38..e02370833b 100644 --- a/ui/app/AppLayouts/Wallet/stores/SwapStore.qml +++ b/ui/app/AppLayouts/Wallet/stores/SwapStore.qml @@ -52,4 +52,8 @@ QtObject { function getWei2Eth(wei, decimals) { return globalUtils.wei2Eth(wei, decimals) } + + function getShareTransactionUrl(txType, asset, amount, address, chainId, toAsset) { + return walletSectionSendInst.shareTransactionURL(txType, asset, amount, address, chainId, toAsset) + } } diff --git a/ui/app/AppLayouts/stores/FeatureFlagsStore.qml b/ui/app/AppLayouts/stores/FeatureFlagsStore.qml index 02a4f6e589..39de354809 100644 --- a/ui/app/AppLayouts/stores/FeatureFlagsStore.qml +++ b/ui/app/AppLayouts/stores/FeatureFlagsStore.qml @@ -5,4 +5,5 @@ QtObject { property bool dappsEnabled property bool swapEnabled property bool sendViaPersonalChatEnabled + property bool transactionDeepLinkEnabled } diff --git a/ui/app/mainui/AppMain.qml b/ui/app/mainui/AppMain.qml index d9608387ee..40166942e5 100644 --- a/ui/app/mainui/AppMain.qml +++ b/ui/app/mainui/AppMain.qml @@ -45,6 +45,7 @@ import AppLayouts.Profile.stores 1.0 as ProfileStores import AppLayouts.Wallet.popups 1.0 as WalletPopups import AppLayouts.Wallet.stores 1.0 as WalletStores import AppLayouts.stores 1.0 as AppStores +import AppLayouts.Wallet.popups.swap 1.0 as WalletSwapPopups import mainui.activitycenter.stores 1.0 import mainui.activitycenter.popups 1.0 @@ -95,6 +96,7 @@ Item { dappsEnabled: featureFlags ? featureFlags.dappsEnabled : false swapEnabled: featureFlags ? featureFlags.swapEnabled : false sendViaPersonalChatEnabled: featureFlags ? featureFlags.sendViaPersonalChatEnabled : false + transactionDeepLinkEnabled: featureFlags ? featureFlags.transactionDeepLinkEnabled : false } required property bool isCentralizedMetricsEnabled @@ -388,11 +390,47 @@ Item { "" ) } + + function onShowTransactionModal(txType, asset, amount, address, chainId, toAsset) { + if (txType === Constants.SendType.Swap) { + d.swapFormData.fromTokensKey = asset + d.swapFormData.toTokenKey = toAsset + d.swapFormData.fromTokenAmount = amount + d.swapFormData.selectedAccountAddress = address + d.swapFormData.selectedNetworkChainId = chainId + Global.openSwapModalRequested(d.swapFormData) + return + } + + sendModal.preSelectedSendType = txType + sendModal.preDefinedAmountToSend = amount + sendModal.preSelectedHoldingID = asset + switch(txType) { + case Constants.SendType.ERC721Transfer: + sendModal.preSelectedHoldingType = Constants.TokenType.ERC721 + break + case Constants.SendType.ERC1155Transfer: + sendModal.preSelectedHoldingType = Constants.TokenType.ERC1155 + break + case Constants.SendType.Transfer: + case Constants.SendType.Bridge: + sendModal.preSelectedHoldingType = Constants.TokenType.ERC20 + break + default: + console.error("Unsupported txType: %1 to open transaction modal").arg(txType) + return + } + sendModal.open(address) + } } QtObject { id: d + property WalletSwapPopups.SwapInputParamsForm swapFormData: WalletSwapPopups.SwapInputParamsForm { + selectedAccountAddress: WalletStores.RootStore.selectedAddress + } + property var activityCenterPopupObj: null function openActivityCenterPopup() { @@ -429,6 +467,7 @@ Item { buyCryptoStore: appMain.buyCryptoStore networkConnectionStore: appMain.networkConnectionStore isDevBuild: !production + transactionDeepLinkEnabled: appMain.featureFlagsStore.transactionDeepLinkEnabled onOpenExternalLink: globalConns.onOpenLink(link) onSaveDomainToUnfurledWhitelist: { @@ -1383,6 +1422,7 @@ Item { emojiPopup: statusEmojiPopup.item stickersPopup: statusStickersPopupLoader.item sendViaPersonalChatEnabled: featureFlagsStore.sendViaPersonalChatEnabled && appMain.networkConnectionStore.sendBuyBridgeEnabled + transactionDeepLinkEnabled: featureFlagsStore.transactionDeepLinkEnabled && appMain.networkConnectionStore.sendBuyBridgeEnabled onProfileButtonClicked: { Global.changeAppSectionBySectionType(Constants.appSection.profile); @@ -1534,6 +1574,7 @@ Item { transactionStore: appMain.transactionStore walletAssetsStore: appMain.walletAssetsStore currencyStore: appMain.currencyStore + transactionDeepLinkEnabled: featureFlagsStore.transactionDeepLinkEnabled && appMain.networkConnectionStore.sendBuyBridgeEnabled onProfileButtonClicked: { Global.changeAppSectionBySectionType(Constants.appSection.profile); @@ -1648,6 +1689,7 @@ Item { collectiblesStore: appMain.walletCollectiblesStore showCustomRoutingMode: !production + transactionDeepLinkEnabled: featureFlagsStore.transactionDeepLinkEnabled onClosed: { sendModal.closed() diff --git a/ui/app/mainui/Popups.qml b/ui/app/mainui/Popups.qml index 0c95e14012..6178fd0fb9 100644 --- a/ui/app/mainui/Popups.qml +++ b/ui/app/mainui/Popups.qml @@ -51,6 +51,7 @@ QtObject { property NetworkConnectionStore networkConnectionStore property WalletStore.BuyCryptoStore buyCryptoStore property bool isDevBuild + property bool transactionDeepLinkEnabled signal openExternalLink(string link) signal saveDomainToUnfurledWhitelist(string domain) @@ -1253,6 +1254,7 @@ QtObject { swapFormData: swapInputParamsForm swapOutputData: SwapOutputData{} } + transactionDeepLinkEnabled: root.transactionDeepLinkEnabled loginType: root.rootStore.loginType onClosed: destroy() } diff --git a/ui/imports/shared/popups/send/SendModal.qml b/ui/imports/shared/popups/send/SendModal.qml index b991cdd7e8..1179ac48ed 100644 --- a/ui/imports/shared/popups/send/SendModal.qml +++ b/ui/imports/shared/popups/send/SendModal.qml @@ -64,6 +64,8 @@ StatusDialog { property int loginType property bool showCustomRoutingMode + property bool transactionDeepLinkEnabled + // In case selected address is incorrect take first account from the list readonly property alias selectedAccount: selectedSenderAccountEntry.item @@ -469,6 +471,38 @@ StatusDialog { amountToSend.forceActiveFocus() } } + + ShareButton { + id: shareButton + + visible: { + if (!popup.transactionDeepLinkEnabled) + return false + switch (store.sendType) { + case Constants.SendType.Bridge: + case Constants.SendType.Transfer: + case Constants.SendType.ERC721Transfer: + case Constants.SendType.ERC1155Transfer: + return true + default: + return false + } + } + enabled: d.isSelectedHoldingValidAsset || (!d.isCollectiblesTransfer && amountToSend.ready) || recipientInputLoader.ready + + onClicked: { + let asset = "" + if (!!d.selectedHolding) { + asset = d.isCollectiblesTransfer ? d.selectedHolding.symbol : d.selectedHolding.tokensKey + } + let recipient = "" + if (recipientInputLoader.ready) { + recipient = popup.store.selectedSenderAccountAddress + } + const url = popup.store.getShareTransactionUrl(store.sendType, asset, amountToSend.asNumber, recipient, 0) + ClipboardUtils.setText(url) + } + } } RowLayout { visible: d.isSelectedHoldingValidAsset && !d.isCollectiblesTransfer diff --git a/ui/imports/shared/stores/send/TransactionStore.qml b/ui/imports/shared/stores/send/TransactionStore.qml index d02f7ff717..512dbbc43c 100644 --- a/ui/imports/shared/stores/send/TransactionStore.qml +++ b/ui/imports/shared/stores/send/TransactionStore.qml @@ -157,6 +157,14 @@ QtObject { } } + function getShareTransactionUrl(txType, asset, amount, address, chainId) { + return walletSectionSendInst.shareTransactionURL(txType, asset, amount, address, chainId, "") + } + + function getShortChainIds(chainShortNames) { + return walletSectionSendInst.getShortChainIds(chainShortNames) + } + function formatCurrencyAmountFromBigInt(balance, symbol, decimals, options = null) { return currencyStore.formatCurrencyAmountFromBigInt(balance, symbol, decimals, options) } diff --git a/ui/imports/shared/views/chat/MessageView.qml b/ui/imports/shared/views/chat/MessageView.qml index 91cf22cebc..c4feeae8b4 100644 --- a/ui/imports/shared/views/chat/MessageView.qml +++ b/ui/imports/shared/views/chat/MessageView.qml @@ -135,6 +135,7 @@ Loader { property bool hasMention: false property bool sendViaPersonalChatEnabled + property bool transactionDeepLinkEnabled property bool stickersLoaded: false property string sticker @@ -759,7 +760,10 @@ Loader { const linkPreviewType = root.linkPreviewModel.getLinkPreviewType(link) - if (linkPreviewType === Constants.LinkPreviewType.Standard || !Utils.isStatusDeepLink(link)) { + if (linkPreviewType === Constants.LinkPreviewType.Standard + || !Utils.isStatusDeepLink(link) + || (!root.transactionDeepLinkEnabled && Utils.isStatusTransactionDeepLink(link))) + { Global.openLink(link) return } diff --git a/ui/imports/utils/Utils.qml b/ui/imports/utils/Utils.qml index 60b723039b..9ea335dc46 100644 --- a/ui/imports/utils/Utils.qml +++ b/ui/imports/utils/Utils.qml @@ -293,6 +293,10 @@ QtObject { return link.includes(Constants.deepLinkPrefix) || link.includes(Constants.externalStatusLink) } + function isStatusTransactionDeepLink(link) { + return isStatusDeepLink(link) && link.indexOf("/tx/") > -1 + } + function removeGifUrls(message) { return message.replace(/(?:https?|ftp):\/\/[\n\S]*(\.gif)+/gm, ''); } diff --git a/vendor/SortFilterProxyModel b/vendor/SortFilterProxyModel index 0141cab5ef..984560cfb0 160000 --- a/vendor/SortFilterProxyModel +++ b/vendor/SortFilterProxyModel @@ -1 +1 @@ -Subproject commit 0141cab5ef0ec2f21ad0b04413936d08cc6e8265 +Subproject commit 984560cfb0b1acd25dbc1055b5de7f6b6fceb548