chore: factor out and create new `SendRecipientInput` component

- some minor visual fixes (padding & clear button color)
- make it always paste plain text, eventhough the base component has to
stay RichText
- use it in SendModal->RecipientView
- add a storybook page
- added QML tests

Fixes #15252
This commit is contained in:
Lukáš Tinkl 2024-07-05 15:42:42 +02:00 committed by Lukáš Tinkl
parent 9fcb54cb74
commit 48d8846e29
10 changed files with 353 additions and 48 deletions

View File

@ -0,0 +1,76 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import Storybook 1.0
import utils 1.0
import shared.popups.send.controls 1.0
SplitView {
id: root
Logs { id: logs }
SplitView {
orientation: Qt.Vertical
SplitView.fillWidth: true
Rectangle {
SplitView.fillHeight: true
SplitView.fillWidth: true
color: Theme.palette.baseColor2
SendRecipientInput {
anchors.centerIn: parent
interactive: ctrlInteractive.checked
checkMarkVisible: ctrlCheckmark.checked
Component.onCompleted: forceActiveFocus()
onClearClicked: logs.logEvent("SendRecipientInput::clearClicked", [], arguments)
onValidateInputRequested: logs.logEvent("SendRecipientInput::validateInputRequested", [], arguments)
}
}
LogsAndControlsPanel {
id: logsAndControlsPanel
SplitView.minimumHeight: 100
SplitView.preferredHeight: 200
logsView.logText: logs.logText
ColumnLayout {
TextEdit {
readOnly: true
selectByMouse: true
text: "valid address: 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc4"
}
Switch {
id: ctrlInteractive
text: "Interactive"
checked: true
}
Switch {
id: ctrlCheckmark
text: "Checkmark visible"
checked: false
}
}
}
}
}
// category: Controls
// https://www.figma.com/design/FkFClTCYKf83RJWoifWgoX/Wallet-v2?node-id=9707-106469&t=MeyLezc91kfFYcm9-0
// https://www.figma.com/design/FkFClTCYKf83RJWoifWgoX/Wallet-v2?node-id=10259-120493&t=MeyLezc91kfFYcm9-0
// https://www.figma.com/design/FkFClTCYKf83RJWoifWgoX/Wallet-v2?node-id=9019-88679&t=MeyLezc91kfFYcm9-0
// https://www.figma.com/design/FkFClTCYKf83RJWoifWgoX/Wallet-v2?node-id=9707-105782&t=MeyLezc91kfFYcm9-0

View File

