diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index a61043608d..7cad78ff37 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -337,11 +337,14 @@ QML_RES_ICONS = \ qml/res/icons/caret-left.png \ qml/res/icons/caret-right.png \ qml/res/icons/check.png \ + qml/res/icons/circle-file.png \ + qml/res/icons/circle-green-check.png \ qml/res/icons/cross.png \ qml/res/icons/error.png \ qml/res/icons/export.png \ qml/res/icons/gear.png \ qml/res/icons/gear-outline.png \ + qml/res/icons/green-check.png \ qml/res/icons/hidden.png \ qml/res/icons/info.png \ qml/res/icons/network-dark.png \ @@ -372,6 +375,7 @@ QML_RES_QML = \ qml/components/NetworkIndicator.qml \ qml/components/ProxySettings.qml \ qml/components/Separator.qml \ + qml/components/SnapshotSettings.qml \ qml/components/StorageLocations.qml \ qml/components/StorageOptions.qml \ qml/components/StorageSettings.qml \ @@ -425,6 +429,7 @@ QML_RES_QML = \ qml/pages/settings/SettingsDeveloper.qml \ qml/pages/settings/SettingsDisplay.qml \ qml/pages/settings/SettingsProxy.qml \ + qml/pages/settings/SettingsSnapshot.qml \ qml/pages/settings/SettingsStorage.qml \ qml/pages/settings/SettingsTheme.qml \ qml/pages/wallet/AddWallet.qml \ diff --git a/src/qml/bitcoin_qml.qrc b/src/qml/bitcoin_qml.qrc index ec48e0c74d..167bfd2f91 100644 --- a/src/qml/bitcoin_qml.qrc +++ b/src/qml/bitcoin_qml.qrc @@ -15,6 +15,7 @@ components/ProxySettings.qml components/StorageLocations.qml components/Separator.qml + components/SnapshotSettings.qml components/StorageOptions.qml components/StorageSettings.qml components/ThemeSettings.qml @@ -25,6 +26,7 @@ controls/CoreTextField.qml controls/ExternalLink.qml controls/FocusBorder.qml + controls/GreenCheckIcon.qml controls/Header.qml controls/Icon.qml controls/InformationPage.qml @@ -66,6 +68,7 @@ pages/settings/SettingsDeveloper.qml pages/settings/SettingsDisplay.qml pages/settings/SettingsProxy.qml + pages/settings/SettingsSnapshot.qml pages/settings/SettingsStorage.qml pages/settings/SettingsTheme.qml pages/wallet/AddWallet.qml @@ -91,11 +94,14 @@ res/icons/caret-left.png res/icons/caret-right.png res/icons/check.png + res/icons/circle-file.png + res/icons/circle-green-check.png res/icons/cross.png res/icons/error.png res/icons/export.png res/icons/gear.png res/icons/gear-outline.png + res/icons/green-check.png res/icons/hidden.png res/icons/info.png res/icons/minus.png diff --git a/src/qml/components/ConnectionSettings.qml b/src/qml/components/ConnectionSettings.qml index 90625a7def..fea589685d 100644 --- a/src/qml/components/ConnectionSettings.qml +++ b/src/qml/components/ConnectionSettings.qml @@ -8,7 +8,36 @@ import QtQuick.Layouts 1.15 import "../controls" ColumnLayout { + property bool snapshotImported: false + function setSnapshotImported(imported) { + snapshotImported = imported + } spacing: 4 + Setting { + id: gotoSnapshot + Layout.fillWidth: true + header: qsTr("Load snapshot") + description: qsTr("Instant use with background sync") + actionItem: Item { + width: 26 + height: 26 + CaretRightIcon { + anchors.centerIn: parent + visible: !snapshotImported + color: gotoSnapshot.stateColor + } + GreenCheckIcon { + anchors.centerIn: parent + visible: snapshotImported + color: Theme.color.transparent + } + } + onClicked: { + connectionSwipe.incrementCurrentIndex() + connectionSwipe.incrementCurrentIndex() + } + } + Separator { Layout.fillWidth: true } Setting { Layout.fillWidth: true header: qsTr("Enable listening") diff --git a/src/qml/components/SnapshotSettings.qml b/src/qml/components/SnapshotSettings.qml new file mode 100644 index 0000000000..ebac415b60 --- /dev/null +++ b/src/qml/components/SnapshotSettings.qml @@ -0,0 +1,204 @@ +// Copyright (c) 2023-present The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 + +import "../controls" + +ColumnLayout { + signal snapshotImportCompleted() + property int snapshotVerificationCycles: 0 + property real snapshotVerificationProgress: 0 + property bool snapshotVerified: false + + id: columnLayout + width: Math.min(parent.width, 450) + anchors.horizontalCenter: parent.horizontalCenter + + + Timer { + id: snapshotSimulationTimer + interval: 50 // Update every 50ms + running: false + repeat: true + onTriggered: { + if (snapshotVerificationProgress < 1) { + snapshotVerificationProgress += 0.01 + } else { + snapshotVerificationCycles++ + if (snapshotVerificationCycles < 1) { + snapshotVerificationProgress = 0 + } else { + running = false + snapshotVerified = true + settingsStack.currentIndex = 2 + } + } + } + } + + StackLayout { + id: settingsStack + currentIndex: 0 + + ColumnLayout { + Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: Math.min(parent.width, 450) + + Image { + Layout.alignment: Qt.AlignCenter + source: "image://images/circle-file" + + sourceSize.width: 200 + sourceSize.height: 200 + } + + Header { + Layout.fillWidth: true + Layout.topMargin: 20 + headerBold: true + header: qsTr("Load snapshot") + descriptionBold: false + descriptionColor: Theme.color.neutral6 + descriptionSize: 17 + descriptionLineHeight: 1.1 + description: qsTr("You can start using the application more quickly by loading a recent transaction snapshot." + + " It will be automatically verified in the background.") + } + + ContinueButton { + Layout.preferredWidth: Math.min(300, columnLayout.width - 2 * Layout.leftMargin) + Layout.topMargin: 40 + Layout.leftMargin: 20 + Layout.rightMargin: Layout.leftMargin + Layout.bottomMargin: 20 + Layout.alignment: Qt.AlignCenter + text: qsTr("Choose snapshot file") + onClicked: { + settingsStack.currentIndex = 1 + snapshotSimulationTimer.start() + } + } + } + + ColumnLayout { + Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: Math.min(parent.width, 450) + + Image { + Layout.alignment: Qt.AlignCenter + source: "image://images/circle-file" + + sourceSize.width: 200 + sourceSize.height: 200 + } + + Header { + Layout.fillWidth: true + Layout.topMargin: 20 + Layout.leftMargin: 20 + Layout.rightMargin: 20 + header: qsTr("Loading Snapshot") + } + + ProgressIndicator { + id: progressIndicator + Layout.topMargin: 20 + width: 200 + height: 20 + progress: snapshotVerificationProgress + Layout.alignment: Qt.AlignCenter + progressColor: Theme.color.blue + } + } + + ColumnLayout { + id: loadedSnapshotColumn + Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: Math.min(parent.width, 450) + + Image { + Layout.alignment: Qt.AlignCenter + source: "image://images/circle-green-check" + + sourceSize.width: 60 + sourceSize.height: 60 + } + + Header { + Layout.fillWidth: true + Layout.topMargin: 20 + headerBold: true + header: qsTr("Snapshot Loaded") + descriptionBold: false + descriptionColor: Theme.color.neutral6 + descriptionSize: 17 + descriptionLineHeight: 1.1 + description: qsTr("It contains transactions up to January 12, 2024. Newer transactions still need to be downloaded." + + " The data will be verified in the background.") + } + + ContinueButton { + Layout.preferredWidth: Math.min(300, columnLayout.width - 2 * Layout.leftMargin) + Layout.topMargin: 40 + Layout.alignment: Qt.AlignCenter + text: qsTr("Done") + onClicked: { + snapshotImportCompleted() + connectionSwipe.decrementCurrentIndex() + connectionSwipe.decrementCurrentIndex() + } + } + + Setting { + id: viewDetails + Layout.alignment: Qt.AlignCenter + header: qsTr("View details") + actionItem: CaretRightIcon { + id: caretIcon + color: viewDetails.stateColor + rotation: viewDetails.expanded ? 90 : 0 + Behavior on rotation { NumberAnimation { duration: 200 } } + } + + property bool expanded: false + + onClicked: { + expanded = !expanded + } + } + + ColumnLayout { + id: detailsContent + visible: viewDetails.expanded + Layout.preferredWidth: Math.min(300, parent.width - 2 * Layout.leftMargin) + Layout.alignment: Qt.AlignCenter + Layout.leftMargin: 80 + Layout.rightMargin: 80 + Layout.topMargin: 10 + spacing: 10 + // TODO: make sure the block height number aligns right + RowLayout { + CoreText { + text: qsTr("Block Height:") + Layout.alignment: Qt.AlignLeft + font.pixelSize: 14 + } + CoreText { + text: qsTr("200,000") + Layout.alignment: Qt.AlignRight + font.pixelSize: 14 + } + } + Separator { Layout.fillWidth: true } + CoreText { + text: qsTr("Hash: 0x1234567890abcdef...") + font.pixelSize: 14 + } + } + } + } +} diff --git a/src/qml/controls/GreenCheckIcon.qml b/src/qml/controls/GreenCheckIcon.qml new file mode 100644 index 0000000000..cf6ccec67c --- /dev/null +++ b/src/qml/controls/GreenCheckIcon.qml @@ -0,0 +1,11 @@ +// Copyright (c) 2023 - present The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick 2.15 +import QtQuick.Controls 2.15 + +Icon { + source: "image://images/green-check" + size: 26 +} diff --git a/src/qml/controls/Header.qml b/src/qml/controls/Header.qml index f3c4c0c3e3..ece49234d2 100644 --- a/src/qml/controls/Header.qml +++ b/src/qml/controls/Header.qml @@ -25,6 +25,7 @@ ColumnLayout { property int subtextSize: 15 property color subtextColor: Theme.color.neutral9 property bool wrap: true + property real descriptionLineHeight: 1 spacing: 0 Loader { @@ -60,6 +61,7 @@ ColumnLayout { text: root.description horizontalAlignment: root.center ? Text.AlignHCenter : Text.AlignLeft wrapMode: wrap ? Text.WordWrap : Text.NoWrap + lineHeight: root.descriptionLineHeight Behavior on color { ColorAnimation { duration: 150 } diff --git a/src/qml/controls/ProgressIndicator.qml b/src/qml/controls/ProgressIndicator.qml index 117a4baebb..9d6d62d329 100644 --- a/src/qml/controls/ProgressIndicator.qml +++ b/src/qml/controls/ProgressIndicator.qml @@ -7,6 +7,7 @@ import QtQuick.Controls 2.15 Control { property real progress: 0 + property color progressColor: Theme.color.orange Behavior on progress { NumberAnimation { easing.type: Easing.Bezier @@ -26,7 +27,7 @@ Control { width: contentItem.width height: contentItem.height radius: contentItem.radius - color: Theme.color.orange + color: progressColor } } } diff --git a/src/qml/controls/Theme.qml b/src/qml/controls/Theme.qml index f57e152cbd..3c7621c2b5 100644 --- a/src/qml/controls/Theme.qml +++ b/src/qml/controls/Theme.qml @@ -27,6 +27,7 @@ Control { required property color blue required property color amber required property color purple + required property color transparent required property color neutral0 required property color neutral1 required property color neutral2 @@ -59,6 +60,7 @@ Control { blue: "#3CA3DE" amber: "#C9B500" purple: "#C075DC" + transparent: "#00000000" neutral0: "#000000" neutral1: "#1A1A1A" neutral2: "#2D2D2D" @@ -91,6 +93,7 @@ Control { blue: "#2D9CDB" amber: "#C9B500" purple: "#BB6BD9" + transparent: "#00000000" neutral0: "#FFFFFF" neutral1: "#F8F8F8" neutral2: "#F4F4F4" diff --git a/src/qml/imageprovider.cpp b/src/qml/imageprovider.cpp index daf2feeae2..abd387f3b4 100644 --- a/src/qml/imageprovider.cpp +++ b/src/qml/imageprovider.cpp @@ -77,6 +77,16 @@ QPixmap ImageProvider::requestPixmap(const QString& id, QSize* size, const QSize return QIcon(":/icons/check").pixmap(requested_size); } + if (id == "circle-file") { + *size = requested_size; + return QIcon(":/icons/circle-file").pixmap(requested_size); + } + + if (id == "circle-green-check") { + *size = requested_size; + return QIcon(":/icons/circle-green-check").pixmap(requested_size); + } + if (id == "cross") { *size = requested_size; return QIcon(":/icons/cross").pixmap(requested_size); @@ -102,6 +112,11 @@ QPixmap ImageProvider::requestPixmap(const QString& id, QSize* size, const QSize return QIcon(":/icons/gear-outline").pixmap(requested_size); } + if (id == "green-check") { + *size = requested_size; + return QIcon(":/icons/green-check").pixmap(requested_size); + } + if (id == "info") { *size = requested_size; return QIcon(":/icons/info").pixmap(requested_size); diff --git a/src/qml/pages/settings/SettingsConnection.qml b/src/qml/pages/settings/SettingsConnection.qml index 9ff9094f11..2e36dd4a99 100644 --- a/src/qml/pages/settings/SettingsConnection.qml +++ b/src/qml/pages/settings/SettingsConnection.qml @@ -13,6 +13,11 @@ Item { property alias navMiddleDetail: connectionSwipe.navMiddleDetail property alias navLeftDetail: connectionSwipe.navLeftDetail property alias showHeader: connectionSwipe.showHeader + + function setSnapshotImported(imported) { + connection_settings.loadedDetailItem.setSnapshotImported(imported) + } + SwipeView { id: connectionSwipe property alias navRightDetail: connection_settings.navRightDetail @@ -38,5 +43,14 @@ Item { connectionSwipe.decrementCurrentIndex() } } + SettingsSnapshot { + onSnapshotImportCompleted: { + setSnapshotImported(true) + } + onBackClicked: { + connectionSwipe.decrementCurrentIndex() + connectionSwipe.decrementCurrentIndex() + } + } } } diff --git a/src/qml/pages/settings/SettingsSnapshot.qml b/src/qml/pages/settings/SettingsSnapshot.qml new file mode 100644 index 0000000000..e6c557a022 --- /dev/null +++ b/src/qml/pages/settings/SettingsSnapshot.qml @@ -0,0 +1,37 @@ +// Copyright (c) 2024-present The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import "../../controls" +import "../../components" + +Page { + signal backClicked + signal snapshotImportCompleted + + id: root + + background: null + implicitWidth: 450 + leftPadding: 20 + rightPadding: 20 + topPadding: 30 + + header: NavigationBar2 { + leftItem: NavButton { + iconSource: "image://images/caret-left" + text: qsTr("Back") + onClicked: root.backClicked() + } + } + SnapshotSettings { + width: Math.min(parent.width, 450) + anchors.horizontalCenter: parent.horizontalCenter + onSnapshotImportCompleted: { + root.snapshotImportCompleted() + } + } +} diff --git a/src/qml/res/icons/circle-file.png b/src/qml/res/icons/circle-file.png new file mode 100644 index 0000000000..14a776e6d5 Binary files /dev/null and b/src/qml/res/icons/circle-file.png differ diff --git a/src/qml/res/icons/circle-green-check.png b/src/qml/res/icons/circle-green-check.png new file mode 100644 index 0000000000..25bb20e00f Binary files /dev/null and b/src/qml/res/icons/circle-green-check.png differ diff --git a/src/qml/res/icons/green-check.png b/src/qml/res/icons/green-check.png new file mode 100644 index 0000000000..65b1799020 Binary files /dev/null and b/src/qml/res/icons/green-check.png differ diff --git a/src/qml/res/src/circle-file.svg b/src/qml/res/src/circle-file.svg new file mode 100644 index 0000000000..d8af3949d8 --- /dev/null +++ b/src/qml/res/src/circle-file.svg @@ -0,0 +1,18 @@ + + circle-file-svg + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/qml/res/src/circle-green-check.svg b/src/qml/res/src/circle-green-check.svg new file mode 100644 index 0000000000..d56c175fd4 --- /dev/null +++ b/src/qml/res/src/circle-green-check.svg @@ -0,0 +1,18 @@ + + Circle arrow up-svg + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/qml/res/src/green-check.svg b/src/qml/res/src/green-check.svg new file mode 100644 index 0000000000..fba9cac6b9 --- /dev/null +++ b/src/qml/res/src/green-check.svg @@ -0,0 +1,4 @@ + + + +