diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 33f4e26f24..e665ab0a03 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -404,7 +404,6 @@ QML_RES_QML = \ qml/controls/TextButton.qml \ qml/controls/Theme.qml \ qml/controls/ToggleButton.qml \ - qml/controls/utils.js \ qml/controls/ValueInput.qml \ qml/pages/initerrormessage.qml \ qml/pages/main.qml \ diff --git a/src/qml/bitcoin_qml.qrc b/src/qml/bitcoin_qml.qrc index e64fab65e1..e9e65f1c25 100644 --- a/src/qml/bitcoin_qml.qrc +++ b/src/qml/bitcoin_qml.qrc @@ -44,7 +44,6 @@ controls/TextButton.qml controls/Theme.qml controls/ToggleButton.qml - controls/utils.js controls/ValueInput.qml pages/initerrormessage.qml pages/main.qml diff --git a/src/qml/components/BlockClock.qml b/src/qml/components/BlockClock.qml index 51b02afe51..f8f1f90a48 100644 --- a/src/qml/components/BlockClock.qml +++ b/src/qml/components/BlockClock.qml @@ -10,7 +10,6 @@ import Qt.labs.settings 1.0 import org.bitcoincore.qt 1.0 import "../controls" -import "../controls/utils.js" as Utils Item { id: root @@ -25,13 +24,12 @@ Item { property alias headerSize: mainText.font.pixelSize property alias subText: subText.text property int headerSize: 32 - property bool connected: nodeModel.numOutboundPeers > 0 - property bool synced: nodeModel.verificationProgress > 0.999 - property string syncProgress: formatProgressPercentage(nodeModel.verificationProgress * 100) + property bool connected: nodeModel.connected + property bool synced: nodeModel.synced + property string syncProgress: nodeModel.formattedVerificationProgress property bool paused: false - property var syncState: Utils.formatRemainingSyncTime(nodeModel.remainingSyncTime) - property string syncTime: syncState.text - property bool estimating: syncState.estimating + property string syncTime: nodeModel.formattedRemainingSyncTime + property bool estimating: nodeModel.estimatingSyncTime property bool faulted: nodeModel.faulted activeFocusOnTab: true @@ -53,7 +51,7 @@ Item { verificationProgress: nodeModel.verificationProgress paused: root.paused || root.faulted connected: root.connected - synced: nodeModel.verificationProgress > 0.999 + synced: root.synced backgroundColor: Theme.color.neutral2 timeTickColor: Theme.color.neutral5 confirmationColors: Theme.color.confirmationColors @@ -242,17 +240,4 @@ Item { } } ] - - - function formatProgressPercentage(progress) { - if (progress >= 1) { - return Math.round(progress) + "%" - } else if (progress >= 0.1) { - return progress.toFixed(1) + "%" - } else if (progress >= 0.01) { - return progress.toFixed(2) + "%" - } else { - return "0%" - } - } } diff --git a/src/qml/controls/utils.js b/src/qml/controls/utils.js deleted file mode 100644 index bcbe9af35c..0000000000 --- a/src/qml/controls/utils.js +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) 2024 The Bitcoin Core developers -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -// utils.js - -function formatRemainingSyncTime(milliseconds) { - var minutes = Math.floor(milliseconds / 60000); - var seconds = Math.floor((milliseconds % 60000) / 1000); - var weeks = Math.floor(minutes / 10080); - minutes %= 10080; - var days = Math.floor(minutes / 1440); - minutes %= 1440; - var hours = Math.floor(minutes / 60); - minutes %= 60; - var result = ""; - var estimatingStatus = false; - - if (weeks > 0) { - return { - text: "~" + weeks + (weeks === 1 ? " week" : " weeks") + " left", - estimating: false - }; - } - if (days > 0) { - return { - text: "~" + days + (days === 1 ? " day" : " days") + " left", - estimating: false - }; - } - if (hours >= 5) { - return { - text: "~" + hours + (hours === 1 ? " hour" : " hours") + " left", - estimating: false - }; - } - if (hours > 0) { - return { - text: "~" + hours + "h " + minutes + "m" + " left", - estimating: false - }; - } - if (minutes >= 5) { - return { - text: "~" + minutes + (minutes === 1 ? " minute" : " minutes") + " left", - estimating: false - }; - } - if (minutes > 0) { - return { - text: "~" + minutes + "m " + seconds + "s" + " left", - estimating: false - }; - } - if (seconds > 0) { - return { - text: "~" + seconds + (seconds === 1 ? " second" : " seconds") + " left", - estimating: false - }; - } else { - return { - text: "Estimating", - estimating: true - }; - } -} diff --git a/src/qml/models/nodemodel.cpp b/src/qml/models/nodemodel.cpp index 521e5fa1c5..bc7d80f184 100644 --- a/src/qml/models/nodemodel.cpp +++ b/src/qml/models/nodemodel.cpp @@ -36,10 +36,32 @@ void NodeModel::setNumOutboundPeers(int new_num) { if (new_num != m_num_outbound_peers) { m_num_outbound_peers = new_num; + + bool new_connected = (m_num_outbound_peers > 0); + if (new_connected != m_connected) { + setConnected(new_connected); + } + Q_EMIT numOutboundPeersChanged(); } } +void NodeModel::setConnected(bool new_connected) +{ + if (new_connected != m_connected) { + m_connected = new_connected; + Q_EMIT connectedChanged(); + } +} + +void NodeModel::setEstimatingSyncTime(bool new_estimating) +{ + if (m_estimating_sync_time != new_estimating) { + m_estimating_sync_time = new_estimating; + Q_EMIT estimatingSyncTimeChanged(); + } +} + void NodeModel::setRemainingSyncTime(double new_progress) { int currentTime = QDateTime::currentDateTime().toMSecsSinceEpoch(); @@ -66,6 +88,7 @@ void NodeModel::setRemainingSyncTime(double new_progress) } if (remainingMSecs > 0 && m_block_process_time.count() % 1000 == 0) { m_remaining_sync_time = remainingMSecs; + setFormattedRemainingSyncTime(m_remaining_sync_time); Q_EMIT remainingSyncTimeChanged(); } @@ -73,18 +96,110 @@ void NodeModel::setRemainingSyncTime(double new_progress) if (m_block_process_time.count() > MAX_SAMPLES) { m_block_process_time.remove(1, m_block_process_time.count() - 1); } + } else { + m_remaining_sync_time = 0; + setFormattedRemainingSyncTime(m_remaining_sync_time); + + Q_EMIT remainingSyncTimeChanged(); + } +} + +void NodeModel::setFormattedRemainingSyncTime(int new_time) +{ + int minutes = new_time / 60000; + int seconds = (new_time % 60000) / 1000; + + int weeks = minutes / 10080; + minutes %= 10080; + + int days = minutes / 1440; + minutes %= 1440; + + int hours = minutes / 60; + minutes %= 60; + + if (weeks > 0) { + m_formatted_remaining_sync_time = QObject::tr("~%1 %2 left") + .arg(weeks) + .arg(weeks == 1 ? QObject::tr("week") : QObject::tr("weeks")); + setEstimatingSyncTime(false); + } else if (days > 0) { + m_formatted_remaining_sync_time = QObject::tr("~%1 %2 left") + .arg(days) + .arg(days == 1 ? QObject::tr("day") : QObject::tr("days")); + setEstimatingSyncTime(false); + } else if (hours >= 5) { + m_formatted_remaining_sync_time = QObject::tr("~%1 %2 left") + .arg(hours) + .arg(hours == 1 ? QObject::tr("hour") : QObject::tr("hours")); + setEstimatingSyncTime(false); + } else if (hours > 0) { + m_formatted_remaining_sync_time = QObject::tr("~%1h %2m left") + .arg(hours) + .arg(minutes); + setEstimatingSyncTime(false); + } else if (minutes >= 5) { + m_formatted_remaining_sync_time = QObject::tr("~%1 %2 left") + .arg(minutes) + .arg(minutes == 1 ? QObject::tr("minute") : QObject::tr("minutes")); + setEstimatingSyncTime(false); + } else if (minutes > 0) { + m_formatted_remaining_sync_time = QObject::tr("~%1m %2s left") + .arg(minutes) + .arg(seconds); + setEstimatingSyncTime(false); + } else if (seconds > 0) { + m_formatted_remaining_sync_time = QObject::tr("~%1 %2 left") + .arg(seconds) + .arg(seconds == 1 ? QObject::tr("second") : QObject::tr("seconds")); + setEstimatingSyncTime(false); + } else { + if (m_synced) { + setEstimatingSyncTime(false); + } else { + m_formatted_remaining_sync_time = QObject::tr("Estimating"); + setEstimatingSyncTime(true); + } } } + void NodeModel::setVerificationProgress(double new_progress) { if (new_progress != m_verification_progress) { setRemainingSyncTime(new_progress); + setFormattedVerificationProgress(new_progress); + + if (new_progress >= 0.999) { + setSynced(true); + } m_verification_progress = new_progress; Q_EMIT verificationProgressChanged(); } } +void NodeModel::setFormattedVerificationProgress(double new_progress) +{ + double percentage = new_progress * 100.00; + + if (percentage >= 99.99) { + m_formatted_verification_progress = QStringLiteral("100%"); + } else if (percentage == 0.0) { + m_formatted_verification_progress = QStringLiteral("0%"); + } else { + int decimal_places = (percentage >= 10.0) ? 1 : 2; + m_formatted_verification_progress = QString::number(percentage, 'f', decimal_places) + '%'; + } +} + +void NodeModel::setSynced(bool new_synced) +{ + if (m_synced != new_synced) { + m_synced = new_synced; + Q_EMIT syncedChanged(); + } +} + void NodeModel::setPause(bool new_pause) { if(m_pause != new_pause) { diff --git a/src/qml/models/nodemodel.h b/src/qml/models/nodemodel.h index a17f9b0833..8a88a38fea 100644 --- a/src/qml/models/nodemodel.h +++ b/src/qml/models/nodemodel.h @@ -30,8 +30,13 @@ class NodeModel : public QObject Q_PROPERTY(QString fullClientVersion READ fullClientVersion CONSTANT) Q_PROPERTY(int numOutboundPeers READ numOutboundPeers NOTIFY numOutboundPeersChanged) Q_PROPERTY(int maxNumOutboundPeers READ maxNumOutboundPeers CONSTANT) + Q_PROPERTY(bool connected READ connected NOTIFY connectedChanged) Q_PROPERTY(int remainingSyncTime READ remainingSyncTime NOTIFY remainingSyncTimeChanged) + Q_PROPERTY(QString formattedRemainingSyncTime READ formattedRemainingSyncTime NOTIFY remainingSyncTimeChanged) + Q_PROPERTY(bool estimatingSyncTime READ estimatingSyncTime NOTIFY estimatingSyncTimeChanged) Q_PROPERTY(double verificationProgress READ verificationProgress NOTIFY verificationProgressChanged) + Q_PROPERTY(QString formattedVerificationProgress READ formattedVerificationProgress NOTIFY verificationProgressChanged) + Q_PROPERTY(bool synced READ synced NOTIFY syncedChanged) Q_PROPERTY(bool pause READ pause WRITE setPause NOTIFY pauseChanged) Q_PROPERTY(bool faulted READ errorState WRITE setErrorState NOTIFY errorStateChanged) @@ -44,10 +49,15 @@ class NodeModel : public QObject int numOutboundPeers() const { return m_num_outbound_peers; } void setNumOutboundPeers(int new_num); int maxNumOutboundPeers() const { return m_max_num_outbound_peers; } + bool connected() const { return m_connected; } int remainingSyncTime() const { return m_remaining_sync_time; } void setRemainingSyncTime(double new_progress); + QString formattedRemainingSyncTime() const { return m_formatted_remaining_sync_time; } + bool estimatingSyncTime() const { return m_estimating_sync_time; } double verificationProgress() const { return m_verification_progress; } void setVerificationProgress(double new_progress); + QString formattedVerificationProgress() const { return m_formatted_verification_progress; } + bool synced() const { return m_synced; } bool pause() const { return m_pause; } void setPause(bool new_pause); bool errorState() const { return m_faulted; } @@ -68,10 +78,13 @@ public Q_SLOTS: Q_SIGNALS: void blockTipHeightChanged(); void numOutboundPeersChanged(); + void connectedChanged(); void remainingSyncTimeChanged(); + void estimatingSyncTimeChanged(); void requestedInitialize(); void requestedShutdown(); void verificationProgressChanged(); + void syncedChanged(); void pauseChanged(bool new_pause); void errorStateChanged(bool new_error_state); @@ -82,12 +95,23 @@ public Q_SLOTS: void timerEvent(QTimerEvent* event) override; private: + void setConnected(bool new_connected); + void setEstimatingSyncTime(bool new_estimating); + void setFormattedRemainingSyncTime(int new_time); + void setFormattedVerificationProgress(double new_progress); + void setSynced(bool new_synced); + // Properties that are exposed to QML. int m_block_tip_height{0}; int m_num_outbound_peers{0}; + bool m_connected{false}; static constexpr int m_max_num_outbound_peers{MAX_OUTBOUND_FULL_RELAY_CONNECTIONS + MAX_BLOCK_RELAY_ONLY_CONNECTIONS}; int m_remaining_sync_time{0}; + QString m_formatted_remaining_sync_time; + bool m_estimating_sync_time{false}; double m_verification_progress{0.0}; + QString m_formatted_verification_progress; + bool m_synced{false}; bool m_pause{false}; bool m_faulted{false}; diff --git a/src/qml/pages/wallet/DesktopWallets.qml b/src/qml/pages/wallet/DesktopWallets.qml index 59a7ac15e4..a050d5e7bc 100644 --- a/src/qml/pages/wallet/DesktopWallets.qml +++ b/src/qml/pages/wallet/DesktopWallets.qml @@ -9,7 +9,6 @@ import QtQuick.Layouts 1.15 import org.bitcoincore.qt 1.0 import "../../controls" -import "../../controls/utils.js" as Utils import "../../components" import "../node" @@ -77,10 +76,10 @@ Page { Tooltip { id: blockClockTooltip - property var syncState: Utils.formatRemainingSyncTime(nodeModel.remainingSyncTime) - property bool synced: nodeModel.verificationProgress > 0.9999 + property string remainingSyncTime: nodeModel.formattedRemainingSyncTime + property bool synced: nodeModel.synced property bool paused: nodeModel.pause - property bool connected: nodeModel.numOutboundPeers > 0 + property bool connected: nodeModel.connected anchors.top: blockClockTabButton.bottom anchors.topMargin: -5 @@ -93,7 +92,7 @@ Page { } else if (connected && synced) { qsTr("Blocktime\n" + Number(nodeModel.blockTipHeight).toLocaleString(Qt.locale(), 'f', 0)) } else if (connected){ - qsTr("Downloading blocks\n" + syncState.text) + qsTr("Downloading blocks\n" + blockClockTooltip.remainingSyncTime) } else { qsTr("Connecting") }