diff --git a/storybook/pages/StatusFeeOptionPage.qml b/storybook/pages/StatusFeeOptionPage.qml new file mode 100644 index 00000000000..80ab296d2b6 --- /dev/null +++ b/storybook/pages/StatusFeeOptionPage.qml @@ -0,0 +1,169 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Controls 2.15 + +import StatusQ.Controls 0.1 +import StatusQ.Core.Theme 0.1 + +import Storybook 1.0 + +SplitView { + orientation: Qt.Horizontal + + Logs { id: logs } + + Pane { + SplitView.fillWidth: true + SplitView.fillHeight: true + + background: Rectangle { + color: Theme.palette.baseColor4 + } + + StatusFeeOption { + id: feeOption + anchors.centerIn: parent + + onClicked: { + console.warn("control clicked...") + selected = !selected + } + } + } + + LogsAndControlsPanel { + SplitView.fillHeight: true + SplitView.preferredWidth: 300 + + logsView.logText: logs.logText + + ColumnLayout { + anchors.fill: parent + + ComboBox { + model: [ + {testCase: StatusFeeOption.Type.Normal, name: "Normal"}, + {testCase: StatusFeeOption.Type.Fast, name: "Fast"}, + {testCase: StatusFeeOption.Type.Urgent, name: "Urgent"}, + {testCase: StatusFeeOption.Type.Custom, name: "Custom"} + ] + + textRole: "name" + valueRole: "testCase" + onCurrentValueChanged: { + console.warn("valueRole: ", currentValue) + feeOption.type = currentValue + } + } + + RowLayout { + TextField { + id: price + Layout.preferredWidth: 130 + text: "1.45 EUR" + inputMethodHints: Qt.ImhFormattedNumbersOnly + + Component.onCompleted: feeOption.subText = price.text + } + + StatusButton { + text: "Set price" + onClicked: { + feeOption.subText = price.text + } + } + } + + RowLayout { + TextField { + id: time + Layout.preferredWidth: 130 + text: "~60s" + + Component.onCompleted: feeOption.additionalText = time.text + } + + StatusButton { + text: "Set time" + onClicked: { + feeOption.additionalText = time.text + } + } + } + + RowLayout { + TextField { + id: unselectedText + Layout.preferredWidth: 130 + text: "Set your own fees & nonce" + + } + + StatusButton { + text: "Set unselected text" + onClicked: { + feeOption.unselectedText = unselectedText.text + } + } + } + + CheckBox { + text: "Show subtext" + checked: true + + onCheckStateChanged: { + feeOption.showSubText = checked + } + + Component.onCompleted: feeOption.showSubText = checked + } + + CheckBox { + text: "Show additional text" + checked: true + + onCheckStateChanged: { + feeOption.showAdditionalText = checked + } + + Component.onCompleted: feeOption.showAdditionalText = checked + } + + CheckBox { + text: "Show unselected text" + checked: false + + onCheckStateChanged: { + if (checked) { + feeOption.unselectedText = unselectedText.text + return + } + feeOption.unselectedText = "" + } + + Component.onCompleted: feeOption.unselectedText = "" + } + + CheckBox { + id: loading + text: "Set loading state" + checked: false + + onCheckStateChanged: { + if (checked) { + feeOption.subText = "" + feeOption.additionalText = "" + return + } + + feeOption.subText = price.text + feeOption.additionalText = time.text + } + } + + Item { Layout.fillHeight: true } + } + } +} + +// category: Controls diff --git a/storybook/pages/StatusInputPage.qml b/storybook/pages/StatusInputPage.qml index 46df3eb6583..3d92da26d7f 100644 --- a/storybook/pages/StatusInputPage.qml +++ b/storybook/pages/StatusInputPage.qml @@ -3,6 +3,7 @@ import QtQuick.Controls 2.14 import QtQuick.Layouts 1.14 import StatusQ.Controls 0.1 +import StatusQ.Core.Theme 0.1 import Storybook 1.0 import Models 1.0 @@ -32,6 +33,16 @@ SplitView { enabled: enabledCheckBox.checked input.edit.readOnly: readOnlyCheckBox.checked input.clearable: clearableCheckBox.checked + label: "main label" + secondaryLabel: "secondary label" + labelIcon: "info" + labelIconColor: Theme.palette.baseColor1 + labelIconClickable: true + leftPadding: 10 + errorMessageCmp.visible: true + errorMessageCmp.text: "Current: 8.2 GWEI" + errorMessageCmp.horizontalAlignment: Text.AlignLeft + bottomLabelMessageCmp.text: "0.0031 ETH" } } diff --git a/storybook/pages/TransactionSettingsPanelPage.qml b/storybook/pages/TransactionSettingsPanelPage.qml new file mode 100644 index 00000000000..14b93d13775 --- /dev/null +++ b/storybook/pages/TransactionSettingsPanelPage.qml @@ -0,0 +1,85 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 + +import StatusQ.Controls 0.1 +import StatusQ.Core.Theme 0.1 + +import AppLayouts.Wallet.views 1.0 + +import utils 1.0 + +import Storybook 1.0 + +SplitView { + id: root + + SplitView { + SplitView.fillWidth: true + SplitView.fillHeight: true + + orientation: Qt.Vertical + + Rectangle { + SplitView.fillWidth: true + SplitView.fillHeight: true + color: Theme.palette.baseColor3 + + TransactionSettings { + id: txSettings + anchors.centerIn: parent + + currentBaseFee: "8.2" + currentSuggestedMinPriorityFee: "0.06" + currentSuggestedMaxPriorityFee: "5.1" + currentGasAmount: "31500" + currentNonce: 21 + + normalPrice: "1.45 EUR" + normalTime: "~60s" + fastPrice: "1.65 EUR" + fastTime: "~40s" + urgentPrice: "1.85 EUR" + urgentTime: "~15s" + + customPrice: "1.45 EUR" + customTime: "~60s" + + customBaseFee: "6.6" + customPriorityFee: "7.7" + customGasAmount: "35000" + customNonce: "22" + + onConfirmClicked: { + logs.logEvent("confirm clicked...") + logs.logEvent(`selected fee mode: ${txSettings.selectedFeeMode}`) + if (selectedFeeMode === StatusFeeOption.Type.Custom) { + logs.logEvent(`selected customBaseFee...${txSettings.customBaseFee}`) + logs.logEvent(`selected customPriorityFee...${txSettings.customPriorityFee}`) + logs.logEvent(`selected customGasAmount...${txSettings.customGasAmount}`) + logs.logEvent(`selected customNonce...${txSettings.customNonce}`) + } + } + } + } + + Logs { + id: logs + } + + LogsView { + clip: true + + SplitView.preferredHeight: 150 + SplitView.fillWidth: true + + logText: logs.logText + } + } + + Pane { + SplitView.preferredWidth: 300 + + } +} + +// category: Panel diff --git a/ui/imports/shared/panels/AnimatedText.qml b/ui/StatusQ/src/StatusQ/Controls/StatusColorAnimation.qml similarity index 50% rename from ui/imports/shared/panels/AnimatedText.qml rename to ui/StatusQ/src/StatusQ/Controls/StatusColorAnimation.qml index 00e5b41cb41..93ab12bd877 100644 --- a/ui/imports/shared/panels/AnimatedText.qml +++ b/ui/StatusQ/src/StatusQ/Controls/StatusColorAnimation.qml @@ -2,6 +2,36 @@ import QtQuick 2.15 import StatusQ.Core.Theme 0.1 +/*! + \qmltype StatusColorAnimation + \inherits SequentialAnimation + \inqmlmodule StatusQ.Controls + \since StatusQ.Controls 0.1 + \brief Animates target property (that shold be a color property) from/to color to target component within set duration. + + Example of how to use it: + + \qml + StatusBaseText { + id: animatedText + + onTextChanged: { + if (text === "") { + return + } + animate.restart() + } + + StatusColorAnimation { + id: animate + target: animatedText + } + } + \endqml + + For a list of components available see StatusQ. + */ + SequentialAnimation { id: root diff --git a/ui/StatusQ/src/StatusQ/Controls/StatusFeeOption.qml b/ui/StatusQ/src/StatusQ/Controls/StatusFeeOption.qml new file mode 100644 index 00000000000..f41406518ce --- /dev/null +++ b/ui/StatusQ/src/StatusQ/Controls/StatusFeeOption.qml @@ -0,0 +1,233 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 + +import StatusQ.Core 0.1 +import StatusQ.Components 0.1 +import StatusQ.Core.Theme 0.1 + +/*! + \qmltype StatusFeeOption + \inherits Control + \inqmlmodule StatusQ.Controls + \since StatusQ.Controls 0.1 + \brief Displays a clickable option which content is fully customizable + + By design appearance of subText and additionalText as well as their values should be set from outside. + If any of those two need to be displayed a component that has an empty value will be rendering a loading state. + The control renders based on the selected property. + When control is clicked clicked signal will be emitted. + + Example of how to use it: + + \qml + StatusFeeOption { + subText: "1.65 EUR" + showSubText: true + + additionalText: "~40s" + showAdditionalText: true + + onSelectedChanged: { + // this option is selected/unselected + } + } + \endqml + + For a list of components available see StatusQ. + */ + +Control { + id: root + + enum Type { + Normal, + Fast, + Urgent, + Custom + } + + property int type: StatusFeeOption.Type.Normal + + property bool selected: false + + property string mainText: { + switch(type) { + case StatusFeeOption.Type.Fast: + return qsTr("Normal") + case StatusFeeOption.Type.Urgent: + return qsTr("Urgent") + case StatusFeeOption.Type.Custom: + return qsTr("Custom") + case StatusFeeOption.Type.Normal: + default: + return qsTr("Normal") + } + } + + property string subText + property bool showSubText + + property string additionalText + property bool showAdditionalText + + property string unselectedText + + property string icon: { + switch(type) { + case StatusFeeOption.Type.Fast: + return Theme.png("wallet/car") + case StatusFeeOption.Type.Urgent: + return Theme.png("wallet/rocket") + case StatusFeeOption.Type.Custom: + return Theme.png("wallet/handwrite") + case StatusFeeOption.Type.Normal: + default: + return Theme.png("wallet/clock") + } + } + + signal clicked() + + font.family: Theme.baseFont.name + font.pixelSize: 12 + + horizontalPadding: 8 + verticalPadding: 8 + + component AnimatedText: StatusBaseText { + id: animatedText + verticalAlignment: Qt.AlignVCenter + font.family: root.font.family + font.pixelSize: root.font.pixelSize + wrapMode: Text.WordWrap + elide: Text.ElideRight + + onTextChanged: { + if (text === "") { + return + } + animate.restart() + } + + + StatusColorAnimation { + id: animate + target: animatedText + fromColor: animatedText.color + } + } + + property Component subTextComponent: AnimatedText { + text: root.subText + } + + property Component additionalTextComponent: AnimatedText { + text: root.additionalText + color: Theme.palette.baseColor1 + } + + property Component unselectedTextComponent: StatusBaseText { + verticalAlignment: Qt.AlignVCenter + text: root.unselectedText + color: Theme.palette.baseColor1 + font.family: root.font.family + font.pixelSize: root.font.pixelSize + wrapMode: Text.WordWrap + elide: Text.ElideRight + } + + property Component loaderComponent: LoadingComponent { + radius: 4 + height: root.font.pixelSize + } + + background: Rectangle { + id: background + implicitHeight: 84 + implicitWidth: 101 + radius: 8 + border.width: 1 + border.color: root.selected? Theme.palette.primaryColor1 : Theme.palette.baseColor2 + color: { + if (root.hovered) { + return Theme.palette.baseColor2 + } + + if (root.selected) { + return Theme.palette.alphaColor(Theme.palette.baseColor2, 0.1) + } + + return Theme.palette.statusAppLayout.backgroundColor + } + + MouseArea { + id: mouseArea + anchors.fill: parent + cursorShape: root.hovered? Qt.PointingHandCursor : undefined + onClicked: root.clicked() + } + } + + contentItem: ColumnLayout { + spacing: 4 + + RowLayout { + + Layout.preferredWidth: parent.width + Layout.preferredHeight: Math.max(mText.height, image.height) + + spacing: 4 + + StatusBaseText { + id: mText + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + verticalAlignment: Qt.AlignVCenter + text: root.mainText + font.family: root.font.family + font.pixelSize: root.font.pixelSize + wrapMode: Text.WordWrap + elide: Text.ElideRight + } + + StatusImage { + id: image + Layout.alignment: Qt.AlignVCenter + visible: root.selected || root.hovered + width: 22 + height: 22 + source: root.icon + } + } + + Item { + id: spacer + Layout.fillWidth: true + Layout.preferredHeight: 8 + (unselectedTextLoader.visible? parent.spacing : 0) + } + + Loader { + visible: root.showSubText + Layout.preferredWidth: parent.width + Layout.alignment: Qt.AlignVCenter + sourceComponent: !!root.subText? subTextComponent : loaderComponent + } + + Loader { + visible: root.showAdditionalText + Layout.preferredWidth: parent.width + Layout.alignment: Qt.AlignVCenter + sourceComponent: !!root.additionalText? additionalTextComponent : loaderComponent + } + + Loader { + id: unselectedTextLoader + visible: !root.selected && !!root.unselectedText + Layout.preferredWidth: parent.width + Layout.fillHeight: true + Layout.alignment: Qt.AlignVCenter + sourceComponent: visible? unselectedTextComponent : loaderComponent + } + } +} diff --git a/ui/StatusQ/src/StatusQ/Controls/StatusInput.qml b/ui/StatusQ/src/StatusQ/Controls/StatusInput.qml index 6dddd5c015c..fd76abdc809 100644 --- a/ui/StatusQ/src/StatusQ/Controls/StatusInput.qml +++ b/ui/StatusQ/src/StatusQ/Controls/StatusInput.qml @@ -96,6 +96,22 @@ Item { \endqml */ property alias errorMessageCmp: errorMessage + /*! + \qmlproperty bottomLabelMessageCmp + This property represents the bottomLabelMessage shown on statusInput on the right of errorMessageCmp. + By default this component is hidden and doesn't have any text set, once the text is set it will become visible. + + Examples of usage + + \qml + StatusInput { + bottomLabelMessageCmp.text: "some text" + bottomLabelMessageCmp.font.pixelSize: 15 + bottomLabelMessageCmp.font.weight: Font.Medium + } + \endqml + */ + property alias bottomLabelMessageCmp: bottomLabelMessage /*! \qmlproperty int StatusInput::labelPadding This property sets the padding of the label text. @@ -111,6 +127,21 @@ Item { This property sets the secondary label text. */ property string secondaryLabel: "" + /*! + \qmlproperty string StatusInput::labelIcon + This property sets the icon displayd on the right of the label. + */ + property string labelIcon: "" + /*! + \qmlproperty string StatusInput::labelIconColor + This property sets the color of the label icon. + */ + property string labelIconColor: Theme.palette.baseColor1 + /*! + \qmlproperty string StatusInput::labelIconClickable + This property sets if the label icon is clickable or not, if clickable labelIconClicked signal will be emitted. + */ + property bool labelIconClickable: false /*! \qmlproperty int StatusInput::charLimit This property sets the character limit of the text input. @@ -205,6 +236,11 @@ Item { This signal is emitted when the icon is clicked. */ signal iconClicked() + /*! + \qmlsignal + This signal is emitted when the label icon is clicked. + */ + signal labelIconClicked() /*! \qmlsignal This signal is emitted when a hard key is pressed passing as parameter the keyboard event. @@ -428,6 +464,23 @@ Item { color: Theme.palette.baseColor1 } + StatusIcon { + id: labelIcon + visible: !!root.labelIcon + width: 16 + height: 16 + icon: root.labelIcon + color: root.labelIconColor + + MouseArea { + anchors.fill: parent + enabled: root.labelIconClickable + hoverEnabled: root.labelIconClickable + cursorShape: containsMouse ? Qt.PointingHandCursor : Qt.ArrowCursor + onClicked: root.labelIconClicked() + } + } + Item { Layout.fillWidth: true } @@ -467,24 +520,34 @@ Item { } } - StatusBaseText { - id: errorMessage - visible: { - if (!text) - return false; + RowLayout { + id: bottomRow + Layout.topMargin: 8 + Layout.fillWidth: true + + StatusBaseText { + id: errorMessage + Layout.fillWidth: true + visible: { + if (!text) + return false; - if ((root.validationMode === StatusInput.ValidationMode.OnlyWhenDirty && statusBaseInput.dirty) || - root.validationMode === StatusInput.ValidationMode.Always) - return !statusBaseInput.valid; + if ((root.validationMode === StatusInput.ValidationMode.OnlyWhenDirty && statusBaseInput.dirty) || + root.validationMode === StatusInput.ValidationMode.Always) + return !statusBaseInput.valid; - return false; + return false; + } + font.pixelSize: 12 + color: Theme.palette.dangerColor1 + wrapMode: Text.WordWrap + horizontalAlignment: Text.AlignRight + } + + StatusBaseText { + id: bottomLabelMessage + visible: !!text } - font.pixelSize: 12 - color: Theme.palette.dangerColor1 - wrapMode: Text.WordWrap - horizontalAlignment: Text.AlignRight - Layout.topMargin: 8 - Layout.fillWidth: true } } } diff --git a/ui/StatusQ/src/StatusQ/Controls/qmldir b/ui/StatusQ/src/StatusQ/Controls/qmldir index 0d5757d3418..548cbb88a07 100644 --- a/ui/StatusQ/src/StatusQ/Controls/qmldir +++ b/ui/StatusQ/src/StatusQ/Controls/qmldir @@ -14,12 +14,14 @@ StatusChatListCategoryItemButton 0.1 StatusChatListCategoryItemButton.qml StatusCheckBox 0.1 StatusCheckBox.qml StatusCircularProgressBar 0.1 StatusCircularProgressBar.qml StatusClearButton 0.1 StatusClearButton.qml +StatusColorAnimation 0.1 StatusColorAnimation.qml StatusColorRadioButton 0.1 StatusColorRadioButton.qml StatusColorSelector 0.1 StatusColorSelector.qml StatusColorSelectorGrid 0.1 StatusColorSelectorGrid.qml StatusComboBox 0.1 StatusComboBox.qml StatusCommunityTag 0.1 StatusCommunityTag.qml StatusDropdown 0.1 StatusDropdown.qml +StatusFeeOption 0.1 StatusFeeOption.qml StatusFlatButton 0.1 StatusFlatButton.qml StatusFlatRoundButton 0.1 StatusFlatRoundButton.qml StatusIconSwitch 0.1 StatusIconSwitch.qml diff --git a/ui/StatusQ/src/StatusQ/Popups/Dialog/StatusDialogHeader.qml b/ui/StatusQ/src/StatusQ/Popups/Dialog/StatusDialogHeader.qml index ce11c13ece4..74f427644ab 100644 --- a/ui/StatusQ/src/StatusQ/Popups/Dialog/StatusDialogHeader.qml +++ b/ui/StatusQ/src/StatusQ/Popups/Dialog/StatusDialogHeader.qml @@ -14,6 +14,13 @@ Rectangle { property alias leftComponent: leftComponentLoader.sourceComponent + property bool internalPopupActive + property color internalOverlayColor + property int popupFullHeight + property Component internalPopupComponent + + signal closeInternalPopup() + color: Theme.palette.statusModal.backgroundColor radius: 8 @@ -73,4 +80,29 @@ Rectangle { samples: 37 color: Theme.palette.dropShadow } + + Rectangle { + id: internalOverlay + anchors.fill: parent + anchors.bottomMargin: -1 * root.popupFullHeight + root.height + visible: root.internalPopupActive + radius: root.radius + color: root.internalOverlayColor + + MouseArea { + anchors.fill: parent + anchors.bottomMargin: popupLoader.height + onClicked: { + root.closeInternalPopup() + } + } + } + + Loader { + id: popupLoader + anchors.bottom: parent.bottom + anchors.bottomMargin: internalOverlay.anchors.bottomMargin + active: root.internalPopupActive + sourceComponent: root.internalPopupComponent + } } diff --git a/ui/StatusQ/src/assets.qrc b/ui/StatusQ/src/assets.qrc index 669bc78b74d..8a8a5bb818c 100644 --- a/ui/StatusQ/src/assets.qrc +++ b/ui/StatusQ/src/assets.qrc @@ -281,6 +281,7 @@ assets/img/icons/security.svg assets/img/icons/seed-phrase.svg assets/img/icons/send.svg + assets/img/icons/settings-advance.svg assets/img/icons/settings-advanced.svg assets/img/icons/settings.svg assets/img/icons/share-android.svg @@ -8995,8 +8996,12 @@ assets/png/traffic_lights/maximize_pressed.png assets/png/traffic_lights/minimise.png assets/png/traffic_lights/minimise_pressed.png - assets/png/wallet/wallet-green.png + assets/png/wallet/car.png + assets/png/wallet/clock.png assets/png/wallet/flying-coin.png + assets/png/wallet/handwrite.png + assets/png/wallet/rocket.png + assets/png/wallet/wallet-green.png assets/png/appearance-dark.png assets/png/appearance-light.png assets/png/appearance-system.png diff --git a/ui/StatusQ/src/assets/img/icons/settings-advance.svg b/ui/StatusQ/src/assets/img/icons/settings-advance.svg new file mode 100644 index 00000000000..6aca648d3c2 --- /dev/null +++ b/ui/StatusQ/src/assets/img/icons/settings-advance.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/ui/StatusQ/src/assets/png/wallet/car.png b/ui/StatusQ/src/assets/png/wallet/car.png new file mode 100644 index 00000000000..bb2d81a39ac Binary files /dev/null and b/ui/StatusQ/src/assets/png/wallet/car.png differ diff --git a/ui/StatusQ/src/assets/png/wallet/clock.png b/ui/StatusQ/src/assets/png/wallet/clock.png new file mode 100644 index 00000000000..842d9bfcc9d Binary files /dev/null and b/ui/StatusQ/src/assets/png/wallet/clock.png differ diff --git a/ui/StatusQ/src/assets/png/wallet/handwrite.png b/ui/StatusQ/src/assets/png/wallet/handwrite.png new file mode 100644 index 00000000000..217b2a7ac80 Binary files /dev/null and b/ui/StatusQ/src/assets/png/wallet/handwrite.png differ diff --git a/ui/StatusQ/src/assets/png/wallet/rocket.png b/ui/StatusQ/src/assets/png/wallet/rocket.png new file mode 100644 index 00000000000..0c9915aae68 Binary files /dev/null and b/ui/StatusQ/src/assets/png/wallet/rocket.png differ diff --git a/ui/StatusQ/src/statusq.qrc b/ui/StatusQ/src/statusq.qrc index 1379cbb10e8..0eaf6e9cab0 100644 --- a/ui/StatusQ/src/statusq.qrc +++ b/ui/StatusQ/src/statusq.qrc @@ -104,12 +104,14 @@ StatusQ/Controls/StatusCheckBox.qml StatusQ/Controls/StatusCircularProgressBar.qml StatusQ/Controls/StatusClearButton.qml + StatusQ/Controls/StatusColorAnimation.qml StatusQ/Controls/StatusColorRadioButton.qml StatusQ/Controls/StatusColorSelector.qml StatusQ/Controls/StatusColorSelectorGrid.qml StatusQ/Controls/StatusComboBox.qml StatusQ/Controls/StatusCommunityTag.qml StatusQ/Controls/StatusDropdown.qml + StatusQ/Controls/StatusFeeOption.qml StatusQ/Controls/StatusFlatButton.qml StatusQ/Controls/StatusFlatRoundButton.qml StatusQ/Controls/StatusIconSwitch.qml diff --git a/ui/app/AppLayouts/Wallet/popups/SignTransactionModalBase.qml b/ui/app/AppLayouts/Wallet/popups/SignTransactionModalBase.qml index a81e79e8667..dc8fdd1ac2b 100644 --- a/ui/app/AppLayouts/Wallet/popups/SignTransactionModalBase.qml +++ b/ui/app/AppLayouts/Wallet/popups/SignTransactionModalBase.qml @@ -100,6 +100,12 @@ StatusDialog { default property alias contents: contentsLayout.data + property bool internalPopupActive: false + property color internalOverlayColor: Theme.palette.backdropColor + property Component internalPopupComponent + + signal closeInternalPopup() + width: 480 padding: 0 @@ -117,6 +123,13 @@ StatusDialog { actions.closeButton.onClicked: root.close() leftComponent: root.headerIconComponent + + internalPopupActive: root.internalPopupActive + internalOverlayColor: root.internalOverlayColor + popupFullHeight: root.height + internalPopupComponent: root.internalPopupComponent + + onCloseInternalPopup: root.closeInternalPopup() } footer: StatusDialogFooter { diff --git a/ui/app/AppLayouts/Wallet/popups/simpleSend/SendSignModal.qml b/ui/app/AppLayouts/Wallet/popups/simpleSend/SendSignModal.qml index 89483243043..84c756864f8 100644 --- a/ui/app/AppLayouts/Wallet/popups/simpleSend/SendSignModal.qml +++ b/ui/app/AppLayouts/Wallet/popups/simpleSend/SendSignModal.qml @@ -9,6 +9,7 @@ import StatusQ.Controls 0.1 import StatusQ.Components 0.1 import AppLayouts.Wallet.panels 1.0 +import AppLayouts.Wallet.views 1.0 import AppLayouts.Wallet.popups 1.0 import utils 1.0 @@ -166,8 +167,28 @@ SignTransactionModalBase { loading: root.feesLoading } } + StatusFlatButton { + tooltip.text: qsTr("Edit transaction settings") + icon.name: "settings-advance" + textColor: hovered? Theme.palette.directColor1 : Theme.palette.baseColor1 + onClicked: { + root.internalPopupActive = true + } + } + } + } + + property Component internalPopup: TransactionSettings { + + onCancelClicked: { + root.internalPopupActive = false } } + internalPopupComponent: internalPopup + + onCloseInternalPopup: { + root.internalPopupActive = false + } // Send Asset SignInfoBox { @@ -196,6 +217,7 @@ SignTransactionModalBase { } ] visible: !root.isCollectible + enabled: !root.internalPopupActive } // Send Collectible @@ -222,6 +244,7 @@ SignTransactionModalBase { onOpenLink: (link) => root.openLinkWithConfirmation(link) } visible: root.isCollectible + enabled: !root.internalPopupActive } // From @@ -263,6 +286,7 @@ SignTransactionModalBase { onOpenLink: (link) => root.openLinkWithConfirmation(link) } ] + enabled: !root.internalPopupActive } // Network diff --git a/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml b/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml index 0dedca2df9e..578e9420008 100644 --- a/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml +++ b/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml @@ -427,7 +427,7 @@ StatusDialog { font.weight: Font.Medium loading: root.swapAdaptor.swapProposalLoading - AnimatedText { + StatusColorAnimation { id: animation target: fees } diff --git a/ui/app/AppLayouts/Wallet/views/TransactionSettings.qml b/ui/app/AppLayouts/Wallet/views/TransactionSettings.qml new file mode 100644 index 00000000000..36a7acde990 --- /dev/null +++ b/ui/app/AppLayouts/Wallet/views/TransactionSettings.qml @@ -0,0 +1,294 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Controls 2.15 + +import StatusQ.Core 0.1 +import StatusQ.Controls 0.1 +import StatusQ.Core.Theme 0.1 + +import shared.controls 1.0 +import shared.popups 1.0 + +Rectangle { + id: root + + property string currentBaseFee + property string currentSuggestedMinPriorityFee + property string currentSuggestedMaxPriorityFee + property string currentGasAmount + property int currentNonce + + property alias normalPrice: optionNormal.subText + property alias normalTime: optionNormal.additionalText + + property alias fastPrice: optionFast.subText + property alias fastTime: optionFast.additionalText + + property alias urgentPrice: optionUrgent.subText + property alias urgentTime: optionUrgent.additionalText + + property alias customPrice: optionCustom.subText + property alias customTime: optionCustom.additionalText + property alias customBaseFee: customBaseFee.text + property alias customPriorityFee: customPriorityFee.text + property alias customGasAmount: customGasAmount.text + property alias customNonce: customNonce.text + + property int selectedFeeMode + + signal confirmClicked() + signal cancelClicked() + + color: Theme.palette.statusModal.backgroundColor + radius: 8 + + implicitHeight: layout.implicitHeight + implicitWidth: layout.implicitWidth + + QtObject { + id: d + + readonly property bool customMode: root.selectedFeeMode === StatusFeeOption.Type.Custom + + function showAlert(title, text, note, url) { + infoBox.title = title + infoBox.text = text + infoBox.note = note + infoBox.url = url + infoBox.active = true + } + } + + focus: true + + Keys.onReleased: { + if (event.key === Qt.Key_Escape) { + root.cancelClicked() + } + } + + Component.onCompleted: root.forceActiveFocus() + + Loader { + id: infoBox + anchors.centerIn: root + active: false + + property string title + property string text + property string note + property string url + + sourceComponent: AlertPopup { + title: infoBox.title + + width: root.width - 2 * 20 + + acceptBtnText: qsTr("Got it") + cancelBtn.text: !!infoBox.url? qsTr("Read more") : "" + cancelBtn.icon.name: "external-link" + cancelBtn.visible: !!infoBox.url + + alertLabel.text: infoBox.text + alertNote.visible: !!infoBox.note + alertNote.text: infoBox.note + alertNote.color: Theme.palette.baseColor1 + + onCancelClicked: { + Qt.openUrlExternally(infoBox.url) + } + + onClosed: { + infoBox.active = false + } + } + + onLoaded: { + infoBox.item.open() + } + } + + ColumnLayout { + id: layout + + ColumnLayout { + Layout.margins: 20 + + spacing: 16 + + StatusBaseText { + Layout.preferredWidth: parent.width + text: qsTr("Transaction settings") + font.pixelSize: 17 + font.bold: true + elide: Text.ElideMiddle + } + + RowLayout { + id: options + spacing: 12 + + StatusFeeOption { + id: optionNormal + type: StatusFeeOption.Type.Normal + selected: root.selectedFeeMode === StatusFeeOption.Type.Normal + showSubText: true + showAdditionalText: true + + onClicked: root.selectedFeeMode = StatusFeeOption.Type.Normal + } + + StatusFeeOption { + id: optionFast + type: StatusFeeOption.Type.Fast + selected: root.selectedFeeMode === StatusFeeOption.Type.Fast + showSubText: true + showAdditionalText: true + + onClicked: root.selectedFeeMode = StatusFeeOption.Type.Fast + } + + StatusFeeOption { + id: optionUrgent + type: StatusFeeOption.Type.Urgent + selected: root.selectedFeeMode === StatusFeeOption.Type.Urgent + showSubText: true + showAdditionalText: true + + onClicked: root.selectedFeeMode = StatusFeeOption.Type.Urgent + } + + StatusFeeOption { + id: optionCustom + type: StatusFeeOption.Type.Custom + selected: root.selectedFeeMode === StatusFeeOption.Type.Custom + showSubText: !!selected + showAdditionalText: !!selected + unselectedText: "Set your own fees & nonce" + + onClicked: root.selectedFeeMode = StatusFeeOption.Type.Custom + } + } + + StatusBaseText { + Layout.preferredWidth: parent.width + visible: !d.customMode + text: qsTr("Increased base and priority fee, incentivising miners to confirm more quickly") + color: Theme.palette.baseColor1 + font.pixelSize: Theme.tertiaryTextFontSize + elide: Text.ElideMiddle + } + + ShapeRectangle { + Layout.preferredWidth: parent.width + Layout.preferredHeight: customLayout.height + customLayout.anchors.margins + visible: d.customMode + + ColumnLayout { + id: customLayout + anchors.left: parent.left + anchors.margins: 20 + width: parent.width - 2 * anchors.margins + spacing: 16 + + StatusInput { + id: customBaseFee + Layout.preferredWidth: parent.width + Layout.topMargin: 20 + label: qsTr("Max base fee") + labelIcon: "info" + labelIconColor: Theme.palette.baseColor1 + labelIconClickable: true + errorMessageCmp.visible: true + errorMessageCmp.color: Theme.palette.baseColor1 + errorMessageCmp.text: qsTr("Current: %1 GWEI").arg(root.currentBaseFee) + errorMessageCmp.horizontalAlignment: Text.AlignLeft + bottomLabelMessageCmp.text: qsTr("0.0031 ETH") + rightPadding: leftPadding + input.rightComponent: StatusBaseText { + text: "GWEI" + color: Theme.palette.baseColor1 + } + + onLabelIconClicked: d.showAlert(label, + qsTr("When your transaction gets included in the block, any difference between your max base fee and the actual base fee will be refunded.\n"), + qsTr("Note: the ETH amount shown for this value is calculated:\nMax base fee (in GWEI) * Max gas amount"), + "") + } + + StatusInput { + id: customPriorityFee + Layout.preferredWidth: parent.width + label: qsTr("Priority fee") + labelIcon: "info" + labelIconColor: Theme.palette.baseColor1 + labelIconClickable: true + errorMessageCmp.visible: true + errorMessageCmp.color: Theme.palette.baseColor1 + errorMessageCmp.text: qsTr("Current: %1 - %2 GWEI").arg(root.currentSuggestedMinPriorityFee).arg(root.currentSuggestedMaxPriorityFee) + errorMessageCmp.horizontalAlignment: Text.AlignLeft + bottomLabelMessageCmp.text: qsTr("0.0031 ETH") + rightPadding: leftPadding + input.rightComponent: StatusBaseText { + text: "GWEI" + color: Theme.palette.baseColor1 + } + + onLabelIconClicked: d.showAlert(label, + qsTr("AKA miner tip. A voluntary fee you can add to incentivise miners or validators to prioritise your transaction.\n\nThe higher the tip, the faster your transaction is likely to be processed, especially curing periods of higher network congestion.\n"), + qsTr("Note: the ETH amount shown for this value is calculated: Priority fee (in GWEI) * Max gas amount"), + "") + } + + StatusInput { + id: customGasAmount + Layout.preferredWidth: parent.width + label: qsTr("Max gas amount") + labelIcon: "info" + labelIconColor: Theme.palette.baseColor1 + labelIconClickable: true + errorMessageCmp.visible: true + errorMessageCmp.color: Theme.palette.baseColor1 + errorMessageCmp.text: qsTr("Current: %1").arg(root.currentGasAmount) + errorMessageCmp.horizontalAlignment: Text.AlignLeft + rightPadding: leftPadding + input.rightComponent: StatusBaseText { + text: "UNITS" + color: Theme.palette.baseColor1 + } + + onLabelIconClicked: d.showAlert(qsTr("Gas amount"), + qsTr("AKA gas limit. Refers to the maximum number of computational steps (or units of gas) that a transaction can consume. It represents the complexity or amount of work required to execute a transaction or smart contract.\n\nThe gas limit is a cap on how much work the transaction can do on the blockchain. If the gas limit is set too low, the transaction may fail due to insufficient gas."), + "", + "") + } + + StatusInput { + id: customNonce + Layout.preferredWidth: parent.width + label: qsTr("Nonce") + labelIcon: "info" + labelIconColor: Theme.palette.baseColor1 + labelIconClickable: true + errorMessageCmp.visible: true + errorMessageCmp.color: Theme.palette.baseColor1 + errorMessageCmp.text: qsTr("Last transaction: %1").arg(root.currentNonce) + errorMessageCmp.horizontalAlignment: Text.AlignLeft + rightPadding: leftPadding + + onLabelIconClicked: d.showAlert(label, + qsTr("Transaction counter ensuring transactions from your account are processed in the correct order and can’t be replayed. Each new transaction increments the nonce by 1, ensuring uniqueness and preventing double-spending.\n\nIf a transaction with a lower nonce is pending, higher nonce transactions will remain in the queue until the earlier one is confirmed."), + "", + "") + } + } + } + + StatusButton { + Layout.preferredWidth: parent.width + text: qsTr("Confirm") + onClicked: root.confirmClicked() + } + } + } +} diff --git a/ui/app/AppLayouts/Wallet/views/qmldir b/ui/app/AppLayouts/Wallet/views/qmldir index 12ef9ae06c1..aa6fae11b2b 100644 --- a/ui/app/AppLayouts/Wallet/views/qmldir +++ b/ui/app/AppLayouts/Wallet/views/qmldir @@ -8,3 +8,4 @@ TokenSelectorSectionDelegate 1.0 TokenSelectorSectionDelegate.qml AccountContextMenu 1.0 AccountContextMenu.qml RecipientView 1.0 RecipientView.qml SendModalFooter 1.0 SendModalFooter.qml +TransactionSettings 1.0 TransactionSettings.qml \ No newline at end of file diff --git a/ui/imports/shared/panels/qmldir b/ui/imports/shared/panels/qmldir index 34dc96c8542..ac9137b100d 100644 --- a/ui/imports/shared/panels/qmldir +++ b/ui/imports/shared/panels/qmldir @@ -18,4 +18,3 @@ Separator 1.0 Separator.qml SequenceColumnLayout 1.0 SequenceColumnLayout.qml StatusAssetSelector 1.0 StatusAssetSelector.qml StyledText 1.0 StyledText.qml -AnimatedText 1.0 AnimatedText.qml diff --git a/ui/imports/shared/popups/AlertPopup.qml b/ui/imports/shared/popups/AlertPopup.qml index 5930996bb9f..b5392285619 100644 --- a/ui/imports/shared/popups/AlertPopup.qml +++ b/ui/imports/shared/popups/AlertPopup.qml @@ -15,8 +15,11 @@ StatusDialog { id: root property alias acceptBtnText: acceptBtn.text + property alias acceptBtn: acceptBtn + property alias cancelBtn: cancelBtn property alias alertText: contentTextItem.text property alias alertLabel: contentTextItem + property alias alertNote: contentNoteItem property int acceptBtnType: StatusBaseButton.Type.Danger property StatusAssetSettings asset: StatusAssetSettings { @@ -36,12 +39,22 @@ StatusDialog { implicitWidth: 400 // by design topPadding: Theme.padding bottomPadding: topPadding - contentItem: StatusBaseText { - id: contentTextItem - - font.pixelSize: Theme.primaryTextFontSize - wrapMode: Text.WordWrap - lineHeight: 1.2 + contentItem: Column { + StatusBaseText { + id: contentTextItem + width: parent.width + font.pixelSize: Theme.primaryTextFontSize + wrapMode: Text.WordWrap + lineHeight: 1.2 + } + StatusBaseText { + id: contentNoteItem + visible: false + width: parent.width + font.pixelSize: Theme.primaryTextFontSize + wrapMode: Text.WordWrap + lineHeight: 1.2 + } } header: StatusDialogHeader { @@ -61,6 +74,7 @@ StatusDialog { rightButtons: ObjectModel { StatusButton { + id: cancelBtn text: qsTr("Cancel") normalColor: "transparent" @@ -75,6 +89,8 @@ StatusDialog { type: root.acceptBtnType + Component.onCompleted: acceptBtn.forceActiveFocus() + onClicked: { root.acceptClicked() close() diff --git a/ui/imports/shared/popups/send/views/TransactionModalFooter.qml b/ui/imports/shared/popups/send/views/TransactionModalFooter.qml index 50282ceac2d..73787d7e470 100644 --- a/ui/imports/shared/popups/send/views/TransactionModalFooter.qml +++ b/ui/imports/shared/popups/send/views/TransactionModalFooter.qml @@ -52,7 +52,7 @@ StatusDialogFooter { estimatedTimeAnimation.restart() } - AnimatedText { + StatusColorAnimation { id: estimatedTimeAnimation target: estimatedTime } @@ -80,7 +80,7 @@ StatusDialogFooter { feesAnimation.restart() } - AnimatedText { + StatusColorAnimation { id: feesAnimation target: fees } diff --git a/ui/imports/shared/popups/walletconnect/DAppSignRequestModal.qml b/ui/imports/shared/popups/walletconnect/DAppSignRequestModal.qml index b9670a6fb86..17fb93dd320 100644 --- a/ui/imports/shared/popups/walletconnect/DAppSignRequestModal.qml +++ b/ui/imports/shared/popups/walletconnect/DAppSignRequestModal.qml @@ -118,7 +118,7 @@ SignTransactionModalBase { maxFeesAnimation.restart() } - AnimatedText { + StatusColorAnimation { id: maxFeesAnimation target: maxFees targetProperty: "customColor" @@ -148,7 +148,7 @@ SignTransactionModalBase { estimatedTimeAnimation.restart() } - AnimatedText { + StatusColorAnimation { id: estimatedTimeAnimation target: estimatedTime targetProperty: "customColor" @@ -224,7 +224,7 @@ SignTransactionModalBase { fiatFeesAnimation.restart() } - AnimatedText { + StatusColorAnimation { id: fiatFeesAnimation target: fiatFees targetProperty: "customColor" @@ -250,7 +250,7 @@ SignTransactionModalBase { cryptoFeesAnimation.restart() } - AnimatedText { + StatusColorAnimation { id: cryptoFeesAnimation target: cryptoFees fromColor: cryptoFees.customColor