@ -0,0 +1,183 @@
import QtQuick 2.15
import QtTest 1.15
import StatusQ 0.1
import StatusQ.Core.Utils 0.1 as StatusQUtils
import shared.popups.send.controls 1.0
Item {
id: root
width: 600
height: 400
Component {
id: componentUnderTest
SendRecipientInput {
anchors.centerIn: parent
focus: true
}
}
SignalSpy {
id: signalSpyClearClicked
target: controlUnderTest
signalName: "clearClicked"
}
SignalSpy {
id: signalSpyValidateInputRequested
target: controlUnderTest
signalName: "validateInputRequested"
}
property SendRecipientInput controlUnderTest: null
TestCase {
name: "SendRecipientInput"
when: windowShown
function init() {
controlUnderTest = createTemporaryObject(componentUnderTest, root)
signalSpyClearClicked.clear()
signalSpyValidateInputRequested.clear()
QClipboardProxy.clear()
}
function test_basicGeometry() {
verify(!!controlUnderTest)
verify(controlUnderTest.width > 0)
verify(controlUnderTest.height > 0)
}
function test_textInput() {
verify(!!controlUnderTest)
verify(controlUnderTest.input.edit.focus)
verify(controlUnderTest.interactive)
compare(controlUnderTest.placeholderText, qsTr("Enter an ENS name or address"))
keyClick(Qt.Key_0)
keyClick(Qt.Key_X)
keyClick(Qt.Key_D)
keyClick(Qt.Key_E)
keyClick(Qt.Key_A)
keyClick(Qt.Key_D)
keyClick(Qt.Key_B)
keyClick(Qt.Key_E)
keyClick(Qt.Key_E)
keyClick(Qt.Key_F)
const plainText = StatusQUtils.StringUtils.plainText(controlUnderTest.text)
// input's text should be what we typed,
compare(plainText, "0xdeadbeef")
// ... and for each letter pressed the signal `validateInputRequested` should be emitted
compare(signalSpyValidateInputRequested.count, plainText.length)
}
function test_interactive() {
verify(!!controlUnderTest)
verify(controlUnderTest.input.edit.focus)
verify(controlUnderTest.interactive)
controlUnderTest.interactive = false
keyClick(Qt.Key_A)
keyClick(Qt.Key_B)
keyClick(Qt.Key_C)
const plainText = StatusQUtils.StringUtils.plainText(controlUnderTest.text)
// when non-interactive, any text input should be ignored
compare(plainText, "")
}
function test_pasteButton() {
verify(!!controlUnderTest)
const pasteButton = findChild(controlUnderTest, "pasteButton")
verify(!!pasteButton)
compare(pasteButton.visible, false)
// copy sth to clipboard
controlUnderTest.text = "0xdeadbeef"
controlUnderTest.input.edit.selectAll()
controlUnderTest.input.edit.copy()
// paste button should be visible if input is empty and clipboard is not
compare(pasteButton.visible, false)
controlUnderTest.input.edit.clear()
compare(pasteButton.visible, true)
}
function test_pasteUsingButton() {
verify(!!controlUnderTest)
const pasteButton = findChild(controlUnderTest, "pasteButton")
verify(!!pasteButton)
tryCompare(pasteButton, "visible", false)
const richTextToTest = "<b>this is bold text</b>"
// copy rich text to clipboard
controlUnderTest.text = richTextToTest
controlUnderTest.input.edit.selectAll()
controlUnderTest.input.edit.copy()
controlUnderTest.input.edit.clear()
compare(controlUnderTest.input.edit.length , 0)
compare(pasteButton.visible, true)
mouseClick(pasteButton)
verify(!controlUnderTest.text.includes("<b>this is bold text</b>"))
}
function test_pasteUsingKbdShortcut() {
verify(!!controlUnderTest)
const pasteButton = findChild(controlUnderTest, "pasteButton")
verify(!!pasteButton)
tryCompare(pasteButton, "visible", false)
const richTextToTest = "<b>this is bold text</b>"
// copy rich text to clipboard
controlUnderTest.text = richTextToTest
controlUnderTest.input.edit.selectAll()
controlUnderTest.input.edit.copy()
controlUnderTest.input.edit.clear()
compare(controlUnderTest.input.edit.length , 0)
compare(pasteButton.visible, true)
keySequence(StandardKey.Paste)
verify(!controlUnderTest.text.includes("<b>this is bold text</b>"))
}
function test_clearButton() {
verify(!!controlUnderTest)
verify(controlUnderTest.input.edit.focus)
verify(controlUnderTest.interactive)
const clearButton = findChild(controlUnderTest, "clearButton")
compare(clearButton.visible, false)
controlUnderTest.text = "0xdeadbeef"
// clear button should be visible with some text
verify(clearButton.visible)
mouseClick(clearButton)
compare(StatusQUtils.StringUtils.plainText(controlUnderTest.text), "")
compare(signalSpyClearClicked.count, 1)
// and when interactive
controlUnderTest.interactive = false
compare(clearButton.visible, false)
}
function test_checkmark() {
verify(!!controlUnderTest)
compare(controlUnderTest.checkMarkVisible, false)
controlUnderTest.checkMarkVisible = true
const checkmarkIcon = findChild(controlUnderTest, "checkmarkIcon")
verify(!!checkmarkIcon)
verify(checkmarkIcon.visible)
}
}
}

View File

