diff --git a/ui/StatusQ/doc/src/images/status_card.png b/ui/StatusQ/doc/src/images/status_card.png new file mode 100644 index 0000000000..17f871cbd7 Binary files /dev/null and b/ui/StatusQ/doc/src/images/status_card.png differ diff --git a/ui/StatusQ/sandbox/main.qml b/ui/StatusQ/sandbox/main.qml index 1d4a4b5321..17a22e88d4 100644 --- a/ui/StatusQ/sandbox/main.qml +++ b/ui/StatusQ/sandbox/main.qml @@ -336,6 +336,11 @@ StatusWindow { selected: viewLoader.source.toString().includes(title) onClicked: mainPageView.page(title, true); } + StatusNavigationListItem { + title: "StatusCard" + selected: viewLoader.source.toString().includes(title) + onClicked: mainPageView.page(title, true); + } } } } diff --git a/ui/StatusQ/sandbox/pages/StatusCardPage.qml b/ui/StatusQ/sandbox/pages/StatusCardPage.qml new file mode 100644 index 0000000000..9ecdb538df --- /dev/null +++ b/ui/StatusQ/sandbox/pages/StatusCardPage.qml @@ -0,0 +1,253 @@ +import QtQuick 2.14 +import QtQuick.Controls 2.14 +import QtQuick.Layouts 1.14 + +import Sandbox 0.1 +import StatusQ.Core 0.1 +import StatusQ.Core.Utils 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Controls 0.1 +import StatusQ.Popups 0.1 +import StatusQ.Components 0.1 + +Item { + + ColumnLayout { + id: layout + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + spacing: 20 + + RowLayout { + StatusSelect { + id: select + label: "Select Card State" + model: ListModel { + ListElement { + name: "default" + } + ListElement { + name: "unavailable" + } + ListElement { + name: "error" + } + ListElement { + name: "unpreferred" + } + } + selectMenu.delegate: StatusMenuItemDelegate { + statusPopupMenu: select + action: StatusMenuItem { + text: name + onTriggered: { + selectedItem.text = name + card.state = name + } + } + } + selectedItemComponent: Item { + id: selectedItem + anchors.fill: parent + property string text: "default" + + StatusBaseText { + text: selectedItem.text + anchors.centerIn: parent + color: Theme.palette.directColor1 + } + } + } + + StatusCheckBox { + text: "advancedMode" + onClicked: { + card.advancedMode = checked + } + } + } + + StatusCard { + id: card + primaryText: "Mainnet" + secondaryText: state === "unavailable" ? "No Gas" : "75" + tertiaryText: state === "unpreferred" ? "UNPREFERRED" : "BALANCE: " + 250 + cardIconName: "status" + advancedInputText: "75" + disabledText: "Disabled" + } + + Rectangle { + height: 1 + width: 700 + color: "black" + } + + // Below is an example on how to implement the network routing using StatusCard and Canvas, also the function in Utils to draw an arrow + Row { + id: cards + spacing: 200 + Column { + id: leftColumn + spacing: 20 + Repeater { + model: fromNetworksList + StatusCard { + primaryText: name + secondaryText: balance === 0 ? "No Balance" : !hasGas ? "No Gas" : tokensToSend + tertiaryText: "BALANCE: " + balance + state: balance === 0 || !hasGas ? "unavailable" : "default" + cardIconName: iconName + advancedMode: card.advancedMode + advancedInputText: tokensToSend + disabledText: "Disabled" + } + } + } + + Column { + id: rightColumn + spacing: 20 + Repeater { + model: toNetworksList + StatusCard { + primaryText: name + secondaryText: tokensToReceive + tertiaryText: "" + state: preferred ? "default" : "unprefeered" + cardIconName: iconName + opacity: preferred ? 1 : 0 + advancedMode: card.advancedMode + advancedInputText: tokensToReceive + disabledText: "Disabled" + } + } + } + } + } + + Canvas { + id: canvas + x: layout.x + leftColumn.x + y: cards.y + width: cards.width + height: cards.height + + function clear() { + var ctx = getContext("2d"); + ctx.reset() + } + + onPaint: { + // Get the canvas context + var ctx = getContext("2d"); + + for(var i = 0; i< fromNetworksList.count; i++) { + if(fromNetworksList.get(i).routedTo !== "") { + for(var j = 0; j< toNetworksList.count; j++) { + if(fromNetworksList.get(i).routedTo === toNetworksList.get(j).name) { + Utils.drawArrow(ctx, leftColumn.children[i].x + leftColumn.children[i].width, + leftColumn.children[i].y + leftColumn.children[i].height/2, + rightColumn.x + rightColumn.children[j].x, + rightColumn.children[j].y + rightColumn.children[j].height/2, + '#627EEA') + } + } + } + } + } + } + + + ListModel { + id: toNetworksList + ListElement { + name: "Mainnet" + iconName: "status" + tokensToReceive: 75 + preferred: true + } + ListElement { + name: "Aztec" + iconName: "status" + tokensToReceive: 0 + preferred: false + } + ListElement { + name: "Hermez" + iconName: "status" + tokensToReceive: 75 + preferred: true + } + ListElement { + name: "Loppring" + iconName: "status" + tokensToReceive: 0 + preferred: true + } + ListElement { + name: "Optimism" + iconName: "status" + tokensToReceive: 100 + preferred: true + } + ListElement { + name: "zkSync" + iconName: "status" + tokensToReceive: 0 + preferred: false + } + } + + ListModel { + id: fromNetworksList + ListElement { + name: "Mainnet" + iconName: "status" + tokensToSend: 75 + balance: 75 + routedTo: "Mainnet" + hasGas: true + } + ListElement { + name: "Aztec" + iconName: "status" + tokensToSend: 0 + balance: 75 + routedTo: "" + hasGas: false + } + ListElement { + name: "Hermez" + iconName: "status" + tokensToSend: 75 + balance: 75 + routedTo: "Hermez" + hasGas: true + } + ListElement { + name: "Loppring" + iconName: "status" + tokensToSend: 0 + balance: 0 + routedTo: "" + hasGas: false + } + ListElement { + name: "Optimism" + iconName: "status" + tokensToSend: 75 + balance: 75 + routedTo: "Optimism" + hasGas: true + } + ListElement { + name: "zkSync" + iconName: "status" + tokensToSend: 25 + balance: 25 + routedTo: "Optimism" + hasGas: true + } + } +} diff --git a/ui/StatusQ/src/StatusQ/Components/StatusCard.qml b/ui/StatusQ/src/StatusQ/Components/StatusCard.qml new file mode 100644 index 0000000000..cb2c5347d7 --- /dev/null +++ b/ui/StatusQ/src/StatusQ/Components/StatusCard.qml @@ -0,0 +1,498 @@ +import QtQuick 2.13 +import QtQuick.Layouts 1.14 + +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Controls 0.1 + +/*! + \qmltype StatusCard + \inherits Rectangle + \inqmlmodule StatusQ.Components + \since StatusQ.Components 0.1 + \brief This component represents a StatusCard as defined in design under https://www.figma.com/file/FkFClTCYKf83RJWoifWgoX/Wallet-v2?node-id=3161%3A171040 + + There is an advanced mode avialable where a StatusBaseInput is provided for the user to be able to change values. + + Example of how the component looks like: + \image status_card.png + + Example of how to use it: + \qml + StatusCard { + id: card + primaryText: "Mainnet" + secondaryText: "75" + tertiaryText: "BALANCE: " + 250 + cardIconName: "status" + advancedMode: false + } + \endqml + For a list of components available see StatusQ. +*/ + +Rectangle { + id: root + + /*! + \qmlproperty string StatusCard::disabledText + This property is the text to be shown when the card is disabled + */ + property string disabledText: "" + /*! + \qmlproperty bool StatusCard::disabled + This property holds if the card is disbaled + */ + property bool disabled: false + + /*! + \qmlproperty bool StatusCard::clickable + This property holds if the card is clickable + */ + property bool clickable: true + + /*! + \qmlproperty bool StatusCard::advancedMode + This property holds if advanced mode is on for the StatusCard component + */ + property bool advancedMode: false + /*! + \qmlproperty int StatusCard::lockTimeout + This property enables user to customise the amount of time given to the user to enter a new value in + advanced mode before it locked for any new changes + */ + property int lockTimeout: 1500 + + /*! + \qmlproperty alias StatusCard::primaryText + Used to set Primary text in the StatusCard + */ + property alias primaryText: primaryText.text + /*! + \qmlproperty string StatusCard::secondaryText + Used to set Secondary text in the StatusCard + */ + property string secondaryText: "" + /*! + \qmlproperty alias StatusCard::tertiaryText + Used to set Tertiary text in the StatusCard + */ + property alias tertiaryText: tertiaryText.text + /*! + \qmlproperty alias StatusCard::advancedInputText + Used to set text in the StatusBaseInput in advancedMode + */ + property alias advancedInputText: advancedInput.text + /*! + \qmlproperty alias StatusCard::errorIconName + Used to assign an icon to the error icon in StatusCard + */ + property alias errorIconName: errorIcon.icon + /*! + \qmlproperty alias StatusCard::cardIconName + Used to assign an icon to the card icon in StatusCard + */ + property alias cardIconName: cardIcon.icon + + /*! + \qmlproperty alias StatusCard::primaryLabel + This property allows user to customize the primary label in the StatusCard + */ + property alias primaryLabel: primaryText + /*! + \qmlproperty alias StatusCard::secondaryLabel + This property allows user to customize the secondary label in the StatusCard + */ + property alias secondaryLabel: secondaryLabel + /*! + \qmlproperty alias StatusCard::tertiaryLabel + This property allows user to customize the tertiary label in the StatusCard + */ + property alias tertiaryLabel: tertiaryText + /*! + \qmlproperty alias StatusCard::advancedInput + This property allows user to customize the StatusBaseInput in advanced mode + */ + property alias advancedInput: advancedInput + /*! + \qmlproperty alias StatusCard::errorIcon + This property allows user to customize the error icon in the StatusCard + */ + property alias errorIcon: errorIcon + /*! + \qmlproperty alias StatusCard::cardIcon + This property allows user to customize the card icon in the StatusCard + */ + property alias cardIcon: cardIcon + + /*! + \qmlsignal StatusCard::clicked + This signal is emitted when the card is clicked + */ + signal clicked() + + /*! + \qmlproperty string StatusCard::state + This property holds the states of the StatusCard. + Possible values are: + \ "default" : Normal state + \ "unavailable" : Unavailable state + \ "unpreferred": Not preffered state + \ "error" : Error state + */ + state: "default" + + implicitHeight: advancedInput.visible ? 90 : 76 + implicitWidth: 128 + radius: 8 + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + acceptedButtons: Qt.LeftButton + enabled: root.clickable && root.state !== "unavailable" + onClicked: { + disabled = !disabled + root.clicked() + } + } + + RowLayout { + id: layout + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.leftMargin: 8 + anchors.rightMargin: 8 + anchors.topMargin: 8 + ColumnLayout { + Layout.maximumWidth: root.width - cardIcon.width - 24 + StatusBaseText { + id: primaryText + Layout.maximumWidth: parent.width + font.pixelSize: 15 + font.weight: Font.Medium + elide: Text.ElideRight + } + RowLayout { + id: basicInput + StatusBaseText { + id: secondaryLabel + font.pixelSize: 13 + font.weight: Font.Medium + } + StatusIcon { + id: errorIcon + width: 14 + height: 14 + Layout.alignment: Qt.AlignTop + icon: "tiny/warning" + color: Theme.palette.pinColor1 + } + } + + StatusBaseInput { + id: advancedInput + property bool locked: false + implicitWidth: 80 + implicitHeight: 32 + topPadding: 0 + bottomPadding: 0 + leftPadding: 8 + rightPadding: 5 + edit.font.pixelSize: 13 + edit.readOnly: locked || disabled + rightComponent: Row { + width: implicitWidth + spacing: 4 + StatusFlatRoundButton { + anchors.verticalCenter: parent.verticalCenter + width: 12 + height: 12 + icon.name: advancedInput.locked ? "lock" : "unlock" + icon.width: 12 + icon.height: 12 + icon.color: advancedInput.locked ? Theme.palette.primaryColor1 : Theme.palette.baseColor1 + type: StatusFlatRoundButton.Type.Secondary + enabled: !disabled + onClicked: { + advancedInput.locked = !advancedInput.locked + waitTimer.restart() + } + } + StatusFlatRoundButton { + width: 14 + height: 14 + icon.name: "clear" + icon.width: 14 + icon.height: 14 + icon.color: Theme.palette.baseColor1 + type: StatusFlatRoundButton.Type.Secondary + onClicked: advancedInput.edit.clear() + } + } + onTextChanged: { + locked = false + waitTimer.restart() + } + Timer { + id: waitTimer + interval: lockTimeout + onTriggered: { + if(advancedInput.edit.text) + advancedInput.locked = true + } + } + } + StatusBaseText { + id: tertiaryText + font.pixelSize: 10 + } + } + StatusIcon { + id: cardIcon + Layout.alignment: Qt.AlignTop | Qt.AlignRight + Layout.preferredHeight: 32 + Layout.preferredWidth: 32 + mipmap: true + } + } + + states: [ + State { + name: "default" + PropertyChanges { + target: root + color: disabled ? Theme.palette.baseColor4 : "transparent" + } + PropertyChanges { + target: root + border.color: disabled ? "transparent" : Theme.palette.primaryColor2 + } + PropertyChanges { + target: primaryText + color: Theme.palette.directColor1 + } + PropertyChanges { + target: primaryText + visible: primaryText.text + } + PropertyChanges { + target: secondaryLabel + color: disabled ? Theme.palette.directColor5: Theme.palette.primaryColor1 + } + PropertyChanges { + target: secondaryLabel + visible: !advancedMode && secondaryLabel.text + } + PropertyChanges { + target: secondaryLabel + text: disabled ? disabledText : secondaryText + } + PropertyChanges { + target: tertiaryText + color: Theme.palette.directColor5 + } + PropertyChanges { + target: tertiaryText + visible: tertiaryText.text + } + PropertyChanges { + target: cardIcon + opacity: disabled ? 0.4 : 1 + } + PropertyChanges { + target: errorIcon + visible: false + } + PropertyChanges { + target: advancedInput + visible: advancedMode + } + PropertyChanges { + target: advancedInput + edit.color: Theme.palette.directColor1 + } + PropertyChanges { + target: basicInput + visible: !advancedMode + } + }, + State { + name: "error" + PropertyChanges { + target: root + color: disabled ? Theme.palette.baseColor4 : "transparent" + } + PropertyChanges { + target: root + border.color: disabled ? "transparent" : Theme.palette.primaryColor2 + } + PropertyChanges { + target: primaryText + color: Theme.palette.directColor1 + } + PropertyChanges { + target: primaryText + visible: primaryText.text + } + PropertyChanges { + target: secondaryLabel + color: disabled ? Theme.palette.directColor5: Theme.palette.dangerColor1 + } + PropertyChanges { + target: secondaryLabel + visible: !advancedMode && secondaryLabel.text + } + PropertyChanges { + target: secondaryLabel + text: disabled ? disabledText : secondaryText + } + PropertyChanges { + target: tertiaryText + color: disabled ? Theme.palette.directColor5 : Theme.palette.dangerColor1 + } + PropertyChanges { + target: tertiaryText + visible: tertiaryText.text + } + PropertyChanges { + target: cardIcon + opacity: disabled ? 0.4 : 1 + } + PropertyChanges { + target: errorIcon + visible: false + } + PropertyChanges { + target: advancedInput + visible: advancedMode + } + PropertyChanges { + target: advancedInput + edit.color: disabled ? Theme.palette.directColor5 : Theme.palette.dangerColor1 + } + PropertyChanges { + target: basicInput + visible: !advancedMode + } + }, + State { + name: "unpreferred" + PropertyChanges { + target: root + color: disabled ? Theme.palette.baseColor4 : "transparent" + } + PropertyChanges { + target: root + border.color: disabled ? "transparent": Theme.palette.pinColor2 + } + PropertyChanges { + target: primaryText + color: Theme.palette.directColor1 + } + PropertyChanges { + target: primaryText + visible: primaryText.text + } + PropertyChanges { + target: secondaryLabel + color: disabled ? Theme.palette.directColor5 : Theme.palette.pinColor1 + } + PropertyChanges { + target: secondaryLabel + visible: !advancedMode && secondaryLabel.text + } + PropertyChanges { + target: secondaryLabel + text: disabled ? disabledText : secondaryText + } + PropertyChanges { + target: tertiaryText + color: disabled ? Theme.palette.directColor5 : Theme.palette.pinColor1 + } + PropertyChanges { + target: tertiaryText + visible: tertiaryText.text + } + PropertyChanges { + target: cardIcon + opacity: disabled ? 0.4 : 1 + } + PropertyChanges { + target: errorIcon + visible: !disabled && !advancedMode + } + PropertyChanges { + target: advancedInput + visible: advancedMode + } + PropertyChanges { + target: advancedInput + edit.color: Theme.palette.directColor1 + } + PropertyChanges { + target: basicInput + visible: !advancedMode + } + }, + State { + name: "unavailable" + PropertyChanges { + target: root + color: "transparent" + } + PropertyChanges { + target: root + border.color: "transparent" + } + PropertyChanges { + target: primaryText + color: Theme.palette.directColor5 + } + PropertyChanges { + target: primaryText + visible: primaryText.text + } + PropertyChanges { + target: secondaryLabel + color: Theme.palette.directColor5 + } + PropertyChanges { + target: secondaryLabel + visible: secondaryLabel.text + } + PropertyChanges { + target: secondaryLabel + text: secondaryText + } + PropertyChanges { + target: tertiaryText + color: Theme.palette.directColor5 + } + PropertyChanges { + target: tertiaryText + visible: tertiaryText.text + } + PropertyChanges { + target: cardIcon + opacity: 0.4 + } + PropertyChanges { + target: errorIcon + visible: false + } + PropertyChanges { + target: advancedInput + visible: false + } + PropertyChanges { + target: advancedInput + edit.color: Theme.palette.directColor1 + } + PropertyChanges { + target: basicInput + visible: true + } + } + ]} diff --git a/ui/StatusQ/src/StatusQ/Components/qmldir b/ui/StatusQ/src/StatusQ/Components/qmldir index 06ccf65ab3..79e834820c 100644 --- a/ui/StatusQ/src/StatusQ/Components/qmldir +++ b/ui/StatusQ/src/StatusQ/Components/qmldir @@ -38,3 +38,4 @@ StatusColorSpace 0.0 StatusColorSpace.qml StatusCommunityCard 0.1 StatusCommunityCard.qml StatusCommunityTags 0.1 StatusCommunityTags.qml StatusItemSelector 0.1 StatusItemSelector.qml +StatusCard 0.1 StatusCard.qml diff --git a/ui/StatusQ/src/StatusQ/Core/Utils/Utils.qml b/ui/StatusQ/src/StatusQ/Core/Utils/Utils.qml index 2b8b6b0a9a..3718fbe0f4 100644 --- a/ui/StatusQ/src/StatusQ/Core/Utils/Utils.qml +++ b/ui/StatusQ/src/StatusQ/Core/Utils/Utils.qml @@ -83,6 +83,73 @@ QtObject { function isHtml(text) { return (/<\/?[a-z][\s\S]*>/i.test(text)) } + + // function to draw arrow + function drawArrow(context, fromx, fromy, tox, toy, color) { + const dx = tox - fromx; + const dy = toy - fromy; + const headlen = 10; // length of head in pixels + const angle = 0 + const radius = 5 + + context.strokeStyle = color ? color : '#627EEA' + + // straight line + if(dy === 0) { + // draw semicircle + context.beginPath(); + context.arc(fromx, fromy, radius, 3*Math.PI/2, Math.PI/2,false); + context.stroke(); + + // draw straightline + // context.setLineDash([5]); + context.beginPath(); + context.moveTo(fromx + radius, fromy); + context.lineTo(tox, toy); + context.stroke(); + + // draw arrow + context.beginPath(); + context.moveTo(tox - headlen * Math.cos(angle - Math.PI / 6), toy - headlen * Math.sin(angle - Math.PI / 6)); + context.lineTo(tox, toy ); + context.lineTo(tox - headlen * Math.cos(angle + Math.PI / 6), toy - headlen * Math.sin(angle + Math.PI / 6)); + context.stroke(); + } + // connecting between 2 different y positions + else { + + // draw semicircle + context.beginPath(); + context.arc(fromx, fromy, radius, 3*Math.PI/2, Math.PI/2,false); + context.stroke(); + + // draw bent line + context.beginPath(); + context.moveTo(fromx + radius, fromy); + context.lineTo(fromx + dx / 2, fromy); + context.lineTo(fromx + dx / 2, toy - radius); + context.stroke(); + + // draw connecting circle + context.beginPath(); + context.moveTo(fromx + dx / 2 + radius, toy); + context.arc(fromx + dx / 2, toy, radius, 0, 2*Math.PI,false); + context.stroke(); + + // draw straightline + context.beginPath(); + context.moveTo(fromx + dx / 2 + radius, toy); + context.lineTo(tox, toy); + context.stroke(); + + // draw arrow + context.beginPath(); + context.moveTo(tox - headlen * Math.cos(angle - Math.PI / 6), toy - headlen * Math.sin(angle - Math.PI / 6)); + context.lineTo(tox, toy ); + context.lineTo(tox - headlen * Math.cos(angle + Math.PI / 6), toy - headlen * Math.sin(angle + Math.PI / 6)); + context.stroke(); + } + } } diff --git a/ui/StatusQ/src/assets/img/icons/lock.svg b/ui/StatusQ/src/assets/img/icons/lock.svg new file mode 100644 index 0000000000..fbbb654843 --- /dev/null +++ b/ui/StatusQ/src/assets/img/icons/lock.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/StatusQ/src/assets/img/icons/unlock.svg b/ui/StatusQ/src/assets/img/icons/unlock.svg new file mode 100644 index 0000000000..50bb2b0e3f --- /dev/null +++ b/ui/StatusQ/src/assets/img/icons/unlock.svg @@ -0,0 +1,3 @@ + + +