fix(liquidity): copy selected holding addresses

This commit is contained in:
Ricardo Guilherme Schmidt
2026-07-18 19:56:57 -03:00
parent 46f8674cdd
commit f20bcaf11f
6 changed files with 195 additions and 29 deletions
@@ -0,0 +1,51 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls.Basic
Button {
id: root
required property var theme
property string value: ""
property bool copied: false
signal copyRequested(string value)
implicitWidth: 30
implicitHeight: 24
visible: root.value.length > 0
enabled: root.value.length > 0
hoverEnabled: true
text: root.copied ? "\u2713" : qsTr("Copy")
Accessible.name: root.copied ? qsTr("Copied") : qsTr("Copy address")
ToolTip.visible: hovered
ToolTip.text: Accessible.name
onClicked: {
root.copied = true
copiedReset.restart()
root.copyRequested(root.value)
}
contentItem: Text {
text: root.text
color: root.enabled ? root.theme.colors.ctaBg : root.theme.colors.textPlaceholder
font.pixelSize: 10
font.weight: Font.Medium
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
background: Rectangle {
radius: 5
color: root.hovered || root.activeFocus ? root.theme.colors.selection : "transparent"
}
Timer {
id: copiedReset
interval: 1600
repeat: false
onTriggered: root.copied = false
}
}
@@ -8,6 +8,7 @@ ComboBox {
required property var theme
property var labelForOption: function(option) { return String(option || "") }
property string tooltipText: ""
implicitHeight: 34
leftPadding: 10
@@ -38,6 +39,9 @@ ComboBox {
font.pixelSize: 10
}
ToolTip.visible: root.tooltipText.length > 0 && root.hovered
ToolTip.text: root.tooltipText
background: Rectangle {
radius: 7
color: !root.enabled
@@ -51,21 +51,36 @@ ColumnLayout {
font.pixelSize: 12
}
AmmSelectionComboBox {
id: lpDestinationPicker
objectName: "lpDestinationPicker"
RowLayout {
Layout.fillWidth: true
theme: root.theme
model: root.destinationRows()
enabled: model.length > 1
currentIndex: root.destinationIndex()
displayText: currentIndex >= 0
? model[currentIndex].label : qsTr("Select destination")
labelForOption: function(destination) { return destination.label }
Accessible.name: qsTr("LP token destination")
onActivated: function(index) {
root.selectDestination(model[index])
spacing: 4
AmmSelectionComboBox {
id: lpDestinationPicker
objectName: "lpDestinationPicker"
Layout.fillWidth: true
theme: root.theme
model: root.destinationRows()
enabled: model.length > 1
currentIndex: root.destinationIndex()
displayText: currentIndex >= 0
? model[currentIndex].label : qsTr("Select destination")
labelForOption: function(destination) { return destination.label }
tooltipText: String(root.snapshot.selectedLpHoldingId || "")
Accessible.name: qsTr("LP token destination")
onActivated: function(index) {
root.selectDestination(model[index])
}
}
AmmCopyButton {
objectName: "copyLpDestinationButton"
Layout.preferredWidth: visible ? implicitWidth : 0
Layout.preferredHeight: implicitHeight
theme: root.theme
value: String(root.snapshot.selectedLpHoldingId || "")
onCopyRequested: function(value) { root.copyToClipboard(value) }
}
}
}
@@ -66,6 +66,12 @@ AmmTokenAmountSurface {
}
}
TextEdit {
id: clipboardProxy
visible: false
}
Timer {
id: commitTimer
@@ -94,23 +100,38 @@ AmmTokenAmountSurface {
onClicked: tokenModal.open()
}
AmmSelectionComboBox {
id: holdingPicker
objectName: "holdingPicker"
RowLayout {
Layout.fillWidth: true
theme: root.theme
spacing: 4
visible: root.holdings.length > 1
enabled: root.holdingSelectionEnabled && root.holdings.length > 1
model: root.holdings
currentIndex: root.holdingIndex()
displayText: currentIndex >= 0
? root.holdingLabel(root.holdings[currentIndex])
: qsTr("Select holding")
labelForOption: function(holding) { return root.holdingLabel(holding) }
Accessible.name: qsTr("Wallet holding for %1").arg(root.label)
onActivated: function(index) {
root.holdingSelected(String(root.holdings[index].holdingId || ""))
AmmSelectionComboBox {
id: holdingPicker
objectName: "holdingPicker"
Layout.fillWidth: true
theme: root.theme
enabled: root.holdingSelectionEnabled && root.holdings.length > 1
model: root.holdings
currentIndex: root.holdingIndex()
displayText: currentIndex >= 0
? root.holdingLabel(root.holdings[currentIndex])
: qsTr("Select holding")
labelForOption: function(holding) { return root.holdingLabel(holding) }
tooltipText: root.selectedHoldingId
Accessible.name: qsTr("Wallet holding for %1").arg(root.label)
onActivated: function(index) {
root.holdingSelected(String(root.holdings[index].holdingId || ""))
}
}
AmmCopyButton {
objectName: "copySelectedHoldingButton"
Layout.preferredWidth: visible ? implicitWidth : 0
Layout.preferredHeight: implicitHeight
theme: root.theme
value: root.selectedHoldingId
onCopyRequested: function(value) { root.copyToClipboard(value) }
}
}
}
@@ -139,6 +160,16 @@ AmmTokenAmountSurface {
tokenModal.acceptInput(value)
}
function copyToClipboard(value) {
if (!value)
return
clipboardProxy.text = value
clipboardProxy.selectAll()
clipboardProxy.copy()
clipboardProxy.deselect()
clipboardProxy.text = ""
}
function commitPendingEdit() {
if (!root.editPending)
return
@@ -86,6 +86,36 @@ TestCase {
compare(picker.background.color, summary.theme.colors.panelBg)
}
function test_lpDestinationCopiesRawBase58HoldingId() {
var address = "2thX6LZfHDZZKUs92febYZhYRcXddmzfzF2NvTkPNE"
var summary = createTemporaryObject(summaryComponent, testCase, {
"snapshot": {
"instruction": "AddLiquidity",
"request": ({ "schema": "new-position.v2" }),
"lpHoldingOptions": [{
"holdingId": address,
"balanceRaw": "7"
}],
"selectedLpHoldingId": address,
"quoteReady": true
}
})
var sink = createTemporaryObject(clipboardSinkComponent, testCase)
verify(summary)
verify(sink)
var picker = findChild(summary, "lpDestinationPicker")
verify(picker)
compare(picker.tooltipText, address)
var copyButton = findChild(summary, "copyLpDestinationButton")
verify(copyButton)
copyButton.click()
sink.paste()
tryCompare(sink, "text", address)
verify(!picker.popup.visible)
}
function test_confirmationShowsQuoteDetails() {
var summary = createTemporaryObject(summaryComponent, testCase, {
"snapshot": {
@@ -39,6 +39,12 @@ TestCase {
}
}
Component {
id: clipboardSinkComponent
TextEdit {}
}
SignalSpy {
id: commitSpy
signalName: "editingCommitted"
@@ -119,6 +125,35 @@ TestCase {
String(input.theme.colors.ctaBg).toLowerCase())
}
function test_selectedHoldingCopiesRawBase58Id() {
var address = "3thX6LZfHDZZKUs92febYZhYRcXddmzfzF2NvTkPNE"
var input = createTemporaryObject(inputComponent, testCase, {
"visible": true,
"holdings": [{
"holdingId": address,
"balanceRaw": "1"
}, {
"holdingId": "22222222222222222222222222222222",
"balanceRaw": "2"
}],
"selectedHoldingId": address
})
var sink = createTemporaryObject(clipboardSinkComponent, testCase)
verify(input)
verify(sink)
var picker = findChild(input, "holdingPicker")
verify(picker)
compare(picker.tooltipText, address)
var copyButton = findChild(input, "copySelectedHoldingButton")
verify(copyButton)
copyButton.click()
sink.paste()
tryCompare(sink, "text", address)
verify(!picker.popup.visible)
}
function test_balanceRefreshShowsLoadingIndicatorWithoutClearingBalance() {
failOnWarning(/Detected recursive rearrange/)
var input = createTemporaryObject(inputComponent, testCase, {