@ -71,8 +71,9 @@ QtObject {
return textAddrss
}
function resolveENS(value) {
return root.mainModuleInst.resolvedENS("", "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc4", "") // return some valid address
function resolveENS(value: string) {
if (!!value && value.endsWith(".eth"))
root.mainModuleInst.resolvedENS("", "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc4", "") // return some valid address
}
function getAsset(assetsList, symbol) {

View File

@ -55,6 +55,7 @@ public:
Q_INVOKABLE bool isValidImageUrl(const QUrl &url, const QStringList &acceptedExtensions) const;
Q_INVOKABLE qint64 getFileSize(const QUrl &url) const;
Q_INVOKABLE void copyTextToClipboard(const QString& text);
Q_INVOKABLE void clear();
signals:
void contentChanged();

View File

@ -92,3 +92,8 @@ void QClipboardProxy::copyTextToClipboard(const QString &text)
{
m_clipboard->setText(text);
}
void QClipboardProxy::clear()
{
m_clipboard->clear();
}

View File

@ -10,6 +10,6 @@ StatusFlatRoundButton {
icon.height: 16
implicitWidth: 24
implicitHeight: 24
icon.color: Theme.palette.baseColor1
icon.color: Theme.palette.directColor9
backgroundHoverColor: "transparent"
}

View File

@ -455,7 +455,7 @@ Item {
onKeyPressed: {
root.keyPressed(event);
}
onEditChanged: {
onEditClicked: {
root.editClicked();
}
onEditingFinished: {

View File

@ -0,0 +1,76 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import StatusQ 0.1
import StatusQ.Controls 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
StatusInput {
id: root
property bool interactive: true
property bool checkMarkVisible
signal clearClicked()
signal validateInputRequested()
placeholderText: qsTr("Enter an ENS name or address")
input.background.color: Theme.palette.indirectColor1
input.background.border.width: 0
input.implicitHeight: 56
rightPadding: 12
input.clearable: false // custom button below
input.edit.readOnly: !root.interactive
multiline: false
input.edit.textFormat: TextEdit.RichText
input.rightComponent: RowLayout {
StatusButton {
objectName: "pasteButton"
font.weight: Font.Normal
borderColor: Theme.palette.primaryColor1
borderWidth: 1
size: StatusBaseButton.Size.Tiny
text: qsTr("Paste")
visible: root.input.edit.length === 0 && root.input.edit.canPaste
focusPolicy: Qt.NoFocus
onClicked: {
root.input.edit.forceActiveFocus()
root.text = QClipboardProxy.text // paste plain text
root.input.edit.cursorPosition = root.input.edit.length
root.validateInputRequested()
}
}
StatusIcon {
objectName: "checkmarkIcon"
Layout.preferredWidth: 16
Layout.preferredHeight: 16
icon: "tiny/checkmark"
color: Theme.palette.primaryColor1
visible: root.checkMarkVisible
}
StatusClearButton {
objectName: "clearButton"
visible: root.input.edit.length !== 0 && root.interactive
onClicked: {
root.input.edit.clear()
root.clearClicked()
}
}
}
Connections {
target: root.input
function onKeyPressed(event) {
if (event.matches(StandardKey.Paste)) {
event.accepted = true
root.text = QClipboardProxy.text // paste plain text
}
}
}
Keys.onTabPressed: event.accepted = true
Keys.onReleased: root.validateInputRequested()
}

View File

@ -10,3 +10,4 @@ BalanceExceeded 1.0 BalanceExceeded.qml
CollectibleBackButtonWithInfo 1.0 CollectibleBackButtonWithInfo.qml
CollectibleNestedDelegate 1.0 CollectibleNestedDelegate.qml
HeaderTitleText 1.0 HeaderTitleText.qml
SendRecipientInput 1.0 SendRecipientInput.qml

View File

@ -175,24 +175,14 @@ Loader {
Component {
id: addressRecipient
StatusInput {
id: recipientInput
SendRecipientInput {
width: parent.width
height: visible ? implicitHeight: 0
visible: !root.isBridgeTx && !!root.selectedAsset
placeholderText: qsTr("Enter an ENS name or address")
input.background.color: Theme.palette.indirectColor1
input.background.border.width: 0
input.implicitHeight: 56
input.clearable: false // custom button below
input.edit.readOnly: !root.interactive
multiline: false
input.edit.textFormat: TextEdit.RichText
text: root.addressText
function validateInput() {
const plainText = store.plainText(recipientInput.text)
const plainText = store.plainText(text)
root.isLoading()
if (Utils.isValidEns(plainText)) {
d.isPending = true
@ -202,38 +192,10 @@ Loader {
}
}
input.rightComponent: RowLayout {
StatusButton {
font.weight: Font.Normal
borderColor: Theme.palette.primaryColor1
size: StatusBaseButton.Size.Tiny
text: qsTr("Paste")
visible: recipientInput.input.edit.length === 0 && recipientInput.input.edit.canPaste
focusPolicy: Qt.NoFocus
onClicked: {
recipientInput.input.edit.forceActiveFocus()
recipientInput.input.edit.paste()
recipientInput.input.edit.cursorPosition = recipientInput.input.edit.length
recipientInput.validateInput()
}
}
StatusIcon {
Layout.preferredWidth: 16
Layout.preferredHeight: 16
icon: "tiny/checkmark"
color: Theme.palette.primaryColor1
visible: root.ready
}
StatusClearButton {
visible: recipientInput.input.edit.length !== 0 && root.interactive
onClicked: {
recipientInput.input.edit.clear()
d.clearValues()
}
}
}
Keys.onTabPressed: event.accepted = true
Keys.onReleased: recipientInput.validateInput()
interactive: root.interactive
checkMarkVisible: root.ready
onClearClicked: d.clearValues()
onValidateInputRequested: validateInput()
}
}