diff --git a/storybook/PagesModel.qml b/storybook/PagesModel.qml index bb370d030e..7e7340c4ff 100644 --- a/storybook/PagesModel.qml +++ b/storybook/PagesModel.qml @@ -285,6 +285,10 @@ ListModel { title: "StatusImageCropPanel" section: "Components" } + ListElement { + title: "StatusBlockProgressBar" + section: "Components" + } ListElement { title: "BrowserSettings" section: "Settings" @@ -297,12 +301,16 @@ ListModel { title: "ProfileSocialLinksPanel" section: "Panels" } - ListElement { - title: "WalletHeader" - section: "Panels" - } ListElement { title: "PopupSizing" section: "Research / Examples" } + ListElement { + title: "WalletHeader" + section: "Wallet" + } + ListElement { + title: "StatusTxProgressBar" + section: "Wallet" + } } diff --git a/storybook/pages/StatusBlockProgressBarPage.qml b/storybook/pages/StatusBlockProgressBarPage.qml new file mode 100644 index 0000000000..513dad229c --- /dev/null +++ b/storybook/pages/StatusBlockProgressBarPage.qml @@ -0,0 +1,68 @@ +import QtQuick 2.14 +import QtQuick.Controls 2.14 + +import utils 1.0 + +import StatusQ.Controls 0.1 +import StatusQ.Core.Theme 0.1 +import Storybook 1.0 + +import Models 1.0 + +SplitView { + id: root + + Logs { id: logs } + + orientation: Qt.Vertical + + Rectangle { + id: rect + SplitView.fillWidth: true + SplitView.fillHeight: true + + StatusBlockProgressBar { + anchors.centerIn: parent + width: 500 + height: 12 + steps: 64 + completedSteps: slider.value + blockSet: 4 + error: failureCheckBox.checked + } + } + + LogsAndControlsPanel { + id: logsAndControlsPanel + + SplitView.minimumHeight: 100 + SplitView.preferredHeight: 200 + + logsView.logText: logs.logText + Column { + CheckBox { + id: failureCheckBox + text: "Failed" + checked: false + } + Slider { + id: slider + value: 0 + from: 0 + to: 64 + stepSize: 1 + Text { + anchors.left: parent.right + anchors.verticalCenter: parent.verticalCenter + text: "Confirmations = " + slider.value + } + } + CheckBox { + id: darkMode + text: "Dark Mode" + checked: false + onCheckedChanged: rect.color = Theme.palette.getColor('graphite3') + } + } + } +} diff --git a/storybook/pages/StatusTxProgressBarPage.qml b/storybook/pages/StatusTxProgressBarPage.qml new file mode 100644 index 0000000000..ddfdc8ad79 --- /dev/null +++ b/storybook/pages/StatusTxProgressBarPage.qml @@ -0,0 +1,133 @@ +import QtQuick 2.14 +import QtQuick.Controls 2.14 + +import utils 1.0 + +import AppLayouts.Wallet.controls 1.0 +import AppLayouts.Wallet.panels 1.0 +import StatusQ.Controls 0.1 +import StatusQ.Core.Theme 0.1 +import Storybook 1.0 + +import Models 1.0 + +SplitView { + id: root + + Logs { id: logs } + + orientation: Qt.Vertical + + QtObject { + id: d + + property var dummyTx: ({ + id: 0xb501e3042105c382a498819b07aba58de3422984e1150655c1583bd1aae144ef, + txType: "erc20", + address: 0x9d41ac74e7d1f981e98f4ec0d631cde0857a2b9c, + blockNumber: 0x7b7935, + blockHash: 0, + timestamp: 1670419848, + nonce: 0x36, + txStatus: 0x1, + chainId: 5, + txHash: 0x82de33a9e81f7c06ea03ad742bc666c4eacb7ec771bac4544ef70a12b2c46d04, + symbol: "ETH", + }) + } + + Item { + SplitView.fillWidth: true + SplitView.fillHeight: true + + Column { + anchors.centerIn: parent + spacing: 100 + StatusTxProgressBar { + anchors.horizontalCenter: parent.horizontalCenter + width: 500 + error: failureCheckBox.checked + isMainnetTx: mainnetCheckbox.checked + confirmations: confirmationsSlider.value + duration: durationSlider.to + progress: durationSlider.value + chainName: isMainnetTx ? "Mainnet" :"Optimism" + } + + Rectangle { + width: root.width + height: 400 + border.width: 2 + WalletTxProgressBlock { + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.topMargin: 20 + width: 500 + error: failureCheckBox.checked + isMainnetTx: mainnetCheckbox.checked + confirmations: confirmationsSlider.value + duration: durationSlider.to + progress: durationSlider.value + chainName: isMainnetTx ? "Mainnet" :"Optimism" + confirmationTimeStamp: 1670419848 + finalisationTimeStamp: 1670419848 + failedTimeStamp: 1670419848 + } + } + } + } + + LogsAndControlsPanel { + id: logsAndControlsPanel + + SplitView.minimumHeight: 100 + SplitView.preferredHeight: 200 + + logsView.logText: logs.logText + Column { + CheckBox { + id: mainnetCheckbox + text: "Mainnet" + checked: true + } + CheckBox { + id: failureCheckBox + text: "Failed" + checked: false + } + Slider { + id: confirmationsSlider + width: 600 + value: 0 + from: 0 + to: 1000 + stepSize: 1 + Text { + anchors.left: parent.right + anchors.verticalCenter: parent.verticalCenter + text: "Confirmations = " + confirmationsSlider.value + } + } + StatusInput { + id: duration + label: "Duration for finalisation" + text: "7" + visible: !mainnetCheckbox.checked && !failureCheckBox.checked + } + Slider { + id: durationSlider + width: 600 + value: 0 + from: 0 + to: Number(duration.text)*24 + stepSize: 1 + Text { + anchors.left: parent.right + anchors.verticalCenter: parent.verticalCenter + text: "Finalisation = " + durationSlider.value + } + visible: !mainnetCheckbox.checked && !failureCheckBox.checked + } + } + } +} diff --git a/ui/StatusQ/src/StatusQ/Controls/StatusBlockProgressBar.qml b/ui/StatusQ/src/StatusQ/Controls/StatusBlockProgressBar.qml new file mode 100644 index 0000000000..82433c274a --- /dev/null +++ b/ui/StatusQ/src/StatusQ/Controls/StatusBlockProgressBar.qml @@ -0,0 +1,109 @@ +import QtQuick 2.14 +import QtQuick.Controls 2.14 + +import StatusQ.Core.Theme 0.1 + +/*! + \qmltype StatusBlockProgressBar + \inherits Control + \inqmlmodule StatusQ.Controls + \since StatusQ.Controls 0.1 + \brief The StatusBlockProgressBar provides a progress bar with steps as blocks + + Example of how to use it: + + \qml + StatusBlockProgressBar { + width: 500 + height: 12 + steps: 64 + completedSteps: transaction.confirmations + blockSet: 4 + error: false + } + \endqml + + For a list of components available see StatusQ. +*/ + +Control { + id: root + + /*! + \qmlproperty color StatusBlockProgressBar::blockColor + This property holds the color for the progress bar blocks + */ + property color blockColor: Theme.palette.blockProgressBarColor + /*! + \qmlproperty color StatusBlockProgressBar::blockSetColor + This property holds the color for the blockSet + */ + property color blockSetColor: Theme.palette.successColor1 + /*! + \qmlproperty color StatusBlockProgressBar::completedColor + This property holds the color for the finalisation blocks + */ + property color completedColor: Theme.palette.primaryColor1 + /*! + \qmlproperty color StatusBlockProgressBar::backgroundColor + This property holds the background color for the bar + */ + property color backgroundColor: "transparent" + /*! + \qmlproperty int StatusBlockProgressBar::steps + This property holds the number of blocks + */ + property int steps: 0 + /*! + \qmlproperty int StatusBlockProgressBar::completedSteps + This property holds the number of completed steps + */ + property int completedSteps: 0 + /*! + \qmlproperty int StatusBlockProgressBar::blockSet + This property holds the number of blocks for different coloring + */ + property int blockSet: 0 + /*! + \qmlproperty bool StatusBlockProgressBar::error + This property holds if there was an error in the progress bar + */ + property bool error: false + + background: Rectangle { + color: root.backgroundColor + } + + contentItem: Row { + id: row + height: parent.height + spacing: 2 + + Repeater { + id: repeater + model: steps + delegate: Rectangle { + width: (root.width - (row.spacing*steps))/steps + height: parent.height + color: { + if(error) { + if(index === 0) { + return Theme.palette.dangerColor1 + } + return blockColor + } + else { + if(index < completedSteps) { + if(index < blockSet) { + return completedColor + } + return blockSetColor + } + return blockColor + } + } + radius: 1 + } + } + } +} diff --git a/ui/StatusQ/src/StatusQ/Controls/StatusProgressBar.qml b/ui/StatusQ/src/StatusQ/Controls/StatusProgressBar.qml index 93ed55aaec..50de494e2b 100644 --- a/ui/StatusQ/src/StatusQ/Controls/StatusProgressBar.qml +++ b/ui/StatusQ/src/StatusQ/Controls/StatusProgressBar.qml @@ -11,6 +11,7 @@ ProgressBar { property color fillColor property color backgroundColor: Theme.palette.directColor8 property color backgroundBorderColor: "transparent" + property int backgroundRadius: 5 width: 416 height: 16 @@ -21,7 +22,7 @@ ProgressBar { implicitHeight: parent.height color: control.backgroundColor border.color: control.backgroundBorderColor - radius: 5 + radius: control.backgroundRadius } contentItem: Item { implicitHeight: parent.height @@ -31,7 +32,7 @@ ProgressBar { width: control.visualPosition * parent.width height: parent.height color: control.fillColor - radius: 5 + radius: control.backgroundRadius StatusBaseText { id: textItem diff --git a/ui/StatusQ/src/StatusQ/Controls/qmldir b/ui/StatusQ/src/StatusQ/Controls/qmldir index 82ffb43603..7e7edf8dab 100644 --- a/ui/StatusQ/src/StatusQ/Controls/qmldir +++ b/ui/StatusQ/src/StatusQ/Controls/qmldir @@ -56,3 +56,4 @@ StatusTextWithLoadingState 0.1 StatusTextWithLoadingState.qml StatusLinkText 0.1 StatusLinkText.qml StatusImageSelector 0.1 StatusImageSelector.qml StatusColorRadioButton 0.1 StatusColorRadioButton.qml +StatusBlockProgressBar 0.1 StatusBlockProgressBar.qml diff --git a/ui/StatusQ/src/StatusQ/Core/Theme/StatusDarkTheme.qml b/ui/StatusQ/src/StatusQ/Core/Theme/StatusDarkTheme.qml index 2c8e6a0634..2125d21ab1 100644 --- a/ui/StatusQ/src/StatusQ/Core/Theme/StatusDarkTheme.qml +++ b/ui/StatusQ/src/StatusQ/Core/Theme/StatusDarkTheme.qml @@ -95,6 +95,8 @@ ThemePalette { "#000086", "#9B81FF", "#3FAEF9", "#9A6600", "#00FFFF", "#008694", "#C2FFFF", "#00F0B6"] + blockProgressBarColor: directColor7 + statusAppLayout: QtObject { property color backgroundColor: baseColor3 property color rightPanelBackgroundColor: baseColor3 diff --git a/ui/StatusQ/src/StatusQ/Core/Theme/StatusLightTheme.qml b/ui/StatusQ/src/StatusQ/Core/Theme/StatusLightTheme.qml index 63ab6866b5..712d0cfe84 100644 --- a/ui/StatusQ/src/StatusQ/Core/Theme/StatusLightTheme.qml +++ b/ui/StatusQ/src/StatusQ/Core/Theme/StatusLightTheme.qml @@ -93,6 +93,8 @@ ThemePalette { "#000086", "#9B81FF", "#3FAEF9", "#9A6600", "#00FFFF", "#008694", "#C2FFFF", "#00F0B6"] + blockProgressBarColor: baseColor3 + statusAppLayout: QtObject { property color backgroundColor: white property color rightPanelBackgroundColor: white diff --git a/ui/StatusQ/src/StatusQ/Core/Theme/ThemePalette.qml b/ui/StatusQ/src/StatusQ/Core/Theme/ThemePalette.qml index 3acb57277c..f2f365a6e4 100644 --- a/ui/StatusQ/src/StatusQ/Core/Theme/ThemePalette.qml +++ b/ui/StatusQ/src/StatusQ/Core/Theme/ThemePalette.qml @@ -181,6 +181,8 @@ QtObject { property var identiconRingColors: [] + property color blockProgressBarColor + property QtObject statusAppLayout: QtObject { property color backgroundColor property color rightPanelBackgroundColor diff --git a/ui/StatusQ/src/statusq.qrc b/ui/StatusQ/src/statusq.qrc index 245f8a41b4..9a25f6271f 100644 --- a/ui/StatusQ/src/statusq.qrc +++ b/ui/StatusQ/src/statusq.qrc @@ -219,5 +219,6 @@ StatusQ/Controls/StatusLinkText.qml StatusQ/Core/Utils/ModelChangeGuard.qml StatusQ/Core/Utils/StackViewStates.qml + StatusQ/Controls/StatusBlockProgressBar.qml diff --git a/ui/app/AppLayouts/Wallet/controls/StatusTxProgressBar.qml b/ui/app/AppLayouts/Wallet/controls/StatusTxProgressBar.qml new file mode 100644 index 0000000000..bc943e5e3d --- /dev/null +++ b/ui/app/AppLayouts/Wallet/controls/StatusTxProgressBar.qml @@ -0,0 +1,106 @@ +import QtQuick 2.14 +import QtQuick.Layouts 1.12 + +import StatusQ.Core.Theme 0.1 +import StatusQ.Core 0.1 +import StatusQ.Controls 0.1 + +ColumnLayout { + id: root + + property bool isMainnetTx: true + property bool error: false + property int steps: isMainnetTx ? 64 : 1 + property int confirmations: 0 + property int confirmationBlocks: isMainnetTx ? 4 : 1 + property string chainName + + property color fillColor: Theme.palette.blockProgressBarColor + property color confirmationColor: Theme.palette.successColor1 + + property alias blockProgressBar: blockProgressBar + property alias titleText: title.text + property alias subText: subText.text + + // Below properties only needed when not a mainnet tx + property alias progress: progressBar.value + property alias duration: progressBar.to + + QtObject { + id: d + readonly property bool finalized: isMainnetTx ? confirmations >= steps : progress === duration + readonly property bool confirmed: confirmations >= confirmationBlocks + readonly property int hoursInADay: 24 + } + + spacing: 8 + + StatusBaseText { + id: title + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.pixelSize: 15 + color: Theme.palette.directColor1 + lineHeight: 22 + lineHeightMode: Text.FixedHeight + text: error ? qsTr("Failed on %1").arg(root.chainName) : + d.finalized ? + qsTr("Finalised on %1").arg(root.chainName) : + d.confirmed ? + qsTr("Confirmed on %1, finalisation in progress...").arg(root.chainName): + confirmations > 0 ? + qsTr("Confirmation in progress on %1...").arg(root.chainName) : + qsTr("Pending on %1...").arg(root.chainName) + } + + RowLayout { + spacing: 2 + Layout.preferredHeight: 12 + Layout.fillWidth: true + + StatusBlockProgressBar { + id: blockProgressBar + Layout.fillWidth: true + Layout.fillHeight: true + visible: root.isMainnetTx + steps: root.steps + completedSteps: root.confirmations + blockSet: root.confirmationBlocks + error: root.error + } + RowLayout { + spacing: 2 + visible: !root.isMainnetTx + Rectangle { + Layout.preferredWidth: 3 + Layout.fillHeight: true + color: error ? Theme.palette.dangerColor1 : confirmations > 0 ? confirmationColor : fillColor + radius: 100 + } + StatusProgressBar { + id: progressBar + Layout.fillWidth: true + Layout.fillHeight: true + from: 0 + to: duration + backgroundColor: root.fillColor + backgroundBorderColor: "transparent" + fillColor: error ? "transparent": Theme.palette.primaryColor1 + backgroundRadius: 2 + } + } + } + + StatusBaseText { + id: subText + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.pixelSize: 13 + color: Theme.palette.baseColor1 + lineHeight: 18 + lineHeightMode: Text.FixedHeight + text: d.finalized && !root.error ? qsTr("In epoch %1").arg(root.confirmations) : d.confirmed && !root.isMainnetTx ? + qsTr("%n day(s) until finality", "", Math.ceil((root.duration - root.progress)/d.hoursInADay)): + qsTr("%1 / %2 confirmations").arg(root.confirmations).arg(root.steps) + } +} diff --git a/ui/app/AppLayouts/Wallet/controls/qmldir b/ui/app/AppLayouts/Wallet/controls/qmldir index e18452098d..930ddc7775 100644 --- a/ui/app/AppLayouts/Wallet/controls/qmldir +++ b/ui/app/AppLayouts/Wallet/controls/qmldir @@ -1,3 +1,4 @@ NetworkFilter 1.0 NetworkFilter.qml NetworkSelectItemDelegate 1.0 NetworkSelectItemDelegate.qml -AccountHeaderGradient 1.0 AccountHeaderGradient.qml \ No newline at end of file +AccountHeaderGradient 1.0 AccountHeaderGradient.qml +StatusTxProgressBar 1.0 StatusTxProgressBar.qml diff --git a/ui/app/AppLayouts/Wallet/panels/WalletTxProgressBlock.qml b/ui/app/AppLayouts/Wallet/panels/WalletTxProgressBlock.qml new file mode 100644 index 0000000000..01cbd05b42 --- /dev/null +++ b/ui/app/AppLayouts/Wallet/panels/WalletTxProgressBlock.qml @@ -0,0 +1,128 @@ +import QtQuick 2.13 +import QtQuick.Layouts 1.13 + +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 + +import utils 1.0 +import shared.panels 1.0 + +import "../controls" + +ColumnLayout { + id: root + + // To-do adapt this for multi-tx, not sure how the data will look for that yet + property bool isMainnetTx: true + property bool error: false + property int confirmations: 0 + property string chainName + property int duration: 0 + property int progress: 0 + property double confirmationTimeStamp + property double finalisationTimeStamp + property double failedTimeStamp + + spacing: 32 + + QtObject { + id: d + readonly property bool finalized: (isMainnetTx ? confirmations >= progressBar.steps : progress === duration) && !error + readonly property bool confirmed: confirmations >= progressBar.confirmationBlocks && !error + } + + Separator { + Layout.fillWidth: true + implicitHeight: 1 + } + + StatusTxProgressBar { + id: progressBar + Layout.topMargin: 8 + Layout.fillWidth: true + error: root.error + isMainnetTx: root.isMainnetTx + confirmations: root.confirmations + duration: root.duration + progress: root.progress + chainName: root.chainName + } + + Column { + spacing: 20 + Column { + spacing: 4 + visible: d.confirmed + StatusBaseText { + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.pixelSize: 13 + color: Theme.palette.baseColor1 + lineHeight: 18 + lineHeightMode: Text.FixedHeight + text: qsTr("Confirmed on %1").arg(root.chainName) + } + StatusBaseText { + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.pixelSize: 13 + color: Theme.palette.directColor1 + lineHeight: 18 + lineHeightMode: Text.FixedHeight + text: LocaleUtils.formatDateTime(root.confirmationTimeStamp * 1000, Locale.LongFormat) + } + } + + Column { + spacing: 4 + visible: d.finalized + StatusBaseText { + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.pixelSize: 13 + color: Theme.palette.baseColor1 + lineHeight: 18 + lineHeightMode: Text.FixedHeight + text: qsTr("Finalised on %1").arg(root.chainName) + } + StatusBaseText { + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.pixelSize: 13 + color: Theme.palette.directColor1 + lineHeight: 18 + lineHeightMode: Text.FixedHeight + text: LocaleUtils.formatDateTime(root.finalisationTimeStamp * 1000, Locale.LongFormat) + } + } + + Column { + spacing: 4 + visible: root.error + StatusBaseText { + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.pixelSize: 13 + color: Theme.palette.baseColor1 + lineHeight: 18 + lineHeightMode: Text.FixedHeight + text: qsTr("Failed on %1").arg(root.chainName) + } + StatusBaseText { + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.pixelSize: 13 + color: Theme.palette.directColor1 + lineHeight: 18 + lineHeightMode: Text.FixedHeight + text: LocaleUtils.formatDateTime(root.failedTimeStamp * 1000, Locale.LongFormat) + } + } + } + + Separator { + Layout.fillWidth: true + Layout.topMargin: 8 + implicitHeight: 1 + } +} diff --git a/ui/app/AppLayouts/Wallet/panels/qmldir b/ui/app/AppLayouts/Wallet/panels/qmldir index 2ac0f2a30d..0723ec4821 100644 --- a/ui/app/AppLayouts/Wallet/panels/qmldir +++ b/ui/app/AppLayouts/Wallet/panels/qmldir @@ -1 +1,2 @@ WalletHeader 1.0 WalletHeader.qml +WalletTxProgressBlock 1.0 WalletTxProgressBlock.qml diff --git a/ui/app/AppLayouts/Wallet/views/TransactionDetailView.qml b/ui/app/AppLayouts/Wallet/views/TransactionDetailView.qml index aa89f24bb2..1268022348 100644 --- a/ui/app/AppLayouts/Wallet/views/TransactionDetailView.qml +++ b/ui/app/AppLayouts/Wallet/views/TransactionDetailView.qml @@ -16,6 +16,7 @@ import shared.stores 1.0 import "../controls" import "../stores" as WalletStores import ".." +import "../panels" Item { id: root @@ -62,6 +63,7 @@ Item { id: transactionHeader objectName: "transactionDetailHeader" width: parent.width + leftPadding: 0 modelData: transaction transactionType: d.isIncoming ? TransactionDelegate.Receive : TransactionDelegate.Send @@ -78,7 +80,7 @@ Item { addressNameFrom: root.isTransactionValid ? WalletStores.RootStore.getNameForAddress(transaction.from): "" sensor.enabled: false formatCurrencyAmount: RootStore.formatCurrencyAmount - color: Theme.palette.statusListItem.backgroundColor + color: Theme.palette.transparent state: "header" onRetryClicked: { @@ -86,6 +88,18 @@ Item { } } + WalletTxProgressBlock { + width: Math.min(513, root.width) + error: transactionHeader.transactionStatus === TransactionDelegate.TransactionStatus.Failed + // To-do once we have days for finalisation for tx other than eth set this to true and also provide duration and progress values + isMainnetTx: true + confirmations: root.isTransactionValid ? Math.abs(RootStore.getLatestBlockNumber() - RootStore.hex2Dec(root.transaction.blockNumber)): 0 + chainName: root.isTransactionValid ? RootStore.getNetworkFullName(transaction.chainId): "" + confirmationTimeStamp: root.isTransactionValid ? transaction.timestamp: "" + finalisationTimeStamp: root.isTransactionValid ? transaction.timestamp: "" + failedTimeStamp: root.isTransactionValid ? transaction.timestamp: "" + } + SavedAddressesDelegate { width: parent.width diff --git a/ui/imports/shared/controls/TransactionDelegate.qml b/ui/imports/shared/controls/TransactionDelegate.qml index 4aff708ce7..39440eae91 100644 --- a/ui/imports/shared/controls/TransactionDelegate.qml +++ b/ui/imports/shared/controls/TransactionDelegate.qml @@ -212,7 +212,7 @@ StatusListItem { } } bgColor: "transparent" - color: Theme.palette.black + color: Theme.palette.directColor1 bgBorderWidth: 1 bgBorderColor: Theme.palette.primaryColor3 }