diff --git a/.cirrus.yml b/.cirrus.yml index 4295d0adba..efc74b9ee0 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -386,3 +386,24 @@ task: - cp src/qt/android/build/outputs/apk/debug/android-debug.apk ${CIRRUS_WORKING_DIR}/unsecure_android.apk unsecure_android_apk_artifacts: path: "unsecure_android.apk" + +task: + name: 'ARM32 Android APK [jammy]' + alias: android32 + << : *CONTAINER_DEPENDS_TEMPLATE + container: + image: ubuntu:jammy + android_sdk_cache: + folder: "depends/SDKs/android" + fingerprint_key: "ANDROID_API_LEVEL=28 ANDROID_BUILD_TOOLS_VERSION=28.0.3 ANDROID_NDK_VERSION=23.2.8568313" + depends_sources_cache: + folder: "depends/sources" + fingerprint_script: git rev-parse HEAD:depends/packages + << : *MAIN_TEMPLATE + env: + << : *CIRRUS_EPHEMERAL_WORKER_TEMPLATE_ENV + FILE_ENV: "./ci/test/00_setup_env_android32.sh" + copy_artifacts_script: + - cp src/qt/android/build/outputs/apk/debug/android-debug.apk ${CIRRUS_WORKING_DIR}/unsecure_android_32bit.apk + unsecure_android_apk_32bit_artifacts: + path: "unsecure_android_32bit.apk" diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index faac02e0c6..a9caeec7b2 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -49,4 +49,5 @@ Links for Windows and macOS build artifacts. Replace with the assigned pull [![Intel macOS](https://img.shields.io/badge/OS-Intel%20macOS-green)](https://api.cirrus-ci.com/v1/artifact/github/bitcoin-core/gui-qml/macos/unsecure_mac_gui.zip?branch=pull/) [![Apple Silicon macOS](https://img.shields.io/badge/OS-Apple%20Silicon%20macOS-green)](https://api.cirrus-ci.com/v1/artifact/github/bitcoin-core/gui-qml/macos_arm64/unsecure_mac_arm64_gui.zip?branch=pull/) [![ARM64 Android](https://img.shields.io/badge/OS-Android-green)](https://api.cirrus-ci.com/v1/artifact/github/bitcoin-core/gui-qml/android/unsecure_android_apk.zip?branch=pull/) +[![ARM32 Android](https://img.shields.io/badge/OS-Android%2032bit-green)](https://api.cirrus-ci.com/v1/artifact/github/bitcoin-core/gui-qml/android32/unsecure_android_32bit_apk.zip?branch=pull/) --> diff --git a/ci/test/00_setup_env_android32.sh b/ci/test/00_setup_env_android32.sh new file mode 100755 index 0000000000..78447c5cd6 --- /dev/null +++ b/ci/test/00_setup_env_android32.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2023 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +export LC_ALL=C.UTF-8 + +export HOST=armv7a-linux-android +export PACKAGES="unzip openjdk-8-jdk gradle" +export CONTAINER_NAME=ci_android +export CI_IMAGE_NAME_TAG="ubuntu:jammy" + +export RUN_UNIT_TESTS=false +export RUN_FUNCTIONAL_TESTS=false + +export ANDROID_API_LEVEL=28 +export ANDROID_BUILD_TOOLS_VERSION=28.0.3 +export ANDROID_NDK_VERSION=23.2.8568313 +export ANDROID_TOOLS_URL=https://dl.google.com/android/repository/commandlinetools-linux-8512546_latest.zip +export ANDROID_HOME="${DEPENDS_DIR}/SDKs/android" +export ANDROID_NDK_HOME="${ANDROID_HOME}/ndk/${ANDROID_NDK_VERSION}" +export DEP_OPTS="ANDROID_SDK=${ANDROID_HOME} ANDROID_NDK=${ANDROID_NDK_HOME} ANDROID_API_LEVEL=${ANDROID_API_LEVEL} ANDROID_TOOLCHAIN_BIN=${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/" + +export BITCOIN_CONFIG="--disable-tests --enable-gui-tests --disable-bench --disable-fuzz-binary --without-utils --without-libs --without-daemon" diff --git a/configure.ac b/configure.ac index 586db4dc69..e26ebade97 100644 --- a/configure.ac +++ b/configure.ac @@ -812,12 +812,15 @@ case $host in case $host in *x86_64*) ANDROID_ARCH=x86_64 + NDK_LIBCXX_DIR=x86_64-linux-android ;; *aarch64*) ANDROID_ARCH=arm64-v8a + NDK_LIBCXX_DIR=aarch64-linux-android ;; *armv7a*) ANDROID_ARCH=armeabi-v7a + NDK_LIBCXX_DIR=arm-linux-androideabi ;; *) AC_MSG_ERROR([Could not determine Android arch, or it is unsupported]) ;; esac @@ -1963,6 +1966,7 @@ AC_SUBST(HAVE_BUILTIN_PREFETCH) AC_SUBST(HAVE_MM_PREFETCH) AC_SUBST(HAVE_STRONG_GETAUXVAL) AC_SUBST(ANDROID_ARCH) +AC_SUBST(NDK_LIBCXX_DIR) AC_SUBST(HAVE_EVHTTP_CONNECTION_GET_PEER_CONST_CHAR) AC_CONFIG_FILES([Makefile src/Makefile doc/man/Makefile share/setup.nsi share/qt/Info.plist test/config.ini]) AC_CONFIG_FILES([contrib/devtools/split-debug.sh],[chmod +x contrib/devtools/split-debug.sh]) diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 6b3536fe98..0cf38d1bc4 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -339,6 +339,7 @@ QML_QRC = qml/bitcoin_qml.qrc QML_RES_QML = \ qml/components/AboutOptions.qml \ qml/components/BlockClock.qml \ + qml/components/BlockClockDisplayMode.qml \ qml/components/BlockCounter.qml \ qml/components/CaretRightButton.qml \ qml/components/ConnectionOptions.qml \ @@ -352,6 +353,7 @@ QML_RES_QML = \ qml/components/StorageLocations.qml \ qml/components/StorageOptions.qml \ qml/components/StorageSettings.qml \ + qml/components/ThemeSettings.qml \ qml/components/TotalBytesIndicator.qml \ qml/controls/ContinueButton.qml \ qml/controls/CoreText.qml \ @@ -386,10 +388,13 @@ QML_RES_QML = \ qml/pages/onboarding/OnboardingStorageLocation.qml \ qml/pages/onboarding/OnboardingStrengthen.qml \ qml/pages/settings/SettingsAbout.qml \ + qml/pages/settings/SettingsBlockClockDisplayMode.qml \ qml/pages/settings/SettingsConnection.qml \ qml/pages/settings/SettingsDeveloper.qml \ + qml/pages/settings/SettingsDisplay.qml \ qml/pages/settings/SettingsProxy.qml \ - qml/pages/settings/SettingsStorage.qml + qml/pages/settings/SettingsStorage.qml \ + qml/pages/settings/SettingsTheme.qml BITCOIN_QT_CPP = $(BITCOIN_QT_BASE_CPP) if TARGET_WINDOWS @@ -518,7 +523,7 @@ QT_BASE_TLD = $(shell tar tf $(QT_BASE_PATH) --exclude='*/*') bitcoin_qt_apk: FORCE mkdir -p $(APK_LIB_DIR) - cp $(dir $(lastword $(CC)))../sysroot/usr/lib/$(host_alias)/libc++_shared.so $(APK_LIB_DIR) + cp $(dir $(lastword $(CC)))../sysroot/usr/lib/$(NDK_LIBCXX_DIR)/libc++_shared.so $(APK_LIB_DIR) tar xf $(QT_BASE_PATH) -C qt/android/src/ $(QT_BASE_TLD)src/android/jar/src --strip-components=5 tar xf $(QT_BASE_PATH) -C qt/android/src/ $(QT_BASE_TLD)src/android/java/src --strip-components=5 patch -i ../depends/patches/qt/fix_android_get_drawable.patch qt/android/src/org/qtproject/qt5/android/QtActivityDelegate.java diff --git a/src/qml/README.md b/src/qml/README.md index 11f3850ca9..e5904a5fd2 100644 --- a/src/qml/README.md +++ b/src/qml/README.md @@ -9,6 +9,7 @@ Unsecure CI artifacts are available for local testing of the master branch, avoi - for Intel macOS: [`unsecure_mac_gui.zip`](https://api.cirrus-ci.com/v1/artifact/github/bitcoin-core/gui-qml/macos/unsecure_mac_gui.zip) - for Apple Silicon macOS: [`unsecure_mac_arm64_gui.zip`](https://api.cirrus-ci.com/v1/artifact/github/bitcoin-core/gui-qml/macos_arm64/unsecure_mac_arm64_gui.zip) - for ARM64 Android: [`unsecure_android_apk.zip`](https://api.cirrus-ci.com/v1/artifact/github/bitcoin-core/gui-qml/android/unsecure_android_apk.zip) +- for ARM32 Android: [`unsecure_android_32bit_apk.zip`](https://api.cirrus-ci.com/v1/artifact/github/bitcoin-core/gui-qml/android32/unsecure_android_32bit_apk.zip) Note: For Apple Silicon macOS machines, the binary must be signed before it can be ran. To apply a signature, run the following on the unzipped CI artifact: diff --git a/src/qml/bitcoin_qml.qrc b/src/qml/bitcoin_qml.qrc index 85644c4a46..b6a5ef0b71 100644 --- a/src/qml/bitcoin_qml.qrc +++ b/src/qml/bitcoin_qml.qrc @@ -2,6 +2,7 @@ components/AboutOptions.qml components/BlockClock.qml + components/BlockClockDisplayMode.qml components/BlockCounter.qml components/CaretRightButton.qml components/ConnectionOptions.qml @@ -15,6 +16,7 @@ components/Separator.qml components/StorageOptions.qml components/StorageSettings.qml + components/ThemeSettings.qml components/TotalBytesIndicator.qml controls/ContinueButton.qml controls/CoreText.qml @@ -49,10 +51,13 @@ pages/onboarding/OnboardingStorageLocation.qml pages/onboarding/OnboardingStrengthen.qml pages/settings/SettingsAbout.qml + pages/settings/SettingsBlockClockDisplayMode.qml pages/settings/SettingsConnection.qml pages/settings/SettingsDeveloper.qml + pages/settings/SettingsDisplay.qml pages/settings/SettingsProxy.qml pages/settings/SettingsStorage.qml + pages/settings/SettingsTheme.qml res/icons/arrow-down.png diff --git a/src/qml/components/AboutOptions.qml b/src/qml/components/AboutOptions.qml index cf82bf101a..f0f118f18d 100644 --- a/src/qml/components/AboutOptions.qml +++ b/src/qml/components/AboutOptions.qml @@ -51,7 +51,7 @@ ColumnLayout { header: qsTr("Version") actionItem: ExternalLink { parentState: versionLink.state - description: "v22.99.0-1e7564eca8a6" + description: nodeModel.fullClientVersion link: "https://bitcoin.org/en/download" iconSource: "image://images/caret-right" iconWidth: 18 diff --git a/src/qml/components/BlockClock.qml b/src/qml/components/BlockClock.qml index 47efc73606..a8c1c98728 100644 --- a/src/qml/components/BlockClock.qml +++ b/src/qml/components/BlockClock.qml @@ -5,6 +5,7 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 +import Qt.labs.settings 1.0 import org.bitcoincore.qt 1.0 @@ -12,9 +13,11 @@ import "../controls" Item { id: root + property real parentWidth: 600 + property real parentHeight: 600 - implicitWidth: 200 - implicitHeight: 200 + width: dial.width + height: dial.height + networkIndicator.height + networkIndicator.anchors.topMargin property alias header: mainText.text property alias headerSize: mainText.font.pixelSize @@ -22,13 +25,26 @@ Item { 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 paused: false + property var syncState: formatRemainingSyncTime(nodeModel.remainingSyncTime) + property string syncTime: syncState.text + property bool estimating: syncState.estimating activeFocusOnTab: true + Settings { + id: settings + property alias blockclocksize: dial.scale + } + BlockClockDial { id: dial - anchors.fill: parent + anchors.horizontalCenter: root.horizontalCenter + scale: Theme.blockclocksize + width: Math.min((root.parentWidth * dial.scale), (root.parentHeight * dial.scale)) + height: dial.width + penWidth: dial.width / 50 timeRatioList: chainModel.timeRatioList verificationProgress: nodeModel.verificationProgress paused: root.paused @@ -56,8 +72,8 @@ Item { background: null icon.source: "image://images/bitcoin-circle" icon.color: Theme.color.neutral9 - icon.width: 40 - icon.height: 40 + icon.width: Math.max(dial.width / 5, 1) + icon.height: Math.max(dial.width / 5, 1) anchors.bottom: mainText.top anchors.horizontalCenter: root.horizontalCenter @@ -68,10 +84,10 @@ Item { Label { id: mainText - anchors.centerIn: parent + anchors.centerIn: dial font.family: "Inter" font.styleName: "Semi Bold" - font.pixelSize: 32 + font.pixelSize: dial.width * (4/25) color: Theme.color.neutral9 Behavior on color { @@ -82,26 +98,64 @@ Item { Label { id: subText anchors.top: mainText.bottom + property bool estimating: root.estimating anchors.horizontalCenter: root.horizontalCenter font.family: "Inter" font.styleName: "Semi Bold" - font.pixelSize: 18 + font.pixelSize: dial.width * (9/100) color: Theme.color.neutral4 - Behavior on color { - ColorAnimation { duration: 150 } + Component.onCompleted: { + colorChanged.connect(function() { + if (!subText.estimating) { + themeChange.restart(); + } + }); + + estimatingChanged.connect(function() { + if (subText.estimating) { + estimatingTime.start(); + } else { + estimatingTime.stop(); + } + }); + + subText.estimatingChanged(subText.estimating); + } + + ColorAnimation on color{ + id: themeChange + target: subText + duration: 150 } + + SequentialAnimation { + id: estimatingTime + loops: Animation.Infinite + ColorAnimation { target: subText; property: "color"; from: subText.color; to: Theme.color.neutral6; duration: 1000 } + ColorAnimation { target: subText; property: "color"; from: Theme.color.neutral6; to: subText.color; duration: 1000 } + } + } PeersIndicator { anchors.top: subText.bottom - anchors.topMargin: 20 + anchors.topMargin: dial.width / 10 anchors.horizontalCenter: root.horizontalCenter numOutboundPeers: nodeModel.numOutboundPeers maxNumOutboundPeers: nodeModel.maxNumOutboundPeers + indicatorDimensions: dial.width * (3/200) + indicatorSpacing: dial.width / 40 paused: root.paused } + NetworkIndicator { + id: networkIndicator + anchors.top: dial.bottom + anchors.topMargin: networkIndicator.visible ? 30 : 0 + anchors.horizontalCenter: root.horizontalCenter + } + MouseArea { anchors.fill: dial cursorShape: Qt.PointingHandCursor @@ -119,17 +173,17 @@ Item { name: "IBD"; when: !synced && !paused && connected PropertyChanges { target: root - header: formatProgressPercentage(nodeModel.verificationProgress * 100) - subText: formatRemainingSyncTime(nodeModel.remainingSyncTime) + header: root.syncProgress + subText: root.syncTime } }, - State { name: "BLOCKCLOCK"; when: synced && !paused && connected PropertyChanges { target: root header: Number(nodeModel.blockTipHeight).toLocaleString(Qt.locale(), 'f', 0) subText: "Blocktime" + estimating: false } }, @@ -138,16 +192,17 @@ Item { PropertyChanges { target: root header: "Paused" - headerSize: 24 + headerSize: dial.width * (3/25) subText: "Tap to resume" + estimating: false } PropertyChanges { target: bitcoinIcon - anchors.bottomMargin: 5 + anchors.bottomMargin: dial.width / 40 } PropertyChanges { target: subText - anchors.topMargin: 4 + anchors.topMargin: dial.width / 50 } }, @@ -156,16 +211,17 @@ Item { PropertyChanges { target: root header: "Connecting" - headerSize: 24 + headerSize: dial.width * (3/25) subText: "Please wait" + estimating: false } PropertyChanges { target: bitcoinIcon - anchors.bottomMargin: 5 + anchors.bottomMargin: dial.width / 40 } PropertyChanges { target: subText - anchors.topMargin: 4 + anchors.topMargin: dial.width / 50 } } ] @@ -191,29 +247,55 @@ Item { minutes %= 1440; var hours = Math.floor(minutes / 60); minutes %= 60; + var result = ""; + var estimatingStatus = false; if (weeks > 0) { - return "~" + weeks + (weeks === 1 ? " week" : " weeks") + " left"; + return { + text: "~" + weeks + (weeks === 1 ? " week" : " weeks") + " left", + estimating: false + }; } if (days > 0) { - return "~" + days + (days === 1 ? " day" : " days") + " left"; + return { + text: "~" + days + (days === 1 ? " day" : " days") + " left", + estimating: false + }; } if (hours >= 5) { - return "~" + hours + (hours === 1 ? " hour" : " hours") + " left"; + return { + text: "~" + hours + (hours === 1 ? " hour" : " hours") + " left", + estimating: false + }; } if (hours > 0) { - return "~" + hours + "h " + minutes + "m" + " left"; + return { + text: "~" + hours + "h " + minutes + "m" + " left", + estimating: false + }; } if (minutes >= 5) { - return "~" + minutes + (minutes === 1 ? " minute" : " minutes") + " left"; + return { + text: "~" + minutes + (minutes === 1 ? " minute" : " minutes") + " left", + estimating: false + }; } if (minutes > 0) { - return "~" + minutes + "m " + seconds + "s" + " left"; + return { + text: "~" + minutes + "m " + seconds + "s" + " left", + estimating: false + }; } if (seconds > 0) { - return "~" + seconds + (seconds === 1 ? " second" : " seconds") + " left"; + return { + text: "~" + seconds + (seconds === 1 ? " second" : " seconds") + " left", + estimating: false + }; + } else { + return { + text: "Estimating", + estimating: true + }; } - - return "Estimating"; } } diff --git a/src/qml/components/BlockClockDisplayMode.qml b/src/qml/components/BlockClockDisplayMode.qml new file mode 100644 index 0000000000..eb552d7456 --- /dev/null +++ b/src/qml/components/BlockClockDisplayMode.qml @@ -0,0 +1,40 @@ +// Copyright (c) 2023 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 Qt.labs.settings 1.0 +import "../controls" + +ColumnLayout { + id: root + spacing: 15 + + ButtonGroup { + id: group + } + + OptionButton { + Layout.fillWidth: true + ButtonGroup.group: group + text: qsTr("Default") + description: qsTr("For personal use on a computer or smartphone.") + checked: Theme.blockclocksize == (1/3) + onClicked: { + Theme.blockclocksize = (1/3) + } + } + + OptionButton { + Layout.fillWidth: true + ButtonGroup.group: group + text: qsTr("Showcase") + description: qsTr("A larger block clock for public display on a tablet or other large screen.") + checked: Theme.blockclocksize == (1/2) + onClicked: { + Theme.blockclocksize = (1/2) + } + } +} diff --git a/src/qml/components/PeersIndicator.qml b/src/qml/components/PeersIndicator.qml index 54a0d52402..7faedad947 100644 --- a/src/qml/components/PeersIndicator.qml +++ b/src/qml/components/PeersIndicator.qml @@ -3,22 +3,25 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. import QtQuick 2.15 -import QtQuick.Layouts 1.15 import "../controls" -RowLayout { +Row { id: root required property int numOutboundPeers required property int maxNumOutboundPeers required property bool paused property int size: 5 + property real indicatorDimensions: 3 + property real indicatorSpacing: 5 - spacing: 5 + height: root.indicatorDimensions + + spacing: root.indicatorSpacing Repeater { model: 5 Rectangle { - width: 3 - height: 3 + width: root.indicatorDimensions + height: root.indicatorDimensions radius: width / 2 color: Theme.color.neutral9 opacity: (index === 0 && root.numOutboundPeers > 0) || (index + 1 <= root.size * root.numOutboundPeers / root.maxNumOutboundPeers) ? 0.95 : 0.45 diff --git a/src/qml/components/ThemeSettings.qml b/src/qml/components/ThemeSettings.qml new file mode 100644 index 0000000000..dd052ec268 --- /dev/null +++ b/src/qml/components/ThemeSettings.qml @@ -0,0 +1,60 @@ +// Copyright (c) 2023 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 Qt.labs.settings 1.0 +import "../controls" + +ColumnLayout { + id: root + spacing: 4 + + Settings { + id: settings + } + + Setting { + Layout.fillWidth: true + header: qsTr("Light") + actionItem: Button { + anchors.centerIn: parent + visible: !Theme.dark + icon.source: "image://images/check" + icon.color: Theme.color.neutral9 + icon.height: 24 + icon.width: 24 + background: null + + Behavior on icon.color { + ColorAnimation { duration: 150 } + } + } + onClicked: { + Theme.dark = false + } + } + Separator { Layout.fillWidth: true } + Setting { + Layout.fillWidth: true + header: qsTr("Dark") + actionItem: Button { + anchors.centerIn: parent + visible: Theme.dark + icon.source: "image://images/check" + icon.color: Theme.color.neutral9 + icon.height: 24 + icon.width: 24 + background: null + + Behavior on icon.color { + ColorAnimation { duration: 150 } + } + } + onClicked: { + Theme.dark = true; + } + } +} diff --git a/src/qml/components/blockclockdial.cpp b/src/qml/components/blockclockdial.cpp index 2d4580e9e0..547389b232 100644 --- a/src/qml/components/blockclockdial.cpp +++ b/src/qml/components/blockclockdial.cpp @@ -10,10 +10,13 @@ #include #include #include +#include BlockClockDial::BlockClockDial(QQuickItem *parent) : QQuickPaintedItem(parent) , m_time_ratio_list{0.0} +, m_pen_width{4} +, m_scale{5/12} , m_background_color{QColor("#2D2D2D")} , m_confirmation_colors{QList{}} , m_time_tick_color{QColor("#000000")} @@ -132,6 +135,20 @@ void BlockClockDial::setPaused(bool paused) } } +void BlockClockDial::setPenWidth(qreal width) +{ + m_pen_width = width; + update(); +} + +void BlockClockDial::setScale(qreal scale) +{ + m_scale = scale; + update(); + + Q_EMIT scaleChanged(); +} + void BlockClockDial::setBackgroundColor(QColor color) { m_background_color = color; @@ -183,7 +200,7 @@ void BlockClockDial::paintBlocks(QPainter * painter) } QPen pen(m_confirmation_colors[5]); - pen.setWidth(4); + pen.setWidthF(m_pen_width); pen.setCapStyle(Qt::FlatCap); const QRectF bounds = getBoundsForPen(pen); painter->setPen(pen); @@ -196,7 +213,7 @@ void BlockClockDial::paintBlocks(QPainter * painter) for (int i = 1; i < numberOfBlocks; i++) { if (numberOfBlocks - i <= 6) { QPen pen(m_confirmation_colors[numberOfBlocks - i - 1]); - pen.setWidth(4); + pen.setWidthF(m_pen_width); pen.setCapStyle(Qt::FlatCap); painter->setPen(pen); } @@ -227,7 +244,7 @@ void BlockClockDial::paintBlocks(QPainter * painter) void BlockClockDial::paintProgress(QPainter * painter) { QPen pen(m_confirmation_colors[5]); - pen.setWidthF(4); + pen.setWidthF(m_pen_width); pen.setCapStyle(Qt::RoundCap); const QRectF bounds = getBoundsForPen(pen); painter->setPen(pen); @@ -250,7 +267,7 @@ void BlockClockDial::paintProgress(QPainter * painter) void BlockClockDial::paintConnectingAnimation(QPainter * painter) { QPen pen; - pen.setWidthF(4); + pen.setWidthF(m_pen_width); setupConnectingGradient(pen); pen.setBrush(QBrush(m_connecting_gradient)); pen.setCapStyle(Qt::RoundCap); @@ -267,7 +284,7 @@ void BlockClockDial::paintConnectingAnimation(QPainter * painter) void BlockClockDial::paintBackground(QPainter * painter) { QPen pen(m_background_color); - pen.setWidthF(4); + pen.setWidthF(m_pen_width); const QRectF bounds = getBoundsForPen(pen); painter->setPen(pen); @@ -283,12 +300,12 @@ double BlockClockDial::degreesPerPixel() void BlockClockDial::paintTimeTicks(QPainter * painter) { QPen pen(m_time_tick_color); - pen.setWidthF(4); + pen.setWidthF(m_pen_width); // Calculate bound based on width of default pen const QRectF bounds = getBoundsForPen(pen); QPen time_tick_pen = QPen(m_time_tick_color); - time_tick_pen.setWidth(2); + time_tick_pen.setWidthF(m_pen_width / 2); time_tick_pen.setCapStyle(Qt::RoundCap); painter->setPen(time_tick_pen); for (double angle = 0; angle < 360; angle += 30) { diff --git a/src/qml/components/blockclockdial.h b/src/qml/components/blockclockdial.h index ea3ed686b1..ecbe98e4da 100644 --- a/src/qml/components/blockclockdial.h +++ b/src/qml/components/blockclockdial.h @@ -9,6 +9,7 @@ #include #include #include +#include class BlockClockDial : public QQuickPaintedItem { @@ -18,6 +19,8 @@ class BlockClockDial : public QQuickPaintedItem Q_PROPERTY(bool connected READ connected WRITE setConnected) Q_PROPERTY(bool synced READ synced WRITE setSynced) Q_PROPERTY(bool paused READ paused WRITE setPaused) + Q_PROPERTY(qreal penWidth READ penWidth WRITE setPenWidth) + Q_PROPERTY(qreal scale READ scale WRITE setScale NOTIFY scaleChanged) Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor) Q_PROPERTY(QList confirmationColors READ confirmationColors WRITE setConfirmationColors ) Q_PROPERTY(QColor timeTickColor READ timeTickColor WRITE setTimeTickColor) @@ -31,6 +34,8 @@ class BlockClockDial : public QQuickPaintedItem bool connected() const { return m_is_connected; }; bool synced() const { return m_is_synced; }; bool paused() const { return m_is_paused; }; + qreal penWidth() const { return m_pen_width; }; + qreal scale() const { return m_scale; }; QColor backgroundColor() const { return m_background_color; }; QList confirmationColors() const { return m_confirmation_colors; }; QColor timeTickColor() const { return m_time_tick_color; }; @@ -41,10 +46,15 @@ public Q_SLOTS: void setConnected(bool connected); void setSynced(bool synced); void setPaused(bool paused); + void setPenWidth(qreal width); + void setScale(qreal scale); void setBackgroundColor(QColor color); void setConfirmationColors(QList colorList); void setTimeTickColor(QColor color); +Q_SIGNALS: + void scaleChanged(); + private: void paintConnectingAnimation(QPainter * painter); void paintProgress(QPainter * painter); @@ -63,6 +73,8 @@ public Q_SLOTS: bool m_is_connected; bool m_is_synced; bool m_is_paused; + qreal m_pen_width; + qreal m_scale; QColor m_background_color; QConicalGradient m_connecting_gradient; qreal m_connecting_start_angle = 90; diff --git a/src/qml/controls/ContinueButton.qml b/src/qml/controls/ContinueButton.qml index 43323f073a..dd1fa76bab 100644 --- a/src/qml/controls/ContinueButton.qml +++ b/src/qml/controls/ContinueButton.qml @@ -4,10 +4,11 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 +import org.bitcoincore.qt 1.0 Button { id: root - hoverEnabled: true + hoverEnabled: AppMode.isDesktop contentItem: CoreText { text: parent.text bold: true diff --git a/src/qml/controls/NavButton.qml b/src/qml/controls/NavButton.qml index d9741bdd8e..7576ab199c 100644 --- a/src/qml/controls/NavButton.qml +++ b/src/qml/controls/NavButton.qml @@ -6,6 +6,8 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 +import org.bitcoincore.qt 1.0 + AbstractButton { id: root property int iconHeight: 30 @@ -14,7 +16,7 @@ AbstractButton { property url iconSource: "" property Rectangle iconBackground: null property color iconColor: Theme.color.neutral9 - hoverEnabled: true + hoverEnabled: AppMode.isDesktop topPadding: text_background.active ? 7 : 14 bottomPadding: text_background.active ? 7 : 14 rightPadding: text_background.active ? 22 : 14 @@ -94,7 +96,7 @@ AbstractButton { } MouseArea { anchors.fill: parent - hoverEnabled: true + hoverEnabled: AppMode.isDesktop onEntered: { root.background.state = "HOVER" } diff --git a/src/qml/controls/OutlineButton.qml b/src/qml/controls/OutlineButton.qml index 927913f4e4..dac386dec6 100644 --- a/src/qml/controls/OutlineButton.qml +++ b/src/qml/controls/OutlineButton.qml @@ -4,10 +4,11 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 +import org.bitcoincore.qt 1.0 Button { id: root - hoverEnabled: true + hoverEnabled: AppMode.isDesktop contentItem: CoreText { text: parent.text bold: true diff --git a/src/qml/controls/Setting.qml b/src/qml/controls/Setting.qml index 3b9331f39a..006ce71414 100644 --- a/src/qml/controls/Setting.qml +++ b/src/qml/controls/Setting.qml @@ -6,6 +6,8 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 +import org.bitcoincore.qt 1.0 + AbstractButton { id: root property bool last: parent && root === parent.children[parent.children.length - 1] @@ -16,7 +18,7 @@ AbstractButton { property string errorText: "" property bool showErrorText: false property color stateColor - hoverEnabled: true + hoverEnabled: AppMode.isDesktop state: "FILLED" states: [ @@ -57,7 +59,7 @@ AbstractButton { MouseArea { id: mouseArea anchors.fill: root - hoverEnabled: true + hoverEnabled: AppMode.isDesktop onEntered: { root.state = "HOVER" } diff --git a/src/qml/controls/TextButton.qml b/src/qml/controls/TextButton.qml index 5704b2b7f7..0bfafecde0 100644 --- a/src/qml/controls/TextButton.qml +++ b/src/qml/controls/TextButton.qml @@ -5,6 +5,8 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 +import org.bitcoincore.qt 1.0 + Button { id: root property int textSize: 18 @@ -13,7 +15,7 @@ Button { property bool bold: true property bool rightalign: false padding: 15 - hoverEnabled: true + hoverEnabled: AppMode.isDesktop contentItem: CoreText { text: root.text bold: root.bold diff --git a/src/qml/controls/Theme.qml b/src/qml/controls/Theme.qml index 76283fcc1c..df62e0a4ad 100644 --- a/src/qml/controls/Theme.qml +++ b/src/qml/controls/Theme.qml @@ -1,12 +1,21 @@ pragma Singleton import QtQuick 2.15 import QtQuick.Controls 2.15 +import Qt.labs.settings 1.0 Control { + id: root property bool dark: true + property real blockclocksize: (5/12) readonly property ColorSet color: dark ? darkColorSet : lightColorSet readonly property ImageSet image: dark ? darkImageSet : lightImageSet + Settings { + id: settings + property alias dark: root.dark + property alias blockclocksize: root.blockclocksize + } + component ColorSet: QtObject { required property color white required property color background diff --git a/src/qml/controls/ToggleButton.qml b/src/qml/controls/ToggleButton.qml index f4663d4d60..a073fc5a6f 100644 --- a/src/qml/controls/ToggleButton.qml +++ b/src/qml/controls/ToggleButton.qml @@ -4,6 +4,7 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 +import org.bitcoincore.qt 1.0 Button { property int bgRadius: 5 @@ -16,7 +17,7 @@ Button { id: root checkable: true - hoverEnabled: true + hoverEnabled: AppMode.isDesktop leftPadding: 12 rightPadding: 12 topPadding: 5 diff --git a/src/qml/nodemodel.h b/src/qml/nodemodel.h index 2e4575f3ea..522e6a2995 100644 --- a/src/qml/nodemodel.h +++ b/src/qml/nodemodel.h @@ -7,10 +7,12 @@ #include #include +#include #include #include +#include QT_BEGIN_NAMESPACE class QTimerEvent; @@ -25,6 +27,7 @@ class NodeModel : public QObject { Q_OBJECT Q_PROPERTY(int blockTipHeight READ blockTipHeight NOTIFY blockTipHeightChanged) + Q_PROPERTY(QString fullClientVersion READ fullClientVersion CONSTANT) Q_PROPERTY(int numOutboundPeers READ numOutboundPeers NOTIFY numOutboundPeersChanged) Q_PROPERTY(int maxNumOutboundPeers READ maxNumOutboundPeers CONSTANT) Q_PROPERTY(int remainingSyncTime READ remainingSyncTime NOTIFY remainingSyncTimeChanged) @@ -36,6 +39,7 @@ class NodeModel : public QObject int blockTipHeight() const { return m_block_tip_height; } void setBlockTipHeight(int new_height); + QString fullClientVersion() const { return QString::fromStdString(FormatFullVersion()); } int numOutboundPeers() const { return m_num_outbound_peers; } void setNumOutboundPeers(int new_num); int maxNumOutboundPeers() const { return m_max_num_outbound_peers; } diff --git a/src/qml/pages/node/NetworkTraffic.qml b/src/qml/pages/node/NetworkTraffic.qml index 16ec8d9593..c85bbf1b25 100644 --- a/src/qml/pages/node/NetworkTraffic.qml +++ b/src/qml/pages/node/NetworkTraffic.qml @@ -5,6 +5,7 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 +import Qt.labs.settings 1.0 import "../../controls" import "../../components" @@ -12,7 +13,13 @@ import org.bitcoincore.qt 1.0 InformationPage { id: root - property int maxSamples: 300 + property int trafficGraphScale: 300 + + Settings { + id: settings + property alias trafficGraphScale: root.trafficGraphScale + } + bannerActive: false bold: true headerText: qsTr("Network Traffic") @@ -41,10 +48,11 @@ InformationPage { anchors.centerIn: parent anchors.margins: 3 spacing: 5 + ToggleButton { text: qsTr("5 min") autoExclusive: true - checked: true + checked: root.trafficGraphScale === 300 bgRadius: 3 textColor: Theme.color.neutral9 textActiveColor: Theme.color.neutral0 @@ -52,14 +60,15 @@ InformationPage { bgDefaultColor: Theme.color.neutral3 onClicked: { - root.maxSamples = 300 - networkTrafficTower.updateFilterWindowSize(root.maxSamples / 10) + root.trafficGraphScale = 300 + networkTrafficTower.updateFilterWindowSize(root.trafficGraphScale / 10) } } ToggleButton { text: qsTr("1 hour") autoExclusive: true + checked: root.trafficGraphScale === 3600 bgRadius: 3 textColor: Theme.color.neutral9 textActiveColor: Theme.color.neutral0 @@ -67,14 +76,15 @@ InformationPage { bgDefaultColor: Theme.color.neutral3 onClicked: { - root.maxSamples = 3600 - networkTrafficTower.updateFilterWindowSize(root.maxSamples / 10) + root.trafficGraphScale = 3600 + networkTrafficTower.updateFilterWindowSize(root.trafficGraphScale / 10) } } ToggleButton { text: qsTr("12 hours") autoExclusive: true + checked: root.trafficGraphScale === 3600 * 12 bgRadius: 3 textColor: Theme.color.neutral9 textActiveColor: Theme.color.neutral0 @@ -82,14 +92,15 @@ InformationPage { bgDefaultColor: Theme.color.neutral3 onClicked: { - root.maxSamples = 3600 * 12 - networkTrafficTower.updateFilterWindowSize(root.maxSamples / 10) + root.trafficGraphScale = 3600 * 12 + networkTrafficTower.updateFilterWindowSize(root.trafficGraphScale / 10) } } ToggleButton { text: qsTr("1 day") autoExclusive: true + checked: root.trafficGraphScale === 3600 * 24 bgRadius: 3 textColor: Theme.color.neutral9 textActiveColor: Theme.color.neutral0 @@ -97,8 +108,8 @@ InformationPage { bgDefaultColor: Theme.color.neutral3 onClicked: { - root.maxSamples = 3600 * 24 - networkTrafficTower.updateFilterWindowSize(root.maxSamples / 10) + root.trafficGraphScale = 3600 * 24 + networkTrafficTower.updateFilterWindowSize(root.trafficGraphScale / 10) } } } @@ -118,7 +129,7 @@ InformationPage { fillColor: Theme.color.green lineColor: Theme.color.green markerLineColor: Theme.color.neutral2 - maxSamples: root.maxSamples + maxSamples: root.trafficGraphScale maxValue: networkTrafficTower.maxReceivedRateBps valueList: networkTrafficTower.receivedRateList maxRateBps: networkTrafficTower.maxReceivedRateBps @@ -137,7 +148,7 @@ InformationPage { fillColor: Theme.color.blue lineColor: Theme.color.blue markerLineColor: Theme.color.neutral2 - maxSamples: root.maxSamples + maxSamples: root.trafficGraphScale maxValue: networkTrafficTower.maxSentRateBps valueList: networkTrafficTower.sentRateList maxRateBps: networkTrafficTower.maxSentRateBps diff --git a/src/qml/pages/node/NodeRunner.qml b/src/qml/pages/node/NodeRunner.qml index eda06f3ce6..dfea71e27c 100644 --- a/src/qml/pages/node/NodeRunner.qml +++ b/src/qml/pages/node/NodeRunner.qml @@ -18,14 +18,9 @@ Page { Component.onCompleted: nodeModel.startNodeInitializionThread(); - ColumnLayout { - spacing: 30 + BlockClock { + parentWidth: parent.width - 40 + parentHeight: parent.height anchors.centerIn: parent - BlockClock { - Layout.alignment: Qt.AlignCenter - } - NetworkIndicator { - Layout.alignment: Qt.AlignCenter - } } } diff --git a/src/qml/pages/node/NodeSettings.qml b/src/qml/pages/node/NodeSettings.qml index 3acf27a0d1..5b0c25e637 100644 --- a/src/qml/pages/node/NodeSettings.qml +++ b/src/qml/pages/node/NodeSettings.qml @@ -38,23 +38,26 @@ Item { width: Math.min(parent.width, 450) anchors.horizontalCenter: parent.horizontalCenter Setting { + id: gotoAbout Layout.fillWidth: true - header: qsTr("Dark Mode") - actionItem: OptionSwitch { - checked: Theme.dark - onToggled: Theme.toggleDark() + header: qsTr("About") + actionItem: CaretRightButton { + stateColor: gotoAbout.stateColor + onClicked: { + nodeSettingsView.push(about_page) + } } - onClicked: loadedItem.toggled() + onClicked: loadedItem.clicked() } Separator { Layout.fillWidth: true } Setting { - id: gotoAbout + id: gotoDisplay Layout.fillWidth: true - header: qsTr("About") + header: qsTr("Display") actionItem: CaretRightButton { - stateColor: gotoAbout.stateColor + stateColor: gotoDisplay.stateColor onClicked: { - nodeSettingsView.push(about_page) + nodeSettingsView.push(display_page) } } onClicked: loadedItem.clicked() @@ -127,6 +130,18 @@ Item { } } } + Component { + id: display_page + SettingsDisplay { + navLeftDetail: NavButton { + iconSource: "image://images/caret-left" + text: qsTr("Back") + onClicked: { + nodeSettingsView.pop() + } + } + } + } Component { id: storage_page SettingsStorage { @@ -162,6 +177,11 @@ Item { peerTableModel.stopAutoRefresh(); } } + navMiddleDetail: Header { + headerBold: true + headerSize: 18 + header: qsTr("Peers") + } } } Component { diff --git a/src/qml/pages/node/Peers.qml b/src/qml/pages/node/Peers.qml index 67c3881b91..2dca9afa42 100644 --- a/src/qml/pages/node/Peers.qml +++ b/src/qml/pages/node/Peers.qml @@ -11,92 +11,92 @@ import "../../components" Page { background: null property alias navLeftDetail: navbar.leftDetail + property alias navMiddleDetail: navbar.middleDetail header: NavigationBar { id: navbar } - CoreText { - anchors.top: parent.top - anchors.horizontalCenter: parent.horizontalCenter - id: description - width: Math.min(parent.width - 40, 450) - text: qsTr("Peers are nodes you are connected to. You want to ensure that you are connected" + - " to x, y and z, but not a, b, and c. Learn more.") - font.pixelSize: 13 - color: Theme.color.neutral7 - } - - Flickable { - id: sortSelection - anchors.top: description.bottom - anchors.topMargin: 20 - anchors.horizontalCenter: parent.horizontalCenter - width: Math.min(parent.width - 40, toggleButtons.width) - height: toggleButtons.height - contentWidth: toggleButtons.width - boundsMovement: width == toggleButtons.width ? - Flickable.StopAtBounds : Flickable.FollowBoundsBehavior - RowLayout { - id: toggleButtons - spacing: 10 - ToggleButton { - text: qsTr("ID") - autoExclusive: true - checked: true - onClicked: { - peerListModelProxy.sortBy = "nodeId" - } - } - ToggleButton { - text: qsTr("Direction") - autoExclusive: true - onClicked: { - peerListModelProxy.sortBy = "direction" - } - } - ToggleButton { - text: qsTr("User Agent") - autoExclusive: true - onClicked: { - peerListModelProxy.sortBy = "subversion" - } - } - ToggleButton { - text: qsTr("Type") - autoExclusive: true - onClicked: { - peerListModelProxy.sortBy = "connectionType" - } - } - ToggleButton { - text: qsTr("Ip") - autoExclusive: true - onClicked: { - peerListModelProxy.sortBy = "address" - } - } - ToggleButton { - text: qsTr("Network") - autoExclusive: true - onClicked: { - peerListModelProxy.sortBy = "network" - } - } - } - } - ListView { id: listView clip: true width: Math.min(parent.width - 40, 450) - anchors.top: sortSelection.bottom - anchors.topMargin: 30 - anchors.bottom: parent.bottom + height: parent.height anchors.horizontalCenter: parent.horizontalCenter model: peerListModelProxy spacing: 15 + header: ColumnLayout { + spacing: 20 + width: parent.width + CoreText { + id: description + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter + text: qsTr("Peers are nodes you exchange data with.") + font.pixelSize: 13 + color: Theme.color.neutral7 + } + + Flickable { + id: sortSelection + Layout.fillWidth: true + Layout.bottomMargin: 30 + Layout.alignment: Qt.AlignHCenter + height: toggleButtons.height + contentWidth: toggleButtons.width + boundsMovement: width == toggleButtons.width ? + Flickable.StopAtBound : Flickable.FollowBoundsBehavior + RowLayout { + id: toggleButtons + spacing: 10 + ToggleButton { + text: qsTr("ID") + autoExclusive: true + checked: true + onClicked: { + peerListModelProxy.sortBy = "nodeId" + } + } + ToggleButton { + text: qsTr("Direction") + autoExclusive: true + onClicked: { + peerListModelProxy.sortBy = "direction" + } + } + ToggleButton { + text: qsTr("User Agent") + autoExclusive: true + onClicked: { + peerListModelProxy.sortBy = "subversion" + } + } + ToggleButton { + text: qsTr("Type") + autoExclusive: true + onClicked: { + peerListModelProxy.sortBy = "connectionType" + } + } + ToggleButton { + text: qsTr("Ip") + autoExclusive: true + onClicked: { + peerListModelProxy.sortBy = "address" + } + } + ToggleButton { + text: qsTr("Network") + autoExclusive: true + onClicked: { + peerListModelProxy.sortBy = "network" + } + } + } + } + } + footer: Loader { height: 75 active: nodeModel.numOutboundPeers < nodeModel.maxNumOutboundPeers diff --git a/src/qml/pages/settings/SettingsBlockClockDisplayMode.qml b/src/qml/pages/settings/SettingsBlockClockDisplayMode.qml new file mode 100644 index 0000000000..6a2c134e66 --- /dev/null +++ b/src/qml/pages/settings/SettingsBlockClockDisplayMode.qml @@ -0,0 +1,18 @@ +// Copyright (c) 2023 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" + +InformationPage { + bannerActive: false + bold: true + headerText: qsTr("Block Clock display mode") + headerMargin: 0 + detailActive: true + detailItem: BlockClockDisplayMode {} +} diff --git a/src/qml/pages/settings/SettingsDisplay.qml b/src/qml/pages/settings/SettingsDisplay.qml new file mode 100644 index 0000000000..b4c953fe95 --- /dev/null +++ b/src/qml/pages/settings/SettingsDisplay.qml @@ -0,0 +1,88 @@ +// Copyright (c) 2023 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" + +Item { + property alias navLeftDetail: displaySettingsView.navLeftDetail + StackView { + id: displaySettingsView + property alias navLeftDetail: displaySettings.navLeftDetail + property bool newcompilebool: false + anchors.fill: parent + + + initialItem: Page { + id: displaySettings + property alias navLeftDetail: navbar.leftDetail + background: null + implicitWidth: 450 + leftPadding: 20 + rightPadding: 20 + topPadding: 30 + + header: NavigationBar { + id: navbar + } + ColumnLayout { + spacing: 4 + width: Math.min(parent.width, 450) + anchors.horizontalCenter: parent.horizontalCenter + Setting { + id: gotoTheme + Layout.fillWidth: true + header: qsTr("Theme") + actionItem: CaretRightButton { + stateColor: gotoTheme.stateColor + onClicked: { + nodeSettingsView.push(theme_page) + } + } + onClicked: loadedItem.clicked() + } + Separator { Layout.fillWidth: true } + Setting { + id: gotoBlockClockSize + Layout.fillWidth: true + header: qsTr("Block Clock display mode") + actionItem: CaretRightButton { + stateColor: gotoBlockClockSize.stateColor + onClicked: { + nodeSettingsView.push(blockclocksize_page) + } + } + onClicked: loadedItem.clicked() + } + } + } + } + Component { + id: theme_page + SettingsTheme { + navLeftDetail: NavButton { + iconSource: "image://images/caret-left" + text: qsTr("Back") + onClicked: { + nodeSettingsView.pop() + } + } + } + } + Component { + id: blockclocksize_page + SettingsBlockClockDisplayMode { + navLeftDetail: NavButton { + iconSource: "image://images/caret-left" + text: qsTr("Back") + onClicked: { + nodeSettingsView.pop() + } + } + } + } +} diff --git a/src/qml/pages/settings/SettingsTheme.qml b/src/qml/pages/settings/SettingsTheme.qml new file mode 100644 index 0000000000..cfe1259fdc --- /dev/null +++ b/src/qml/pages/settings/SettingsTheme.qml @@ -0,0 +1,18 @@ +// Copyright (c) 2023 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" + +InformationPage { + bannerActive: false + bold: true + headerText: qsTr("Theme") + headerMargin: 0 + detailActive: true + detailItem: ThemeSettings {} +} diff --git a/src/qml/res/icons/bitcoin-circle.png b/src/qml/res/icons/bitcoin-circle.png index dac55c3610..9535609393 100644 Binary files a/src/qml/res/icons/bitcoin-circle.png and b/src/qml/res/icons/bitcoin-circle.png differ diff --git a/src/qt/android/AndroidManifest.xml b/src/qt/android/AndroidManifest.xml index 8d2c1ac901..b7a4bf0231 100644 --- a/src/qt/android/AndroidManifest.xml +++ b/src/qt/android/AndroidManifest.xml @@ -24,7 +24,7 @@ - +