From dba6b751cc9bfdcc5e9afe957e60e7a922707e4b Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Fri, 20 Feb 2026 10:13:49 +0100 Subject: [PATCH 01/28] android: update for rebase p4a, update qt to 6.10, ndk to 28 p4a rebased on spesmilo/electrum_202602, 375a05de21b538d704174b1efeb3fc85d151f94e --- contrib/android/Dockerfile | 45 +++++++------------ contrib/android/buildozer_qml.spec | 4 +- contrib/android/p4a_recipes/cffi/__init__.py | 7 ++- .../p4a_recipes/hostpython3/__init__.py | 2 +- .../android/p4a_recipes/openssl/__init__.py | 5 +-- .../android/p4a_recipes/pyjnius/__init__.py | 7 ++- contrib/android/p4a_recipes/pyqt6/__init__.py | 6 +-- .../android/p4a_recipes/pyqt6sip/__init__.py | 4 +- .../p4a_recipes/pyqt_builder/__init__.py | 5 +-- .../android/p4a_recipes/python3/__init__.py | 2 +- contrib/android/p4a_recipes/qt6/__init__.py | 8 ++-- .../p4a_recipes/setuptools/__init__.py | 4 +- contrib/android/p4a_recipes/sip/__init__.py | 6 +-- 13 files changed, 42 insertions(+), 63 deletions(-) diff --git a/contrib/android/Dockerfile b/contrib/android/Dockerfile index d462bd4d2..3520ffe76 100644 --- a/contrib/android/Dockerfile +++ b/contrib/android/Dockerfile @@ -33,12 +33,8 @@ RUN apt -y update -qq \ ENV ANDROID_NDK_HOME="${ANDROID_HOME}/android-ndk" -#ENV ANDROID_NDK_VERSION="23b" -#ENV ANDROID_NDK_HASH="c6e97f9c8cfe5b7be0a9e6c15af8e7a179475b7ded23e2d1c1fa0945d6fb4382" -#ENV ANDROID_NDK_VERSION="27d" -#ENV ANDROID_NDK_HASH="601246087a682d1944e1e16dd85bc6e49560fe8b6d61255be2829178c8ed15d9" -ENV ANDROID_NDK_VERSION="23d-canary" -ENV ANDROID_NDK_HASH="6944ffc20ab018ff4ef6a403048d0a99d50a0630c3eae690c8f803c452f46f3e" +ENV ANDROID_NDK_VERSION="28c" +ENV ANDROID_NDK_HASH="dfb20d396df28ca02a8c708314b814a4d961dc9074f9a161932746f815aa552f" ENV ANDROID_NDK_HOME_V="${ANDROID_NDK_HOME}-r${ANDROID_NDK_VERSION}" # get the latest version from https://developer.android.com/ndk/downloads/index.html @@ -48,31 +44,21 @@ ENV ANDROID_NDK_DL_URL="https://dl.google.com/android/repository/${ANDROID_NDK_A # below disabled in favor of CI build download # download and install Android NDK -#RUN curl --location --progress-bar \ -# "${ANDROID_NDK_DL_URL}" \ -# --output "${ANDROID_NDK_ARCHIVE}" \ -# && echo "${ANDROID_NDK_HASH} ${ANDROID_NDK_ARCHIVE}" | sha256sum -c - \ -# && mkdir --parents "${ANDROID_NDK_HOME_V}" \ -# && unzip -q "${ANDROID_NDK_ARCHIVE}" -d "${ANDROID_HOME}" \ -# && ln -sfn "${ANDROID_NDK_HOME_V}" "${ANDROID_NDK_HOME}" \ -# && rm -rf "${ANDROID_NDK_ARCHIVE}" - -# temporary build using NDK from CI -ENV CI_REV="12186248" -ENV CI_NDK_FILE="android-ndk-${CI_REV}-linux-x86_64.zip" -COPY contrib/android/dl-ndk-ci.sh /tmp/ -RUN /tmp/dl-ndk-ci.sh https://ci.android.com/builds/submitted/${CI_REV}/linux/latest/${CI_NDK_FILE} \ - && echo "${ANDROID_NDK_HASH} android-ndk-ci-linux-x86_64.zip" | sha256sum -c - \ +RUN curl --location --progress-bar \ + "${ANDROID_NDK_DL_URL}" \ + --output "${ANDROID_NDK_ARCHIVE}" \ + && echo "${ANDROID_NDK_HASH} ${ANDROID_NDK_ARCHIVE}" | sha256sum -c - \ && mkdir --parents "${ANDROID_NDK_HOME_V}" \ - && unzip -q "android-ndk-ci-linux-x86_64.zip" -d "${ANDROID_HOME}" \ + && unzip -q "${ANDROID_NDK_ARCHIVE}" -d "${ANDROID_HOME}" \ && ln -sfn "${ANDROID_NDK_HOME_V}" "${ANDROID_NDK_HOME}" \ - && rm -rf "android-ndk-ci-linux-x86_64.zip" + && rm -rf "${ANDROID_NDK_ARCHIVE}" ENV ANDROID_SDK_HOME="${ANDROID_HOME}/android-sdk" # get the latest version from https://developer.android.com/studio/index.html -ENV ANDROID_SDK_TOOLS_VERSION="9477386" -ENV ANDROID_SDK_HASH="bd1aa17c7ef10066949c88dc6c9c8d536be27f992a1f3b5a584f9bd2ba5646a0" +ENV ANDROID_SDK_TOOLS_VERSION="14742923" +ENV ANDROID_SDK_HASH="04453066b540409d975c676d781da1477479dde3761310f1a7eb92a1dfb15af7" + ENV ANDROID_SDK_TOOLS_ARCHIVE="commandlinetools-linux-${ANDROID_SDK_TOOLS_VERSION}_latest.zip" ENV ANDROID_SDK_TOOLS_DL_URL="https://dl.google.com/android/repository/${ANDROID_SDK_TOOLS_ARCHIVE}" ENV ANDROID_SDK_MANAGER="${ANDROID_SDK_HOME}/cmdline-tools/bin/sdkmanager --sdk_root=${ANDROID_SDK_HOME}" @@ -130,8 +116,8 @@ RUN apt -y update -qq \ RUN yes | ${ANDROID_SDK_MANAGER} --licenses > /dev/null -ENV ANDROID_SDK_BUILD_TOOLS_MAJOR_V="31" -ENV ANDROID_SDK_BUILD_TOOLS_VERSION="31.0.0" +ENV ANDROID_SDK_BUILD_TOOLS_MAJOR_V="35" +ENV ANDROID_SDK_BUILD_TOOLS_VERSION="35.0.0" # download platforms, API, build tools RUN ${ANDROID_SDK_MANAGER} "platforms;android-${ANDROID_SDK_BUILD_TOOLS_MAJOR_V}" > /dev/null && \ @@ -190,7 +176,6 @@ RUN apt -y update -qq \ && apt -y install -qq --no-install-recommends --allow-downgrades \ libopengl-dev \ libegl-dev \ - dos2unix \ && apt -y autoremove \ && apt -y clean @@ -250,8 +235,8 @@ RUN cd /opt \ && /opt/venv/bin/python3 -m pip install --no-build-isolation --no-dependencies -e . # install python-for-android -ENV P4A_CHECKOUT_COMMIT="a01269f7799587ad74ee40e0b642d917b8db7d4e" -# ^ from branch electrum_20251211 (note: careful with force-pushing! see #8162) +ENV P4A_CHECKOUT_COMMIT="375a05de21b538d704174b1efeb3fc85d151f94e" +# ^ from branch electrum_202602 (note: careful with force-pushing! see #8162) RUN cd /opt \ && git clone https://github.com/spesmilo/python-for-android \ && cd python-for-android \ diff --git a/contrib/android/buildozer_qml.spec b/contrib/android/buildozer_qml.spec index 454618176..2c6120af8 100644 --- a/contrib/android/buildozer_qml.spec +++ b/contrib/android/buildozer_qml.spec @@ -105,7 +105,7 @@ android.permissions = INTERNET, CAMERA, WRITE_EXTERNAL_STORAGE, POST_NOTIFICATIO # (int) Android API to use (compileSdkVersion) # note: when changing, Dockerfile also needs to be changed to install corresponding build tools -android.api = 31 +android.api = 35 # (int) Android targetSdkVersion android.target_sdk_version = 35 @@ -114,7 +114,7 @@ android.target_sdk_version = 35 android.minapi = 23 # (str) Android NDK version to use -android.ndk = 23b +android.ndk = 28c # (int) Android NDK API to use (optional). This is the minimum API your app will support. android.ndk_api = 23 diff --git a/contrib/android/p4a_recipes/cffi/__init__.py b/contrib/android/p4a_recipes/cffi/__init__.py index c96c7ac51..a5bbf2b32 100644 --- a/contrib/android/p4a_recipes/cffi/__init__.py +++ b/contrib/android/p4a_recipes/cffi/__init__.py @@ -6,14 +6,13 @@ from pythonforandroid.util import load_source util = load_source('util', os.path.join(os.path.dirname(os.path.dirname(__file__)), 'util.py')) -assert CffiRecipe._version == "1.15.1" -assert CffiRecipe.depends == ['setuptools', 'pycparser', 'libffi', 'python3'] +assert CffiRecipe._version == "2.0.0" +assert CffiRecipe.depends == ['pycparser', 'libffi', 'python3'], CffiRecipe.depends assert CffiRecipe.python_depends == [] class CffiRecipePinned(util.InheritedRecipeMixin, CffiRecipe): - version = "1.17.1" - sha512sum = "907129891d56351ca5cb885aae62334ad432321826d6eddfaa32195b4c7b7689a80333e6d14d0aab479a646aba148b9852c0815b80344dfffa4f183a5e74372c" + sha512sum = "a71b74e642e11eb50e9bb4ae0e7116bdb3c4a7c9622a3766d84506fa7994c02e09644b41b439b95ca99b0303e91891897cff38018d498eb087e0961f0ad4fb8b" recipe = CffiRecipePinned() diff --git a/contrib/android/p4a_recipes/hostpython3/__init__.py b/contrib/android/p4a_recipes/hostpython3/__init__.py index a9a1be3eb..9d1dcb89b 100644 --- a/contrib/android/p4a_recipes/hostpython3/__init__.py +++ b/contrib/android/p4a_recipes/hostpython3/__init__.py @@ -13,7 +13,7 @@ assert HostPython3Recipe.python_depends == [] class HostPython3RecipePinned(util.InheritedRecipeMixin, HostPython3Recipe): # PYTHON_VERSION= # < line here so that I can grep the codebase and teleport here version = "3.11.14" - sha512sum = "41fb3ae22ce4ac0e8bb6b9ae8db88a810af1001d944e3f1abc9e86824ae4be31347e3e3a70425ab12271c6b7eeef552f00164ef23cfffa2551c3c9d1fe5ab91f" + sha512sum = "4642f6d59c76c6e5dbd827fdb28694376a9cc76e513146d092b49afb41513b3c9dff2339cfcebfb5b260f5cdc49a59a69906e284e5d478b2189d3374e9e24fd5" recipe = HostPython3RecipePinned() diff --git a/contrib/android/p4a_recipes/openssl/__init__.py b/contrib/android/p4a_recipes/openssl/__init__.py index fdbb7d662..501d0bcd0 100644 --- a/contrib/android/p4a_recipes/openssl/__init__.py +++ b/contrib/android/p4a_recipes/openssl/__init__.py @@ -6,14 +6,13 @@ from pythonforandroid.util import load_source util = load_source('util', os.path.join(os.path.dirname(os.path.dirname(__file__)), 'util.py')) -assert OpenSSLRecipe._version == "3.0.18" +assert OpenSSLRecipe._version == "3.3.1" assert OpenSSLRecipe.depends == [] assert OpenSSLRecipe.python_depends == [] class OpenSSLRecipePinned(util.InheritedRecipeMixin, OpenSSLRecipe): - version = "3.0.18" - sha512sum = "6bdd16f33b83ae2a12777230c4ff00d0595bbc00253ac8c3ac31e1375e818fc74d7f491bd2e507ff33cab9f0498cfb28fa8690f75a98663568d40901523cdf3c" + sha512sum = "d3682a5ae0721748c6b9ec2f1b74d2b1ba61ee6e4c0d42387b5037a56ef34312833b6abb522d19400b45d807dd65cc834156f5e891cb07fbaf69fcf67e1c595d" recipe = OpenSSLRecipePinned() diff --git a/contrib/android/p4a_recipes/pyjnius/__init__.py b/contrib/android/p4a_recipes/pyjnius/__init__.py index 16a052e0e..7e850c922 100644 --- a/contrib/android/p4a_recipes/pyjnius/__init__.py +++ b/contrib/android/p4a_recipes/pyjnius/__init__.py @@ -6,14 +6,13 @@ from pythonforandroid.util import load_source util = load_source('util', os.path.join(os.path.dirname(os.path.dirname(__file__)), 'util.py')) -assert PyjniusRecipe._version == "1.5.0" -assert PyjniusRecipe.depends == [('genericndkbuild', 'sdl2', 'qt6'), 'six', 'python3'] +assert PyjniusRecipe._version == "1.7.0" +assert PyjniusRecipe.depends == [('genericndkbuild', 'sdl2', 'sdl3', 'qt6'), 'six', 'python3'], PyjniusRecipe.depends assert PyjniusRecipe.python_depends == [] class PyjniusRecipePinned(util.InheritedRecipeMixin, PyjniusRecipe): - version = "1.6.1" - sha512sum = "deb5ac566479111c6f4c6adb895821b263d72bf88414fb093bdfd5ad5d0b7aea56b53d5ef0967e28db360f4fb6fb1c2264123f15c747884799df55848191c424" + sha512sum = "a192c30ef87ca9601455976feb49f03dfdb8e1bf2545744a7b771a6d0930a56b334c7a2a39d30fb8855c070f16e4673dc5ff6920b04a6155ab5f9247b271df76" recipe = PyjniusRecipePinned() diff --git a/contrib/android/p4a_recipes/pyqt6/__init__.py b/contrib/android/p4a_recipes/pyqt6/__init__.py index 37b2a2119..2ca63803e 100644 --- a/contrib/android/p4a_recipes/pyqt6/__init__.py +++ b/contrib/android/p4a_recipes/pyqt6/__init__.py @@ -6,13 +6,13 @@ from pythonforandroid.util import load_source util = load_source('util', os.path.join(os.path.dirname(os.path.dirname(__file__)), 'util.py')) -assert PyQt6Recipe._version == "6.4.2" -assert PyQt6Recipe.depends == ['qt6', 'pyjnius', 'setuptools', 'pyqt6sip', 'hostpython3', 'pyqt_builder'] +assert PyQt6Recipe._version == "6.10.1" +assert PyQt6Recipe.depends == ['qt6', 'pyjnius', 'setuptools', 'pyqt6sip', 'hostpython3', 'python3'], PyQt6Recipe.depends assert PyQt6Recipe.python_depends == [] class PyQt6RecipePinned(util.InheritedRecipeMixin, PyQt6Recipe): - sha512sum = "51e5f0d028ee7984876da1653cb135d61e2c402f18b939a92477888cc7c86d3bc2889477403dee6b3d9f66519ee3236d344323493b4c2c2e658e1637b10e53bf" + sha512sum = "af9bb54b20fd177cf1dac5fe8fb0ff289e1e7e42716d09093d49dd99a7d8065c6b6f34784ed19e21e7e07ba0d550b270cb6be7273f7180e2bf886160fc773d01" recipe = PyQt6RecipePinned() diff --git a/contrib/android/p4a_recipes/pyqt6sip/__init__.py b/contrib/android/p4a_recipes/pyqt6sip/__init__.py index 301c15762..6c354250e 100644 --- a/contrib/android/p4a_recipes/pyqt6sip/__init__.py +++ b/contrib/android/p4a_recipes/pyqt6sip/__init__.py @@ -6,13 +6,13 @@ from pythonforandroid.util import load_source util = load_source('util', os.path.join(os.path.dirname(os.path.dirname(__file__)), 'util.py')) -assert PyQt6SipRecipe._version == "13.5.1" +assert PyQt6SipRecipe._version == "13.10.3" assert PyQt6SipRecipe.depends == ['setuptools', 'python3'] assert PyQt6SipRecipe.python_depends == [] class PyQt6SipRecipePinned(util.InheritedRecipeMixin, PyQt6SipRecipe): - sha512sum = "1e4170d167a326afe6df86e4a35e209299548054981cb2e5d56da234ef9db4d8594bcb05b6be363c3bc6252776ae9de63d589a3d9f33fba8250d39cdb5e9061a" + sha512sum = "555b061eec3db6a66388fae07de21f58d756f6f12b13e4ede729c3348d2c8997ac5a59d3006ee45c3a09b5cde673f579265fa254bc583a4ba721748cf8f3a617" recipe = PyQt6SipRecipePinned() diff --git a/contrib/android/p4a_recipes/pyqt_builder/__init__.py b/contrib/android/p4a_recipes/pyqt_builder/__init__.py index 6b6a67488..516dc99aa 100644 --- a/contrib/android/p4a_recipes/pyqt_builder/__init__.py +++ b/contrib/android/p4a_recipes/pyqt_builder/__init__.py @@ -1,13 +1,12 @@ from pythonforandroid.recipes.pyqt_builder import PyQtBuilderRecipe -assert PyQtBuilderRecipe._version == "1.15.1" +assert PyQtBuilderRecipe._version == "1.19.1" assert PyQtBuilderRecipe.depends == ["sip", "packaging", "python3"] assert PyQtBuilderRecipe.python_depends == [] class PyQtBuilderRecipePinned(PyQtBuilderRecipe): - sha512sum = "61ee73b6bb922c04739da60025ab50d35d345d2e298943305fcbd3926cda31d732cc5e5b0dbfc39f5eb85c0f0b091b8c3f5fee00dcc240d7849c5c4191c1368a" - + sha512sum = "2308c51f93c37b1d13f312e4f2475d26b22d374ef284925fead9eab4aa89b994770431aca45170ac2154b4813fff151798f113f56d4cbf6c6e544fb463104a6d" recipe = PyQtBuilderRecipePinned() diff --git a/contrib/android/p4a_recipes/python3/__init__.py b/contrib/android/p4a_recipes/python3/__init__.py index 6fc9d51e8..11cf39546 100644 --- a/contrib/android/p4a_recipes/python3/__init__.py +++ b/contrib/android/p4a_recipes/python3/__init__.py @@ -13,7 +13,7 @@ assert Python3Recipe.python_depends == [] class Python3RecipePinned(util.InheritedRecipeMixin, Python3Recipe): # PYTHON_VERSION= # < line here so that I can grep the codebase and teleport here version = "3.11.14" - sha512sum = "41fb3ae22ce4ac0e8bb6b9ae8db88a810af1001d944e3f1abc9e86824ae4be31347e3e3a70425ab12271c6b7eeef552f00164ef23cfffa2551c3c9d1fe5ab91f" + sha512sum = "4642f6d59c76c6e5dbd827fdb28694376a9cc76e513146d092b49afb41513b3c9dff2339cfcebfb5b260f5cdc49a59a69906e284e5d478b2189d3374e9e24fd5" recipe = Python3RecipePinned() diff --git a/contrib/android/p4a_recipes/qt6/__init__.py b/contrib/android/p4a_recipes/qt6/__init__.py index 1a91e0bf3..741826e7c 100644 --- a/contrib/android/p4a_recipes/qt6/__init__.py +++ b/contrib/android/p4a_recipes/qt6/__init__.py @@ -1,19 +1,17 @@ import os from pythonforandroid.recipes.qt6 import Qt6Recipe - from pythonforandroid.util import load_source util = load_source('util', os.path.join(os.path.dirname(os.path.dirname(__file__)), 'util.py')) -assert Qt6Recipe._version == "6.4.3" -# assert Qt6Recipe._version == "6.5.3" +assert Qt6Recipe._version == "6.10.1" assert Qt6Recipe.depends == ['python3', 'hostqt6'] assert Qt6Recipe.python_depends == [] + class Qt6RecipePinned(util.InheritedRecipeMixin, Qt6Recipe): - sha512sum = "0bdbe8b9a43390c98cf19e851ec5394bc78438d227cf9d0d7a3748aee9a32a7f14fc46f52d4fa283819f21413567080aee7225c566af5278557f5e1992674da3" - # sha512sum = "ca8ea3b81c121886636988275f7fa8ae6d19f7be02669e63ab19b4285b611057a41279db9532c25ae87baa3904b010e1db68b899cd0eda17a5a8d3d87098b4d5" + sha512sum = "62e8a8fcdef84187bff43e6185a1ba983e3db4d927ec01cd0ff5247d12eb7fd116a8f67323b3e44ba23f2e1792ade8c54e033cf28f34ec42a776ec204b9c2d8d" recipe = Qt6RecipePinned() diff --git a/contrib/android/p4a_recipes/setuptools/__init__.py b/contrib/android/p4a_recipes/setuptools/__init__.py index 10a26f398..23c3596aa 100644 --- a/contrib/android/p4a_recipes/setuptools/__init__.py +++ b/contrib/android/p4a_recipes/setuptools/__init__.py @@ -1,13 +1,13 @@ from pythonforandroid.recipes.setuptools import SetuptoolsRecipe -assert SetuptoolsRecipe._version == "51.3.3" +assert SetuptoolsRecipe._version == "80.9.0" assert SetuptoolsRecipe.depends == ['python3'] assert SetuptoolsRecipe.python_depends == [] class SetuptoolsRecipePinned(SetuptoolsRecipe): - sha512sum = "5a3572466a68c6f650111448ce3343f64c62044650bb8635edbff97e2bc7b216b8bbe3b4e3bccf34e6887f3bedc911b27ca5f9a515201cae49cf44fbacf03345" + sha512sum = "36eb1f219d29c6b9e135936bde2001ad70a971c8069cd0175d3a5325b450e6843a903d3f70043c9f534768ebeab8ab0c544b8f44456555d333f1ed72daa5c18b" recipe = SetuptoolsRecipePinned() diff --git a/contrib/android/p4a_recipes/sip/__init__.py b/contrib/android/p4a_recipes/sip/__init__.py index d1aea65e4..417c9be16 100644 --- a/contrib/android/p4a_recipes/sip/__init__.py +++ b/contrib/android/p4a_recipes/sip/__init__.py @@ -1,13 +1,13 @@ from pythonforandroid.recipes.sip import SipRecipe -assert SipRecipe._version == "6.7.9" -assert SipRecipe.depends == ["setuptools", "packaging", "tomli", "ply", "python3"], SipRecipe.depends +assert SipRecipe._version == "6.15.1" +assert SipRecipe.depends == ["setuptools", "packaging", "tomli", "python3"], SipRecipe.depends assert SipRecipe.python_depends == [] class SipRecipePinned(SipRecipe): - sha512sum = "bb9d0d0d92002b6fd33f7e8ebe8cd62456dacc16b5734b73760b1ba14fb9b1f2b9b6640b40196c6cf5f345e1afde48bdef39675c4d3480041771325d4cf3c233" + sha512sum = "30a312419ba82c0221c0cf03c3fb3ad7d45bb8fe633d1d7477025a7986b0a7f7b7b781a8d9cd6bcdb78f3b872231fd1eed123a761b497861822f2e35093f574d" recipe = SipRecipePinned() From 9772a6d5b672e4f7807b5e9f3610337531dfcf2c Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Mon, 16 Feb 2026 14:57:30 +0100 Subject: [PATCH 02/28] qml: add workarounds for issue assigning custom types to QObject properties - on the python side, for pyqtProperty's with a setter, the pyqtProperty should be declared as QVariant type - on the qml side, properties should be declared 'var', not the custom type. --- .../components/ChannelOpenProgressDialog.qml | 2 +- .../gui/qml/components/ConfirmTxDialog.qml | 2 +- electrum/gui/qml/components/Constants.qml | 1 + electrum/gui/qml/components/InvoiceDialog.qml | 4 +-- .../qml/components/LnurlPayRequestDialog.qml | 2 +- .../components/LnurlWithdrawRequestDialog.qml | 4 +-- .../gui/qml/components/OpenChannelDialog.qml | 2 +- electrum/gui/qml/components/SendDialog.qml | 4 +-- .../gui/qml/components/WalletMainView.qml | 1 + .../gui/qml/components/controls/BtcField.qml | 2 +- .../qml/components/controls/ChannelBar.qml | 10 +++--- .../components/controls/FormattedAmount.qml | 2 +- electrum/gui/qml/components/main.qml | 2 +- electrum/gui/qml/qeaddressdetails.py | 5 +-- electrum/gui/qml/qechanneldetails.py | 5 +-- electrum/gui/qml/qechannellistmodel.py | 10 ++++-- electrum/gui/qml/qechannelopener.py | 9 ++--- electrum/gui/qml/qeinvoice.py | 8 +++-- electrum/gui/qml/qelnpaymentdetails.py | 5 +-- electrum/gui/qml/qepiresolver.py | 5 +-- electrum/gui/qml/qerequestdetails.py | 5 +-- electrum/gui/qml/qeswaphelper.py | 17 ++++++---- electrum/gui/qml/qetxdetails.py | 5 +-- electrum/gui/qml/qetxfinalizer.py | 33 +++++++++++-------- 24 files changed, 86 insertions(+), 59 deletions(-) diff --git a/electrum/gui/qml/components/ChannelOpenProgressDialog.qml b/electrum/gui/qml/components/ChannelOpenProgressDialog.qml index 6179dd4c0..3cea2e135 100644 --- a/electrum/gui/qml/components/ChannelOpenProgressDialog.qml +++ b/electrum/gui/qml/components/ChannelOpenProgressDialog.qml @@ -23,7 +23,7 @@ ElDialog { property string channelBackup - function reset() { + function resetDialog() { state = '' errorText.text = '' peerText.text = '' diff --git a/electrum/gui/qml/components/ConfirmTxDialog.qml b/electrum/gui/qml/components/ConfirmTxDialog.qml index f51a7f365..94617d305 100644 --- a/electrum/gui/qml/components/ConfirmTxDialog.qml +++ b/electrum/gui/qml/components/ConfirmTxDialog.qml @@ -11,7 +11,7 @@ ElDialog { id: dialog required property QtObject finalizer - required property Amount satoshis + required property var satoshis // type: Amount property string address property string message property bool showOptions: true diff --git a/electrum/gui/qml/components/Constants.qml b/electrum/gui/qml/components/Constants.qml index 95d79a479..7935a8c5d 100644 --- a/electrum/gui/qml/components/Constants.qml +++ b/electrum/gui/qml/components/Constants.qml @@ -31,6 +31,7 @@ Item { property color darkerBackground: Qt.darker(Material.background, 1.20) property color lighterBackground: Qt.lighter(Material.background, 1.10) property color darkerDialogBackground: Qt.darker(Material.dialogColor, 1.20) + property color dialogColor: Material.dialogColor property color notificationBackground: Qt.lighter(Material.background, 1.5) property color colorCredit: "#ff80ff80" diff --git a/electrum/gui/qml/components/InvoiceDialog.qml b/electrum/gui/qml/components/InvoiceDialog.qml index 51f41de5a..882c97bf8 100644 --- a/electrum/gui/qml/components/InvoiceDialog.qml +++ b/electrum/gui/qml/components/InvoiceDialog.qml @@ -10,7 +10,7 @@ import "controls" ElDialog { id: dialog - property Invoice invoice + property var invoice // type Invoice property bool payImmediately: false property string broadcastTxid @@ -24,7 +24,7 @@ ElDialog { property bool _canMax: invoice.invoiceType == Invoice.OnchainInvoice - property Amount _invoice_amount: invoice.amount + property var _invoice_amount: invoice.amount // type: Amount ColumnLayout { anchors.fill: parent diff --git a/electrum/gui/qml/components/LnurlPayRequestDialog.qml b/electrum/gui/qml/components/LnurlPayRequestDialog.qml index 338dc08d8..64f45453d 100644 --- a/electrum/gui/qml/components/LnurlPayRequestDialog.qml +++ b/electrum/gui/qml/components/LnurlPayRequestDialog.qml @@ -13,7 +13,7 @@ ElDialog { title: qsTr('LNURL Payment request') iconSource: '../../../icons/link.png' - property InvoiceParser invoiceParser + property var invoiceParser // type: InvoiceParser padding: 0 needsSystemBarPadding: false diff --git a/electrum/gui/qml/components/LnurlWithdrawRequestDialog.qml b/electrum/gui/qml/components/LnurlWithdrawRequestDialog.qml index d5a84c6f1..02a9be479 100644 --- a/electrum/gui/qml/components/LnurlWithdrawRequestDialog.qml +++ b/electrum/gui/qml/components/LnurlWithdrawRequestDialog.qml @@ -13,8 +13,8 @@ ElDialog { title: qsTr('LNURL Withdraw request') iconSource: '../../../icons/link.png' - property Wallet wallet: Daemon.currentWallet - property RequestDetails requestDetails + property var wallet: Daemon.currentWallet // type: Wallet + property var requestDetails // type: RequestDetails padding: 0 needsSystemBarPadding: false diff --git a/electrum/gui/qml/components/OpenChannelDialog.qml b/electrum/gui/qml/components/OpenChannelDialog.qml index 97d709b52..99aa6cd2d 100644 --- a/electrum/gui/qml/components/OpenChannelDialog.qml +++ b/electrum/gui/qml/components/OpenChannelDialog.qml @@ -298,7 +298,7 @@ ElDialog { } onChannelOpening: (peer) => { console.log('Channel is opening') - app.channelOpenProgressDialog.reset() + app.channelOpenProgressDialog.resetDialog() app.channelOpenProgressDialog.peer = peer app.channelOpenProgressDialog.open() } diff --git a/electrum/gui/qml/components/SendDialog.qml b/electrum/gui/qml/components/SendDialog.qml index e17c45ce4..256012ffd 100644 --- a/electrum/gui/qml/components/SendDialog.qml +++ b/electrum/gui/qml/components/SendDialog.qml @@ -11,8 +11,8 @@ import "controls" ElDialog { id: dialog - property InvoiceParser invoiceParser - property PIResolver piResolver + property var invoiceParser // type: InvoiceParser + property var piResolver // type: PIResolver signal txFound(data: string) signal channelBackupFound(data: string) diff --git a/electrum/gui/qml/components/WalletMainView.qml b/electrum/gui/qml/components/WalletMainView.qml index c39cda5ed..593e49f9d 100644 --- a/electrum/gui/qml/components/WalletMainView.qml +++ b/electrum/gui/qml/components/WalletMainView.qml @@ -2,6 +2,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts import QtQuick.Controls.Material +import QtQuick.Controls.Material.impl import QtQml import org.electrum 1.0 diff --git a/electrum/gui/qml/components/controls/BtcField.qml b/electrum/gui/qml/components/controls/BtcField.qml index a42624356..ce11cfe1f 100644 --- a/electrum/gui/qml/components/controls/BtcField.qml +++ b/electrum/gui/qml/components/controls/BtcField.qml @@ -16,7 +16,7 @@ TextField { regularExpression: msatPrecision ? Config.btcAmountRegexMsat : Config.btcAmountRegex } - property Amount textAsSats + property var textAsSats onTextChanged: { textAsSats = Config.unitsToSats(amount.text) if (fiatfield.activeFocus) diff --git a/electrum/gui/qml/components/controls/ChannelBar.qml b/electrum/gui/qml/components/controls/ChannelBar.qml index 8635356f7..4f67dd765 100644 --- a/electrum/gui/qml/components/controls/ChannelBar.qml +++ b/electrum/gui/qml/components/controls/ChannelBar.qml @@ -6,11 +6,11 @@ import QtQuick.Controls.Material import org.electrum 1.0 Item { - property Amount capacity - property Amount localCapacity - property Amount remoteCapacity - property Amount canSend - property Amount canReceive + property var capacity // type: Amount + property var localCapacity // type: Amount + property var remoteCapacity // type: Amount + property var canSend // type: Amount + property var canReceive // type: Amount property bool frozenForSending: false property bool frozenForReceiving: false diff --git a/electrum/gui/qml/components/controls/FormattedAmount.qml b/electrum/gui/qml/components/controls/FormattedAmount.qml index 80d6d778a..43c57e68c 100644 --- a/electrum/gui/qml/components/controls/FormattedAmount.qml +++ b/electrum/gui/qml/components/controls/FormattedAmount.qml @@ -6,7 +6,7 @@ import QtQuick.Controls.Material import org.electrum 1.0 GridLayout { - required property Amount amount + required property var amount // type: Amount property bool showAlt: true property bool singleLine: true property bool valid: true diff --git a/electrum/gui/qml/components/main.qml b/electrum/gui/qml/components/main.qml index 420213092..7de57ff7f 100644 --- a/electrum/gui/qml/components/main.qml +++ b/electrum/gui/qml/components/main.qml @@ -128,7 +128,7 @@ ApplicationWindow background: Rectangle { implicitHeight: 48 - color: Material.dialogColor + color: constants.dialogColor layer.enabled: true layer.effect: ElevationEffect { diff --git a/electrum/gui/qml/qeaddressdetails.py b/electrum/gui/qml/qeaddressdetails.py index 8ff78cf7a..c17f50d4c 100644 --- a/electrum/gui/qml/qeaddressdetails.py +++ b/electrum/gui/qml/qeaddressdetails.py @@ -1,4 +1,4 @@ -from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject +from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QVariant from electrum.logging import get_logger from electrum.util import UserFacingException @@ -35,12 +35,13 @@ class QEAddressDetails(AuthMixin, QObject): self._historyModel = None walletChanged = pyqtSignal() - @pyqtProperty(QEWallet, notify=walletChanged) + @pyqtProperty(QVariant, notify=walletChanged) def wallet(self): return self._wallet @wallet.setter def wallet(self, wallet: QEWallet): + assert isinstance(wallet, QEWallet) if self._wallet != wallet: self._wallet = wallet self.walletChanged.emit() diff --git a/electrum/gui/qml/qechanneldetails.py b/electrum/gui/qml/qechanneldetails.py index 1f74c6fb5..be9c2d525 100644 --- a/electrum/gui/qml/qechanneldetails.py +++ b/electrum/gui/qml/qechanneldetails.py @@ -2,7 +2,7 @@ import threading from enum import IntEnum from typing import Optional, TYPE_CHECKING -from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, pyqtEnum +from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, pyqtEnum, QVariant from electrum.i18n import _ from electrum.gui import messages @@ -61,12 +61,13 @@ class QEChannelDetails(AuthMixin, QObject, QtEventListener): self.unregister_callbacks() walletChanged = pyqtSignal() - @pyqtProperty(QEWallet, notify=walletChanged) + @pyqtProperty(QVariant, notify=walletChanged) def wallet(self) -> QEWallet: return self._wallet @wallet.setter def wallet(self, wallet: QEWallet): + assert wallet is None or isinstance(wallet, QEWallet) if self._wallet != wallet: self._wallet = wallet self.walletChanged.emit() diff --git a/electrum/gui/qml/qechannellistmodel.py b/electrum/gui/qml/qechannellistmodel.py index 3e68bce39..0079cdcd5 100644 --- a/electrum/gui/qml/qechannellistmodel.py +++ b/electrum/gui/qml/qechannellistmodel.py @@ -1,5 +1,6 @@ -from PyQt6.QtCore import Qt, QAbstractListModel, QModelIndex -from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot +from typing import TYPE_CHECKING + +from PyQt6.QtCore import Qt, QAbstractListModel, QModelIndex, pyqtProperty, pyqtSignal, pyqtSlot from electrum.lnchannel import ChannelState from electrum.lnutil import LOCAL, REMOTE @@ -12,6 +13,9 @@ from electrum.gui.common_qt.util import qt_event_listener, QtEventListener from .qetypes import QEAmount from .qemodelfilter import QEFilterProxyModel +if TYPE_CHECKING: + from electrum.wallet import Abstract_Wallet + class QEChannelListModel(QAbstractListModel, QtEventListener): _logger = get_logger(__name__) @@ -27,7 +31,7 @@ class QEChannelListModel(QAbstractListModel, QtEventListener): _network_signal = pyqtSignal(str, object) - def __init__(self, wallet, parent=None): + def __init__(self, wallet: 'Abstract_Wallet', parent=None): super().__init__(parent) self.wallet = wallet self._channels = [] diff --git a/electrum/gui/qml/qechannelopener.py b/electrum/gui/qml/qechannelopener.py index 927f7a883..f01fd7713 100644 --- a/electrum/gui/qml/qechannelopener.py +++ b/electrum/gui/qml/qechannelopener.py @@ -4,7 +4,7 @@ from asyncio.exceptions import TimeoutError from typing import Optional import electrum_ecc as ecc -from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject +from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QVariant from electrum.i18n import _ from electrum.gui import messages @@ -14,7 +14,6 @@ from electrum.lntransport import extract_nodeid, ConnStringFormatError from electrum.bitcoin import DummyAddress from electrum.lnworker import hardcoded_trampoline_nodes from electrum.logging import get_logger -from electrum.fee_policy import FeePolicy from electrum.transaction import PartialTransaction from .auth import AuthMixin, auth_protect @@ -55,12 +54,13 @@ class QEChannelOpener(QObject, AuthMixin): self._updating_max = False walletChanged = pyqtSignal() - @pyqtProperty(QEWallet, notify=walletChanged) + @pyqtProperty(QVariant, notify=walletChanged) def wallet(self): return self._wallet @wallet.setter def wallet(self, wallet: QEWallet): + assert wallet is None or isinstance(wallet, QEWallet) if self._wallet != wallet: self._wallet = wallet self.walletChanged.emit() @@ -79,12 +79,13 @@ class QEChannelOpener(QObject, AuthMixin): self.validate() amountChanged = pyqtSignal() - @pyqtProperty(QEAmount, notify=amountChanged) + @pyqtProperty(QVariant, notify=amountChanged) def amount(self): return self._amount @amount.setter def amount(self, amount: QEAmount): + assert amount is None or isinstance(amount, QEAmount) if self._amount != amount: self._amount.copyFrom(amount) self.amountChanged.emit() diff --git a/electrum/gui/qml/qeinvoice.py b/electrum/gui/qml/qeinvoice.py index 395f4c785..e9d30fc32 100644 --- a/electrum/gui/qml/qeinvoice.py +++ b/electrum/gui/qml/qeinvoice.py @@ -4,7 +4,7 @@ from enum import IntEnum from typing import Optional, Dict, Any, Tuple from urllib.parse import urlparse -from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, pyqtEnum, QTimer +from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, pyqtEnum, QTimer, QVariant from electrum.i18n import _ from electrum.logging import get_logger @@ -111,12 +111,13 @@ class QEInvoice(QObject, QtEventListener): self.determine_can_pay() walletChanged = pyqtSignal() - @pyqtProperty(QEWallet, notify=walletChanged) + @pyqtProperty(QVariant, notify=walletChanged) def wallet(self): return self._wallet @wallet.setter def wallet(self, wallet: QEWallet): + assert wallet is None or isinstance(wallet, QEWallet) if self._wallet != wallet: self._wallet = wallet self.walletChanged.emit() @@ -153,12 +154,13 @@ class QEInvoice(QObject, QtEventListener): self._amount.copyFrom(QEAmount(from_invoice=self._effectiveInvoice)) return self._amount - @pyqtProperty(QEAmount, notify=amountOverrideChanged) + @pyqtProperty(QVariant, notify=amountOverrideChanged) def amountOverride(self): return self._amountOverride @amountOverride.setter def amountOverride(self, new_amount: QEAmount): + assert new_amount is None or isinstance(new_amount, QEAmount) self._logger.debug(f'set new override amount {repr(new_amount)}') self._amountOverride.copyFrom(new_amount) self.amountOverrideChanged.emit() diff --git a/electrum/gui/qml/qelnpaymentdetails.py b/electrum/gui/qml/qelnpaymentdetails.py index 574fb1b01..2857c1828 100644 --- a/electrum/gui/qml/qelnpaymentdetails.py +++ b/electrum/gui/qml/qelnpaymentdetails.py @@ -1,4 +1,4 @@ -from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject +from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QVariant from electrum.logging import get_logger from electrum.util import bfh, format_time @@ -27,12 +27,13 @@ class QELnPaymentDetails(QObject): self._preimage = '' walletChanged = pyqtSignal() - @pyqtProperty(QEWallet, notify=walletChanged) + @pyqtProperty(QVariant, notify=walletChanged) def wallet(self): return self._wallet @wallet.setter def wallet(self, wallet: QEWallet): + assert wallet is None or isinstance(wallet, QEWallet) if self._wallet != wallet: self._wallet = wallet self.walletChanged.emit() diff --git a/electrum/gui/qml/qepiresolver.py b/electrum/gui/qml/qepiresolver.py index e171f80ec..f2c5b916d 100644 --- a/electrum/gui/qml/qepiresolver.py +++ b/electrum/gui/qml/qepiresolver.py @@ -1,7 +1,7 @@ from enum import IntEnum from typing import Optional -from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QTimer +from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QTimer, QVariant from electrum.logging import get_logger from electrum.i18n import _ @@ -51,12 +51,13 @@ class QEPIResolver(QObject): self.invoiceResolved.emit(self._pi) walletChanged = pyqtSignal() - @pyqtProperty(QEWallet, notify=walletChanged) + @pyqtProperty(QVariant, notify=walletChanged) def wallet(self) -> Optional[QEWallet]: return self._wallet @wallet.setter def wallet(self, wallet: QEWallet) -> None: + assert wallet is None or isinstance(wallet, QEWallet) self._wallet = wallet @pyqtProperty(bool, notify=busyChanged) diff --git a/electrum/gui/qml/qerequestdetails.py b/electrum/gui/qml/qerequestdetails.py index ababb00f7..37fd70b5c 100644 --- a/electrum/gui/qml/qerequestdetails.py +++ b/electrum/gui/qml/qerequestdetails.py @@ -2,7 +2,7 @@ from enum import IntEnum from typing import Optional from urllib.parse import urlparse -from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QTimer, pyqtEnum +from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QTimer, pyqtEnum, QVariant from electrum.logging import get_logger from electrum.invoices import ( @@ -74,12 +74,13 @@ class QERequestDetails(QObject, QtEventListener): self.statusChanged.emit() walletChanged = pyqtSignal() - @pyqtProperty(QEWallet, notify=walletChanged) + @pyqtProperty(QVariant, notify=walletChanged) def wallet(self): return self._wallet @wallet.setter def wallet(self, wallet: QEWallet): + assert wallet is None or isinstance(wallet, QEWallet) if self._wallet != wallet: self._wallet = wallet self.walletChanged.emit() diff --git a/electrum/gui/qml/qeswaphelper.py b/electrum/gui/qml/qeswaphelper.py index 7475216c4..c112acf42 100644 --- a/electrum/gui/qml/qeswaphelper.py +++ b/electrum/gui/qml/qeswaphelper.py @@ -4,7 +4,7 @@ from enum import IntEnum from typing import Union, Optional, TYPE_CHECKING, Sequence from PyQt6.QtCore import (pyqtProperty, pyqtSignal, pyqtSlot, QObject, QTimer, pyqtEnum, QAbstractListModel, Qt, - QModelIndex) + QModelIndex, QVariant) from PyQt6.QtGui import QColor from electrum.i18n import _ @@ -205,12 +205,13 @@ class QESwapHelper(AuthMixin, QObject, QtEventListener): self.unregister_callbacks() walletChanged = pyqtSignal() - @pyqtProperty(QEWallet, notify=walletChanged) + @pyqtProperty(QVariant, notify=walletChanged) def wallet(self): return self._wallet @wallet.setter def wallet(self, wallet: QEWallet): + assert wallet is None or isinstance(wallet, QEWallet) if self._wallet != wallet: self._wallet = wallet self.run_swap_manager() @@ -294,34 +295,37 @@ class QESwapHelper(AuthMixin, QObject, QtEventListener): self.userinfoChanged.emit() tosendChanged = pyqtSignal() - @pyqtProperty(QEAmount, notify=tosendChanged) + @pyqtProperty(QVariant, notify=tosendChanged) def tosend(self): return self._tosend @tosend.setter def tosend(self, tosend): + assert tosend is None or isinstance(tosend, QEAmount) if self._tosend != tosend: self._tosend = tosend self.tosendChanged.emit() toreceiveChanged = pyqtSignal() - @pyqtProperty(QEAmount, notify=toreceiveChanged) + @pyqtProperty(QVariant, notify=toreceiveChanged) def toreceive(self): return self._toreceive @toreceive.setter def toreceive(self, toreceive): + assert toreceive is None or isinstance(toreceive, QEAmount) if self._toreceive != toreceive: self._toreceive = toreceive self.toreceiveChanged.emit() serverMiningfeeChanged = pyqtSignal() - @pyqtProperty(QEAmount, notify=serverMiningfeeChanged) + @pyqtProperty(QVariant, notify=serverMiningfeeChanged) def serverMiningfee(self): return self._server_miningfee @serverMiningfee.setter def serverMiningfee(self, server_miningfee): + assert server_miningfee is None or isinstance(server_miningfee, QEAmount) if self._server_miningfee != server_miningfee: self._server_miningfee = server_miningfee self.serverMiningfeeChanged.emit() @@ -338,12 +342,13 @@ class QESwapHelper(AuthMixin, QObject, QtEventListener): self.serverfeepercChanged.emit() miningfeeChanged = pyqtSignal() - @pyqtProperty(QEAmount, notify=miningfeeChanged) + @pyqtProperty(QVariant, notify=miningfeeChanged) def miningfee(self): return self._miningfee @miningfee.setter def miningfee(self, miningfee): + assert miningfee is None or isinstance(miningfee, QEAmount) if self._miningfee != miningfee: self._miningfee = miningfee self.miningfeeChanged.emit() diff --git a/electrum/gui/qml/qetxdetails.py b/electrum/gui/qml/qetxdetails.py index 50e885e07..cfbf2f67a 100644 --- a/electrum/gui/qml/qetxdetails.py +++ b/electrum/gui/qml/qetxdetails.py @@ -1,6 +1,6 @@ from typing import Optional -from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject +from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QVariant from electrum.i18n import _ from electrum.logging import get_logger @@ -103,12 +103,13 @@ class QETxDetails(QObject, QtEventListener): self.update() walletChanged = pyqtSignal() - @pyqtProperty(QEWallet, notify=walletChanged) + @pyqtProperty(QVariant, notify=walletChanged) def wallet(self): return self._wallet @wallet.setter def wallet(self, wallet: QEWallet): + assert wallet is None or isinstance(wallet, QEWallet) if self._wallet != wallet: self._wallet = wallet self.walletChanged.emit() diff --git a/electrum/gui/qml/qetxfinalizer.py b/electrum/gui/qml/qetxfinalizer.py index e4114a88a..2e46eb0f7 100644 --- a/electrum/gui/qml/qetxfinalizer.py +++ b/electrum/gui/qml/qetxfinalizer.py @@ -5,7 +5,7 @@ from decimal import Decimal from typing import Optional, TYPE_CHECKING, Callable from functools import partial -from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, pyqtEnum +from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, pyqtEnum, QVariant from electrum.logging import get_logger from electrum.i18n import _ @@ -68,12 +68,13 @@ class FeeSlider(QObject): self._config = None # type: Optional[SimpleConfig] walletChanged = pyqtSignal() - @pyqtProperty(QEWallet, notify=walletChanged) + @pyqtProperty(QVariant, notify=walletChanged) def wallet(self): return self._wallet @wallet.setter def wallet(self, wallet: QEWallet): + assert wallet is None or isinstance(wallet, QEWallet) if self._wallet != wallet: self._wallet = wallet self._config = self._wallet.wallet.config @@ -170,12 +171,13 @@ class TxFeeSlider(FeeSlider): self._warning = '' feeChanged = pyqtSignal() - @pyqtProperty(QEAmount, notify=feeChanged) + @pyqtProperty(QVariant, notify=feeChanged) def fee(self): return self._fee @fee.setter def fee(self, fee): + assert fee is None or isinstance(fee, QEAmount) if self._fee != fee: self._fee.copyFrom(fee) self.feeChanged.emit() @@ -419,12 +421,13 @@ class QETxFinalizer(TxFeeSlider): self.addressChanged.emit() amountChanged = pyqtSignal() - @pyqtProperty(QEAmount, notify=amountChanged) + @pyqtProperty(QVariant, notify=amountChanged) def amount(self): return self._amount @amount.setter - def amount(self, amount): + def amount(self, amount: QEAmount): + assert amount is None or isinstance(amount, QEAmount) if self._amount != amount: self._logger.debug(str(amount)) self._amount.copyFrom(amount) @@ -436,12 +439,13 @@ class QETxFinalizer(TxFeeSlider): return self._effectiveAmount extraFeeChanged = pyqtSignal() - @pyqtProperty(QEAmount, notify=extraFeeChanged) + @pyqtProperty(QVariant, notify=extraFeeChanged) def extraFee(self): return self._extraFee @extraFee.setter - def extraFee(self, extrafee): + def extraFee(self, extrafee: QEAmount): + assert extrafee is None or isinstance(extrafee, QEAmount) if self._extraFee != extrafee: self._extraFee.copyFrom(extrafee) self.extraFeeChanged.emit() @@ -666,12 +670,13 @@ class QETxRbfFeeBumper(TxFeeSlider, TxMonMixin): self._bump_methods_available = [] oldfeeChanged = pyqtSignal() - @pyqtProperty(QEAmount, notify=oldfeeChanged) + @pyqtProperty(QVariant, notify=oldfeeChanged) def oldfee(self): return self._oldfee @oldfee.setter - def oldfee(self, oldfee): + def oldfee(self, oldfee: QEAmount): + assert oldfee is None or isinstance(oldfee, QEAmount) if self._oldfee != oldfee: self._oldfee.copyFrom(oldfee) self.oldfeeChanged.emit() @@ -806,12 +811,13 @@ class QETxCanceller(TxFeeSlider, TxMonMixin): self._rbf = True oldfeeChanged = pyqtSignal() - @pyqtProperty(QEAmount, notify=oldfeeChanged) + @pyqtProperty(QVariant, notify=oldfeeChanged) def oldfee(self): return self._oldfee @oldfee.setter - def oldfee(self, oldfee): + def oldfee(self, oldfee: QEAmount): + assert oldfee is None or isinstance(oldfee, QEAmount) if self._oldfee != oldfee: self._oldfee.copyFrom(oldfee) self.oldfeeChanged.emit() @@ -938,12 +944,13 @@ class QETxCpfpFeeBumper(TxFeeSlider, TxMonMixin): self._rbf = True totalFeeChanged = pyqtSignal() - @pyqtProperty(QEAmount, notify=totalFeeChanged) + @pyqtProperty(QVariant, notify=totalFeeChanged) def totalFee(self): return self._total_fee @totalFee.setter - def totalFee(self, totalfee): + def totalFee(self, totalfee: QEAmount): + assert totalfee is None or isinstance(totalfee, QEAmount) if self._total_fee != totalfee: self._total_fee.copyFrom(totalfee) self.totalFeeChanged.emit() From fd5b867689f6f2b29d4681e0edd51238be7ac415 Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Wed, 18 Feb 2026 16:20:39 +0100 Subject: [PATCH 03/28] qml: don't import QtMultimedia when running on android (android 8 compat) --- electrum/gui/qml/components/ScanDialog.qml | 5 ++++- electrum/gui/qml/components/main.qml | 10 ++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/electrum/gui/qml/components/ScanDialog.qml b/electrum/gui/qml/components/ScanDialog.qml index d8e4ed93a..8b6b3d750 100644 --- a/electrum/gui/qml/components/ScanDialog.qml +++ b/electrum/gui/qml/components/ScanDialog.qml @@ -6,7 +6,8 @@ import org.electrum import "controls" -// currently not used on android, kept for future use when qt6 camera stops crashing +// currently not used on android, kept for testing on desktop, and future use +// on android when qt6 camera support becomes usable (i.e. stops crashing) ElDialog { id: scanDialog @@ -50,4 +51,6 @@ ElDialog { onClicked: doReject() } } + + onClosed: destroy() } diff --git a/electrum/gui/qml/components/main.qml b/electrum/gui/qml/components/main.qml index 7de57ff7f..ce01372c9 100644 --- a/electrum/gui/qml/components/main.qml +++ b/electrum/gui/qml/components/main.qml @@ -7,7 +7,6 @@ import QtQuick.Controls.Material.impl import QtQuick.Window import QtQml -import QtMultimedia import org.electrum 1.0 @@ -450,12 +449,6 @@ ApplicationWindow onFinished: destroy() } } - Component { - id: _qtScanDialog - ScanDialog { - onClosed: destroy() - } - } Component { id: crashDialog @@ -533,7 +526,8 @@ ApplicationWindow if (AppController.isAndroid()) { app.scanDialog = _scanDialog } else { - app.scanDialog = _qtScanDialog + // for running on Desktop. uses QtMultimedia. + app.scanDialog = Qt.createComponent('ScanDialog.qml') } function continueWithServerConnection() { From 4d55b049b6151c587006eecd2c2e8571487e5e9d Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Wed, 18 Feb 2026 16:26:50 +0100 Subject: [PATCH 04/28] android: upgrade to androidx.core:core:1.16.0 from com.android.support:support-compat:28.0.0 --- contrib/android/buildozer_qml.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/android/buildozer_qml.spec b/contrib/android/buildozer_qml.spec index 2c6120af8..2d08a7f92 100644 --- a/contrib/android/buildozer_qml.spec +++ b/contrib/android/buildozer_qml.spec @@ -168,7 +168,7 @@ android.add_src = electrum/gui/qml/java_classes/ # kotlin-stdlib is required for zxing-cpp (BarcodeScannerView) android.gradle_dependencies = - com.android.support:support-compat:28.0.0, + androidx.core:core:1.16.0, org.jetbrains.kotlin:kotlin-stdlib:1.8.22 android.add_activities = org.electrum.qr.SimpleScannerActivity, org.electrum.biometry.BiometricActivity From 42472a1e9496ae2ffb2ed12b7eba791aabc5aa52 Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Thu, 19 Feb 2026 17:31:01 +0100 Subject: [PATCH 05/28] android: minimum API 26 required for Qt6.10 (Android 8.0) --- contrib/android/buildozer_qml.spec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/android/buildozer_qml.spec b/contrib/android/buildozer_qml.spec index 2d08a7f92..a73fdc5b8 100644 --- a/contrib/android/buildozer_qml.spec +++ b/contrib/android/buildozer_qml.spec @@ -111,13 +111,13 @@ android.api = 35 android.target_sdk_version = 35 # (int) Minimum API required. You will need to set the android.ndk_api to be as low as this value. -android.minapi = 23 +android.minapi = 26 # (str) Android NDK version to use android.ndk = 28c # (int) Android NDK API to use (optional). This is the minimum API your app will support. -android.ndk_api = 23 +android.ndk_api = 26 # (bool) Use --private data storage (True) or --dir public storage (False) #android.private_storage = True From cdb5c0b86df55620cbbd30a74310be7bd8fcb7b3 Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Fri, 20 Feb 2026 13:36:08 +0100 Subject: [PATCH 06/28] qml: styling updates qt6.10 --- electrum/gui/qml/components/Addresses.qml | 3 ++ .../qml/components/BIP39RecoveryDialog.qml | 5 +- electrum/gui/qml/components/Channels.qml | 5 +- electrum/gui/qml/components/Constants.qml | 3 +- .../gui/qml/components/ExportTxDialog.qml | 2 +- .../gui/qml/components/GenericShareDialog.qml | 2 +- electrum/gui/qml/components/InvoiceDialog.qml | 2 +- electrum/gui/qml/components/MessageDialog.qml | 7 ++- .../gui/qml/components/NostrConfigDialog.qml | 20 +++++--- .../gui/qml/components/OpenWalletDialog.qml | 32 ++++++------ .../gui/qml/components/ProxyConfigDialog.qml | 16 +++--- .../qml/components/ReceiveDetailsDialog.qml | 2 +- electrum/gui/qml/components/ReceiveDialog.qml | 2 +- electrum/gui/qml/components/SendDialog.qml | 2 +- .../gui/qml/components/ServerConfigDialog.qml | 28 ++++++----- .../components/SignVerifyMessageDialog.qml | 3 +- electrum/gui/qml/components/SwapDialog.qml | 2 +- electrum/gui/qml/components/SweepDialog.qml | 27 ++++++---- electrum/gui/qml/components/Wallets.qml | 1 - .../components/controls/ButtonContainer.qml | 29 +++++++++-- .../controls/DialogButtonContainer.qml | 3 ++ .../gui/qml/components/controls/ElDialog.qml | 8 +-- .../qml/components/controls/HelpDialog.qml | 2 +- .../qml/components/controls/PasswordField.qml | 1 + .../components/controls/SeedKeyboardKey.qml | 4 ++ .../qml/components/controls/SeedTextArea.qml | 23 +++++---- .../qml/components/controls/ServerConfig.qml | 47 ++++++++---------- .../components/controls/TextHighlightPane.qml | 2 +- .../qml/components/wizard/WCConfirmSeed.qml | 1 + .../components/wizard/WCCosignerKeystore.qml | 1 + .../qml/components/wizard/WCCreateSeed.qml | 1 + .../gui/qml/components/wizard/WCEnterExt.qml | 1 + .../qml/components/wizard/WCHaveMasterKey.qml | 1 + .../gui/qml/components/wizard/WCHaveSeed.qml | 7 ++- .../gui/qml/components/wizard/WCImport.qml | 2 + .../gui/qml/components/wizard/WCMultisig.qml | 1 + .../wizard/WCScriptAndDerivation.qml | 11 +++-- .../components/wizard/WCShowMasterPubkey.qml | 49 ------------------- .../components/wizard/WCWalletPassword.qml | 22 ++++++--- electrum/gui/qml/components/wizard/Wizard.qml | 4 +- 40 files changed, 209 insertions(+), 175 deletions(-) create mode 100644 electrum/gui/qml/components/controls/DialogButtonContainer.qml delete mode 100644 electrum/gui/qml/components/wizard/WCShowMasterPubkey.qml diff --git a/electrum/gui/qml/components/Addresses.qml b/electrum/gui/qml/components/Addresses.qml index aecd75415..368a04662 100644 --- a/electrum/gui/qml/components/Addresses.qml +++ b/electrum/gui/qml/components/Addresses.qml @@ -109,6 +109,7 @@ Pane { verticalPadding: 0 horizontalPadding: 0 + bottomPadding: 1 background: PaneInsetBackground {} ElListView { @@ -249,6 +250,8 @@ Pane { ButtonContainer { Layout.fillWidth: true + headerComponent: null + FlatButton { Layout.fillWidth: true Layout.preferredWidth: 1 diff --git a/electrum/gui/qml/components/BIP39RecoveryDialog.qml b/electrum/gui/qml/components/BIP39RecoveryDialog.qml index 6910d3de8..97e9d0a47 100644 --- a/electrum/gui/qml/components/BIP39RecoveryDialog.qml +++ b/electrum/gui/qml/components/BIP39RecoveryDialog.qml @@ -37,6 +37,7 @@ ElDialog { InfoTextArea { Layout.fillWidth: true Layout.margins: constants.paddingMedium + backgroundColor: constants.darkerDialogBackground text: bip39RecoveryListModel.state == Bip39RecoveryListModel.Scanning ? qsTr('Scanning for accounts...') @@ -65,7 +66,9 @@ ElDialog { verticalPadding: 0 horizontalPadding: 0 - background: PaneInsetBackground {} + background: PaneInsetBackground { + baseColor: constants.darkerDialogBackground + } ColumnLayout { spacing: 0 diff --git a/electrum/gui/qml/components/Channels.qml b/electrum/gui/qml/components/Channels.qml index 4369c8fbc..8a7d80055 100644 --- a/electrum/gui/qml/components/Channels.qml +++ b/electrum/gui/qml/components/Channels.qml @@ -63,9 +63,6 @@ Pane { Layout.fillWidth: true Layout.fillHeight: true Layout.topMargin: constants.paddingLarge - Layout.bottomMargin: constants.paddingLarge - Layout.leftMargin: constants.paddingMedium - Layout.rightMargin: constants.paddingMedium verticalPadding: 0 horizontalPadding: 0 @@ -121,6 +118,8 @@ Pane { ButtonContainer { Layout.fillWidth: true + headerComponent: null + FlatButton { Layout.fillWidth: true Layout.preferredWidth: 1 diff --git a/electrum/gui/qml/components/Constants.qml b/electrum/gui/qml/components/Constants.qml index 7935a8c5d..ea08a2fac 100644 --- a/electrum/gui/qml/components/Constants.qml +++ b/electrum/gui/qml/components/Constants.qml @@ -29,9 +29,10 @@ Item { property color mutedForeground: 'gray' //Qt.lighter(Material.background, 2) property color darkerBackground: Qt.darker(Material.background, 1.20) - property color lighterBackground: Qt.lighter(Material.background, 1.10) property color darkerDialogBackground: Qt.darker(Material.dialogColor, 1.20) + property color highlightBackground: Qt.lighter(Material.background, 1.30) property color dialogColor: Material.dialogColor + property color seedTextAreaBackground: Qt.darker(darkerDialogBackground, 1.20) property color notificationBackground: Qt.lighter(Material.background, 1.5) property color colorCredit: "#ff80ff80" diff --git a/electrum/gui/qml/components/ExportTxDialog.qml b/electrum/gui/qml/components/ExportTxDialog.qml index 249f07c18..998079d03 100644 --- a/electrum/gui/qml/components/ExportTxDialog.qml +++ b/electrum/gui/qml/components/ExportTxDialog.qml @@ -76,7 +76,7 @@ ElDialog { } } - ButtonContainer { + DialogButtonContainer { id: buttons Layout.fillWidth: true diff --git a/electrum/gui/qml/components/GenericShareDialog.qml b/electrum/gui/qml/components/GenericShareDialog.qml index afaa75f62..4f5886d48 100644 --- a/electrum/gui/qml/components/GenericShareDialog.qml +++ b/electrum/gui/qml/components/GenericShareDialog.qml @@ -88,7 +88,7 @@ ElDialog { } } - ButtonContainer { + DialogButtonContainer { Layout.fillWidth: true FlatButton { diff --git a/electrum/gui/qml/components/InvoiceDialog.qml b/electrum/gui/qml/components/InvoiceDialog.qml index 882c97bf8..a5465f3d7 100644 --- a/electrum/gui/qml/components/InvoiceDialog.qml +++ b/electrum/gui/qml/components/InvoiceDialog.qml @@ -458,7 +458,7 @@ ElDialog { } } - ButtonContainer { + DialogButtonContainer { Layout.fillWidth: true FlatButton { diff --git a/electrum/gui/qml/components/MessageDialog.qml b/electrum/gui/qml/components/MessageDialog.qml index a2211ff75..4452b3baa 100644 --- a/electrum/gui/qml/components/MessageDialog.qml +++ b/electrum/gui/qml/components/MessageDialog.qml @@ -46,8 +46,13 @@ ElDialog { } } - ButtonContainer { + DialogButtonContainer { Layout.fillWidth: true + function beforeLayout() { + if (!dialog.text) { + headerComponent = null + } + } FlatButton { Layout.fillWidth: true diff --git a/electrum/gui/qml/components/NostrConfigDialog.qml b/electrum/gui/qml/components/NostrConfigDialog.qml index f53646826..b9e2aa8c4 100644 --- a/electrum/gui/qml/components/NostrConfigDialog.qml +++ b/electrum/gui/qml/components/NostrConfigDialog.qml @@ -50,6 +50,7 @@ ElDialog { Layout.fillHeight: true Layout.leftMargin: constants.paddingLarge Layout.rightMargin: constants.paddingLarge + Layout.bottomMargin: constants.paddingLarge TextHighlightPane { Layout.fillWidth: true @@ -65,6 +66,7 @@ ElDialog { RowLayout { Layout.fillWidth: true + Layout.topMargin: constants.paddingLarge ElTextArea { id: relays_ta Layout.fillWidth: true @@ -96,14 +98,18 @@ ElDialog { } } - FlatButton { + DialogButtonContainer { Layout.fillWidth: true - text: qsTr('Ok') - enabled: valid - icon.source: '../../icons/confirmed.png' - onClicked: { - Config.nostrRelays = clean_array(relays_ta.text).join(",") - rootItem.close() + + FlatButton { + Layout.fillWidth: true + text: qsTr('Ok') + enabled: valid + icon.source: '../../icons/confirmed.png' + onClicked: { + Config.nostrRelays = clean_array(relays_ta.text).join(",") + rootItem.close() + } } } } diff --git a/electrum/gui/qml/components/OpenWalletDialog.qml b/electrum/gui/qml/components/OpenWalletDialog.qml index 51ec37e61..dbc5dad70 100644 --- a/electrum/gui/qml/components/OpenWalletDialog.qml +++ b/electrum/gui/qml/components/OpenWalletDialog.qml @@ -41,23 +41,20 @@ ElDialog { InfoTextArea { id: notice + Layout.fillWidth: true + Layout.bottomMargin: constants.paddingSmall text: Daemon.singlePasswordEnabled || isStartup ? qsTr('Please enter password') : qsTr('Wallet %1 requires password to unlock').arg(name) - iconStyle: InfoTextArea.IconStyle.Warn - Layout.fillWidth: true - } - - Label { - text: qsTr('Password') - Layout.fillWidth: true - color: Material.accentColor + compact: true + iconStyle: InfoTextArea.IconStyle.Info + backgroundColor: constants.darkerDialogBackground } PasswordField { id: password Layout.fillWidth: true - Layout.leftMargin: constants.paddingXLarge + placeholderText: qsTr('Password') onTextChanged: { unlockButton.enabled = true @@ -77,16 +74,19 @@ ElDialog { } } - FlatButton { - id: unlockButton + DialogButtonContainer { Layout.fillWidth: true - icon.source: '../../icons/unlock.png' - text: qsTr("Unlock") - onClicked: { - unlock() + + FlatButton { + id: unlockButton + Layout.fillWidth: true + icon.source: '../../icons/unlock.png' + text: qsTr("Unlock") + onClicked: { + unlock() + } } } - } function unlock() { diff --git a/electrum/gui/qml/components/ProxyConfigDialog.qml b/electrum/gui/qml/components/ProxyConfigDialog.qml index c3d6bf31b..fb31db19f 100644 --- a/electrum/gui/qml/components/ProxyConfigDialog.qml +++ b/electrum/gui/qml/components/ProxyConfigDialog.qml @@ -32,13 +32,17 @@ ElDialog { Item { Layout.fillHeight: true; Layout.preferredWidth: 1 } - FlatButton { + DialogButtonContainer { Layout.fillWidth: true - text: qsTr('Ok') - icon.source: '../../icons/confirmed.png' - onClicked: { - Network.proxy = proxyconfig.toProxyDict() - rootItem.close() + + FlatButton { + Layout.fillWidth: true + text: qsTr('Ok') + icon.source: '../../icons/confirmed.png' + onClicked: { + Network.proxy = proxyconfig.toProxyDict() + rootItem.close() + } } } } diff --git a/electrum/gui/qml/components/ReceiveDetailsDialog.qml b/electrum/gui/qml/components/ReceiveDetailsDialog.qml index c29df76b9..964bdd2d0 100644 --- a/electrum/gui/qml/components/ReceiveDetailsDialog.qml +++ b/electrum/gui/qml/components/ReceiveDetailsDialog.qml @@ -95,7 +95,7 @@ ElDialog { } } - ButtonContainer { + DialogButtonContainer { Layout.fillWidth: true FlatButton { diff --git a/electrum/gui/qml/components/ReceiveDialog.qml b/electrum/gui/qml/components/ReceiveDialog.qml index 19982d05f..9b7ca92db 100644 --- a/electrum/gui/qml/components/ReceiveDialog.qml +++ b/electrum/gui/qml/components/ReceiveDialog.qml @@ -143,7 +143,7 @@ ElDialog { } - ButtonContainer { + DialogButtonContainer { id: buttons Layout.fillWidth: true diff --git a/electrum/gui/qml/components/SendDialog.qml b/electrum/gui/qml/components/SendDialog.qml index 256012ffd..6244823f8 100644 --- a/electrum/gui/qml/components/SendDialog.qml +++ b/electrum/gui/qml/components/SendDialog.qml @@ -66,7 +66,7 @@ ElDialog { } } - ButtonContainer { + DialogButtonContainer { Layout.fillWidth: true FlatButton { diff --git a/electrum/gui/qml/components/ServerConfigDialog.qml b/electrum/gui/qml/components/ServerConfigDialog.qml index 9fed62953..688cf0f54 100644 --- a/electrum/gui/qml/components/ServerConfigDialog.qml +++ b/electrum/gui/qml/components/ServerConfigDialog.qml @@ -26,27 +26,31 @@ ElDialog { ColumnLayout { Layout.fillWidth: true Layout.fillHeight: true - Layout.leftMargin: constants.paddingLarge - Layout.rightMargin: constants.paddingLarge ServerConfig { id: serverconfig Layout.fillWidth: true Layout.fillHeight: true + Layout.leftMargin: constants.paddingLarge + Layout.rightMargin: constants.paddingLarge } } - FlatButton { + DialogButtonContainer { Layout.fillWidth: true - text: qsTr('Ok') - enabled: serverconfig.addressValid - icon.source: '../../icons/confirmed.png' - onClicked: { - let auto_connect = serverconfig.serverConnectMode == ServerConnectModeComboBox.Mode.Autoconnect - let server = serverconfig.address - let one_server = serverconfig.serverConnectMode == ServerConnectModeComboBox.Mode.Single - Network.setServerParameters(server, auto_connect, one_server) - rootItem.close() + + FlatButton { + Layout.fillWidth: true + text: qsTr('Ok') + enabled: serverconfig.addressValid + icon.source: '../../icons/confirmed.png' + onClicked: { + let auto_connect = serverconfig.serverConnectMode == ServerConnectModeComboBox.Mode.Autoconnect + let server = serverconfig.address + let one_server = serverconfig.serverConnectMode == ServerConnectModeComboBox.Mode.Single + Network.setServerParameters(server, auto_connect, one_server) + rootItem.close() + } } } } diff --git a/electrum/gui/qml/components/SignVerifyMessageDialog.qml b/electrum/gui/qml/components/SignVerifyMessageDialog.qml index 4a6321bac..a37ce2c78 100644 --- a/electrum/gui/qml/components/SignVerifyMessageDialog.qml +++ b/electrum/gui/qml/components/SignVerifyMessageDialog.qml @@ -179,8 +179,9 @@ ElDialog { } } - ButtonContainer { + DialogButtonContainer { Layout.fillWidth: true + FlatButton { Layout.fillWidth: true Layout.preferredWidth: 1 diff --git a/electrum/gui/qml/components/SwapDialog.qml b/electrum/gui/qml/components/SwapDialog.qml index 126b602b0..02f02a82d 100644 --- a/electrum/gui/qml/components/SwapDialog.qml +++ b/electrum/gui/qml/components/SwapDialog.qml @@ -282,7 +282,7 @@ ElDialog { Item { Layout.fillHeight: true; Layout.preferredWidth: 1 } - ButtonContainer { + DialogButtonContainer { Layout.columnSpan: 2 Layout.fillWidth: true FlatButton { diff --git a/electrum/gui/qml/components/SweepDialog.qml b/electrum/gui/qml/components/SweepDialog.qml index 849a70356..14d2898ed 100644 --- a/electrum/gui/qml/components/SweepDialog.qml +++ b/electrum/gui/qml/components/SweepDialog.qml @@ -60,6 +60,8 @@ ElDialog { Layout.fillWidth: true TextHighlightPane { Layout.fillWidth: true + backgroundColor: constants.darkerDialogBackground + Label { text: qsTr('Enter the list of private keys to sweep into this wallet') width: parent.width @@ -133,24 +135,29 @@ ElDialog { iconStyle: InfoTextArea.IconStyle.Warn Layout.fillWidth: true Layout.margins: constants.paddingMedium + backgroundColor: constants.darkerDialogBackground visible: text } } } - FlatButton { + DialogButtonContainer { Layout.fillWidth: true - Layout.preferredWidth: 1 - enabled: valid - icon.source: '../../icons/tab_send.png' - text: qsTr('Sweep...') - onClicked: { - console.log('sweeping') - root.privateKeys = sweepkeys.text - root.accept() + headerComponent: null + + FlatButton { + Layout.fillWidth: true + Layout.preferredWidth: 1 + enabled: valid + icon.source: '../../icons/tab_send.png' + text: qsTr('Sweep...') + onClicked: { + console.log('sweeping') + root.privateKeys = sweepkeys.text + root.accept() + } } } - } Bitcoin { diff --git a/electrum/gui/qml/components/Wallets.qml b/electrum/gui/qml/components/Wallets.qml index b38fdf78a..0d0435695 100644 --- a/electrum/gui/qml/components/Wallets.qml +++ b/electrum/gui/qml/components/Wallets.qml @@ -32,7 +32,6 @@ Pane { ColumnLayout { Layout.fillWidth: true - Layout.margins: constants.paddingLarge Heading { text: qsTr('Wallets') diff --git a/electrum/gui/qml/components/controls/ButtonContainer.qml b/electrum/gui/qml/components/controls/ButtonContainer.qml index 527897c0b..511168d38 100644 --- a/electrum/gui/qml/components/controls/ButtonContainer.qml +++ b/electrum/gui/qml/components/controls/ButtonContainer.qml @@ -7,11 +7,26 @@ Container { id: root property bool showSeparator: true + property color separatorColor: constants.darkerBackground + property Component headerComponent: Component { + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 2 + Layout.leftMargin: constants.paddingSmall + Layout.rightMargin: constants.paddingSmall + color: root.separatorColor + } + } + property var _contentRootItem + property var _headerItem property Item _layout function fillContentItem() { - var contentRoot = containerLayout.createObject(root) + var outerLayout = rootLayout.createObject(root) + if (headerComponent != null) + _headerItem = headerComponent.createObject(outerLayout) + var contentRoot = containerLayout.createObject(outerLayout) contentRoot.children.length = 0 // empty array let total = contentChildren.length @@ -32,7 +47,8 @@ Container { contentRoot.children.push(button) } - contentItem = contentRoot + contentItem = outerLayout //contentRoot + _contentRootItem = contentRoot } // override this function to dynamically add buttons. @@ -43,6 +59,13 @@ Container { fillContentItem() } + Component { + id: rootLayout + ColumnLayout { + spacing: 0 + } + } + Component { id: containerLayout RowLayout { @@ -59,7 +82,7 @@ Container { Layout.preferredWidth: showSeparator ? 2 : 0 Layout.preferredHeight: pheight Layout.alignment: Qt.AlignVCenter - color: constants.darkerBackground + color: root.separatorColor Component.onCompleted: { // create binding here, we need to be able to have stable ref master_idx visible = Qt.binding(function() { diff --git a/electrum/gui/qml/components/controls/DialogButtonContainer.qml b/electrum/gui/qml/components/controls/DialogButtonContainer.qml new file mode 100644 index 000000000..35e526921 --- /dev/null +++ b/electrum/gui/qml/components/controls/DialogButtonContainer.qml @@ -0,0 +1,3 @@ +ButtonContainer { + separatorColor: constants.darkerDialogBackground +} diff --git a/electrum/gui/qml/components/controls/ElDialog.qml b/electrum/gui/qml/components/controls/ElDialog.qml index 74bd44c21..2c7572816 100644 --- a/electrum/gui/qml/components/controls/ElDialog.qml +++ b/electrum/gui/qml/components/controls/ElDialog.qml @@ -106,10 +106,10 @@ Dialog { Rectangle { Layout.fillWidth: true - Layout.leftMargin: constants.paddingXXSmall - Layout.rightMargin: constants.paddingXXSmall - height: 1 - color: Qt.rgba(0,0,0,0.5) + Layout.leftMargin: constants.paddingSmall + Layout.rightMargin: constants.paddingSmall + Layout.preferredHeight: 2 + color: constants.darkerDialogBackground } } diff --git a/electrum/gui/qml/components/controls/HelpDialog.qml b/electrum/gui/qml/components/controls/HelpDialog.qml index ac7dac0ad..28724b538 100644 --- a/electrum/gui/qml/components/controls/HelpDialog.qml +++ b/electrum/gui/qml/components/controls/HelpDialog.qml @@ -35,7 +35,7 @@ ElDialog { implicitHeight: rootLayout.height + topPadding + bottomPadding padding: constants.paddingLarge background: Rectangle { - color: constants.lighterBackground + color: constants.highlightBackground } ColumnLayout { id: rootLayout diff --git a/electrum/gui/qml/components/controls/PasswordField.qml b/electrum/gui/qml/components/controls/PasswordField.qml index e9fc0e10d..064ed6480 100644 --- a/electrum/gui/qml/components/controls/PasswordField.qml +++ b/electrum/gui/qml/components/controls/PasswordField.qml @@ -7,6 +7,7 @@ RowLayout { property alias text: password_tf.text property alias tf: password_tf property alias echoMode: password_tf.echoMode + property alias placeholderText: password_tf.placeholderText property bool showReveal: true signal accepted diff --git a/electrum/gui/qml/components/controls/SeedKeyboardKey.qml b/electrum/gui/qml/components/controls/SeedKeyboardKey.qml index fa5fcc9f9..68b08c3d5 100644 --- a/electrum/gui/qml/components/controls/SeedKeyboardKey.qml +++ b/electrum/gui/qml/components/controls/SeedKeyboardKey.qml @@ -19,6 +19,10 @@ Pane { kbd.keyEvent(keycode, key) } + background: Rectangle { + color: constants.darkerDialogBackground + } + FlatButton { anchors.fill: parent diff --git a/electrum/gui/qml/components/controls/SeedTextArea.qml b/electrum/gui/qml/components/controls/SeedTextArea.qml index 76952ccad..9674c008f 100644 --- a/electrum/gui/qml/components/controls/SeedTextArea.qml +++ b/electrum/gui/qml/components/controls/SeedTextArea.qml @@ -35,10 +35,11 @@ Pane { TextArea { id: seedtextarea Layout.fillWidth: true - Layout.minimumHeight: fontMetrics.height * 3 + topPadding + bottomPadding + Layout.minimumHeight: fontMetrics.lineSpacing * 3 + topPadding + bottomPadding rightPadding: constants.paddingLarge leftPadding: constants.paddingLarge + bottomPadding: constants.paddingXLarge wrapMode: TextInput.WordWrap font.bold: true @@ -47,8 +48,9 @@ Pane { inputMethodHints: Qt.ImhSensitiveData | Qt.ImhLowercaseOnly | Qt.ImhNoPredictiveText readOnly: AppController.isAndroid() - background: Rectangle { - color: constants.darkerBackground + Component.onCompleted: { + background.filled = true + background.fillColor = constants.seedTextAreaBackground } onTextChanged: { @@ -66,16 +68,19 @@ Pane { Rectangle { anchors.fill: contentText color: root.indicatorValid ? 'green' : 'red' - border.color: Material.accentColor - radius: 2 + radius: 3 } Label { id: contentText text: root.indicatorText anchors.right: parent.right anchors.bottom: parent.bottom - leftPadding: root.indicatorText != '' ? constants.paddingLarge : 0 - rightPadding: root.indicatorText != '' ? constants.paddingLarge : 0 + anchors.rightMargin: constants.paddingXXSmall + anchors.bottomMargin: constants.paddingXXSmall + leftPadding: root.indicatorText != '' ? constants.paddingMedium : 0 + rightPadding: root.indicatorText != '' ? constants.paddingMedium : 0 + topPadding: root.indicatorText != '' ? constants.paddingXXSmall/2 : 0 + bottomPadding: root.indicatorText != '' ? constants.paddingXXSmall/2 : 0 font.bold: false font.pixelSize: constants.fontSizeSmall } @@ -99,7 +104,7 @@ Pane { Layout.margins: constants.paddingXXSmall width: suggestionLabel.width height: suggestionLabel.height - color: constants.lighterBackground + color: constants.darkerDialogBackground radius: constants.paddingXXSmall Label { id: suggestionLabel @@ -127,7 +132,7 @@ Pane { Layout.fillWidth: true Layout.preferredHeight: kbd.width / 1.75 visible: !root.readOnly - onKeyEvent: { + onKeyEvent: (keycode, text) => { if (keycode == Qt.Key_Backspace) { if (seedtextarea.text.length > 0) seedtextarea.text = seedtextarea.text.substring(0, seedtextarea.text.length-1) diff --git a/electrum/gui/qml/components/controls/ServerConfig.qml b/electrum/gui/qml/components/controls/ServerConfig.qml index aae493bae..0df5cb786 100644 --- a/electrum/gui/qml/components/controls/ServerConfig.qml +++ b/electrum/gui/qml/components/controls/ServerConfig.qml @@ -43,7 +43,7 @@ Item { HelpButton { Layout.alignment: Qt.AlignRight - heading: qsTr('Connection mode')+':' + heading: qsTr('Connection mode') + ':' helptext: Config.getTranslatedMessage('MSG_CONNECTMODE_SERVER_HELP') + '

' + Config.getTranslatedMessage('MSG_CONNECTMODE_NODES_HELP') + '
    ' + '
  • ' + Config.getTranslatedMessage('MSG_CONNECTMODE_AUTOCONNECT') + @@ -56,39 +56,32 @@ Item { } } - Label { - text: qsTr("Server") - enabled: address_tf.enabled - } - - TextHighlightPane { + TextField { + id: address_tf Layout.fillWidth: true - TextField { - id: address_tf - enabled: server_connect_mode_cb.currentValue != ServerConnectModeComboBox.Mode.Autoconnect - width: parent.width - inputMethodHints: Qt.ImhNoPredictiveText + enabled: server_connect_mode_cb.currentValue != ServerConnectModeComboBox.Mode.Autoconnect + inputMethodHints: Qt.ImhNoPredictiveText + placeholderText: qsTr('Server') - property bool valid: true + property bool valid: true - function validate() { - if (!enabled) { - valid = true - return - } - valid = Network.isValidServerAddress(address_tf.text) + function validate() { + if (!enabled) { + valid = true + return } + valid = Network.isValidServerAddress(address_tf.text) + } - onTextChanged: validate() - onEnabledChanged: validate() + onTextChanged: validate() + onEnabledChanged: validate() - Rectangle { - anchors.fill: parent - color: "red" - opacity: 0.2 - visible: !parent.valid - } + Rectangle { + anchors.fill: parent + color: "red" + opacity: 0.2 + visible: !parent.valid } } diff --git a/electrum/gui/qml/components/controls/TextHighlightPane.qml b/electrum/gui/qml/components/controls/TextHighlightPane.qml index fe6e07167..6275b3215 100644 --- a/electrum/gui/qml/components/controls/TextHighlightPane.qml +++ b/electrum/gui/qml/components/controls/TextHighlightPane.qml @@ -6,7 +6,7 @@ import QtQuick.Controls.Material Pane { padding: constants.paddingSmall - property color backgroundColor: Qt.lighter(Material.background, 1.15) + property color backgroundColor: constants.highlightBackground property color borderColor: 'transparent' background: Rectangle { diff --git a/electrum/gui/qml/components/wizard/WCConfirmSeed.qml b/electrum/gui/qml/components/wizard/WCConfirmSeed.qml index a2b058e95..eb239c698 100644 --- a/electrum/gui/qml/components/wizard/WCConfirmSeed.qml +++ b/electrum/gui/qml/components/wizard/WCConfirmSeed.qml @@ -30,6 +30,7 @@ WizardComponent { InfoTextArea { Layout.fillWidth: true Layout.bottomMargin: constants.paddingLarge + backgroundColor: constants.darkerDialogBackground text: qsTr('Your seed is important!') + ' ' + qsTr('If you lose your seed, your money will be permanently lost.') + ' ' + qsTr('To make sure that you have properly saved your seed, please retype it here.') diff --git a/electrum/gui/qml/components/wizard/WCCosignerKeystore.qml b/electrum/gui/qml/components/wizard/WCCosignerKeystore.qml index 40ef1cb22..ab14a5db0 100644 --- a/electrum/gui/qml/components/wizard/WCCosignerKeystore.qml +++ b/electrum/gui/qml/components/wizard/WCCosignerKeystore.qml @@ -43,6 +43,7 @@ WizardComponent { Layout.fillWidth: true visible: cosigner + backgroundColor: constants.darkerDialogBackground RowLayout { width: parent.width diff --git a/electrum/gui/qml/components/wizard/WCCreateSeed.qml b/electrum/gui/qml/components/wizard/WCCreateSeed.qml index efa4df90a..50420a440 100644 --- a/electrum/gui/qml/components/wizard/WCCreateSeed.qml +++ b/electrum/gui/qml/components/wizard/WCCreateSeed.qml @@ -47,6 +47,7 @@ WizardComponent { InfoTextArea { id: warningtext Layout.fillWidth: true + backgroundColor: constants.darkerDialogBackground iconStyle: InfoTextArea.IconStyle.Warn } diff --git a/electrum/gui/qml/components/wizard/WCEnterExt.qml b/electrum/gui/qml/components/wizard/WCEnterExt.qml index b804f09ab..8740f8f1b 100644 --- a/electrum/gui/qml/components/wizard/WCEnterExt.qml +++ b/electrum/gui/qml/components/wizard/WCEnterExt.qml @@ -61,6 +61,7 @@ WizardComponent { id: validationtext Layout.fillWidth: true Layout.columnSpan: 2 + backgroundColor: constants.darkerDialogBackground visible: text iconStyle: InfoTextArea.IconStyle.Error } diff --git a/electrum/gui/qml/components/wizard/WCHaveMasterKey.qml b/electrum/gui/qml/components/wizard/WCHaveMasterKey.qml index 11f04eb04..91702d8b6 100644 --- a/electrum/gui/qml/components/wizard/WCHaveMasterKey.qml +++ b/electrum/gui/qml/components/wizard/WCHaveMasterKey.qml @@ -75,6 +75,7 @@ WizardComponent { Layout.fillWidth: true visible: cosigner + backgroundColor: constants.darkerDialogBackground RowLayout { width: parent.width diff --git a/electrum/gui/qml/components/wizard/WCHaveSeed.qml b/electrum/gui/qml/components/wizard/WCHaveSeed.qml index cdb95bb2d..eb0f53878 100644 --- a/electrum/gui/qml/components/wizard/WCHaveSeed.qml +++ b/electrum/gui/qml/components/wizard/WCHaveSeed.qml @@ -122,6 +122,7 @@ WizardComponent { Layout.fillWidth: true visible: cosigner + backgroundColor: constants.darkerDialogBackground RowLayout { width: parent.width @@ -169,6 +170,7 @@ WizardComponent { ComboBox { id: seed_variant_cb + visible: !is2fa textRole: 'text' @@ -188,13 +190,16 @@ WizardComponent { id: infotext Layout.fillWidth: true Layout.columnSpan: 2 - Layout.bottomMargin: constants.paddingLarge + Layout.topMargin: constants.paddingLarge + compact: true + backgroundColor: constants.darkerDialogBackground } SeedTextArea { id: seedtext Layout.fillWidth: true Layout.columnSpan: 2 + Layout.topMargin: constants.paddingLarge placeholderText: cosigner ? qsTr('Enter cosigner seed') : qsTr('Enter your seed') diff --git a/electrum/gui/qml/components/wizard/WCImport.qml b/electrum/gui/qml/components/wizard/WCImport.qml index fbf402a8b..98a1942f2 100644 --- a/electrum/gui/qml/components/wizard/WCImport.qml +++ b/electrum/gui/qml/components/wizard/WCImport.qml @@ -27,8 +27,10 @@ WizardComponent { ColumnLayout { width: parent.width height: parent.height + InfoTextArea { Layout.preferredWidth: parent.width + backgroundColor: constants.darkerDialogBackground text: qsTr('Enter a list of Bitcoin addresses (this will create a watching-only wallet), or a list of private keys.') } diff --git a/electrum/gui/qml/components/wizard/WCMultisig.qml b/electrum/gui/qml/components/wizard/WCMultisig.qml index 96c6a2be1..6b867c535 100644 --- a/electrum/gui/qml/components/wizard/WCMultisig.qml +++ b/electrum/gui/qml/components/wizard/WCMultisig.qml @@ -41,6 +41,7 @@ WizardComponent { InfoTextArea { Layout.preferredWidth: parent.width + backgroundColor: constants.darkerDialogBackground text: qsTr('Choose the number of participants, and the number of signatures needed to unlock funds in your wallet.') } diff --git a/electrum/gui/qml/components/wizard/WCScriptAndDerivation.qml b/electrum/gui/qml/components/wizard/WCScriptAndDerivation.qml index c7d7f2781..9464c8912 100644 --- a/electrum/gui/qml/components/wizard/WCScriptAndDerivation.qml +++ b/electrum/gui/qml/components/wizard/WCScriptAndDerivation.qml @@ -161,19 +161,19 @@ WizardComponent { InfoTextArea { Layout.fillWidth: true + Layout.topMargin: constants.paddingMedium + Layout.bottomMargin: constants.paddingMedium + compact: true + backgroundColor: constants.darkerDialogBackground text: qsTr('You can override the suggested derivation path.') + ' ' + qsTr('If you are not sure what this is, leave this field unchanged.') } - Label { - text: qsTr('Derivation path') - } - TextField { id: derivationpathtext Layout.fillWidth: true - Layout.leftMargin: constants.paddingMedium inputMethodHints: Qt.ImhNoPredictiveText + placeholderText: qsTr('Derivation path') onTextChanged: validate() } @@ -181,6 +181,7 @@ WizardComponent { InfoTextArea { id: validationtext Layout.fillWidth: true + backgroundColor: constants.darkerDialogBackground visible: text iconStyle: InfoTextArea.IconStyle.Error } diff --git a/electrum/gui/qml/components/wizard/WCShowMasterPubkey.qml b/electrum/gui/qml/components/wizard/WCShowMasterPubkey.qml deleted file mode 100644 index 0bc9746e4..000000000 --- a/electrum/gui/qml/components/wizard/WCShowMasterPubkey.qml +++ /dev/null @@ -1,49 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Controls - -import org.electrum 1.0 - -import "../controls" - -WizardComponent { - valid: true - - property string masterPubkey: wizard_data['multisig_master_pubkey'] - - ColumnLayout { - width: parent.width - - Label { - text: qsTr('Here is your master public key. Please share it with your cosigners') - Layout.fillWidth: true - wrapMode: Text.Wrap - } - - TextHighlightPane { - Layout.fillWidth: true - - RowLayout { - width: parent.width - Label { - Layout.fillWidth: true - text: masterPubkey - font.pixelSize: constants.fontSizeMedium - font.family: FixedFont - wrapMode: Text.Wrap - } - ToolButton { - icon.source: '../../../icons/share.png' - icon.color: 'transparent' - onClicked: { - var dialog = app.genericShareDialog.createObject(app, - { title: qsTr('Master public key'), text: masterPubkey } - ) - dialog.open() - } - } - } - } - } - -} diff --git a/electrum/gui/qml/components/wizard/WCWalletPassword.qml b/electrum/gui/qml/components/wizard/WCWalletPassword.qml index b71bfefc9..68921f7b4 100644 --- a/electrum/gui/qml/components/wizard/WCWalletPassword.qml +++ b/electrum/gui/qml/components/wizard/WCWalletPassword.qml @@ -45,12 +45,14 @@ WizardComponent { Label { Layout.fillWidth: true - text: !enforceExistingPassword ? qsTr('Enter password') : qsTr('Enter existing password') + Layout.bottomMargin: constants.paddingSmall + text: !enforceExistingPassword ? qsTr('Enter a password to secure access to your wallet') : qsTr('Enter your existing wallet password') wrapMode: Text.Wrap } PasswordField { id: password1 + placeholderText: qsTr('Enter password') onTextChanged: { if (enforceExistingPassword) { root.passwordMatchesAnyExisting = false @@ -59,16 +61,12 @@ WizardComponent { } } - Label { - text: qsTr('Enter password (again)') - visible: !enforceExistingPassword - } - PasswordField { id: password2 showReveal: false echoMode: password1.echoMode visible: !enforceExistingPassword + placeholderText: qsTr('Enter password (again)') } RowLayout { @@ -100,12 +98,14 @@ WizardComponent { text: qsTr('Passwords don\'t match') visible: (password1.text != password2.text) && !enforceExistingPassword iconStyle: InfoTextArea.IconStyle.Warn + backgroundColor: constants.darkerDialogBackground } InfoTextArea { Layout.alignment: Qt.AlignCenter text: qsTr('Password too short') - visible: (password1.text == password2.text) && !valid && !enforceExistingPassword + visible: (password1.text == password2.text) && password1.text != '' && !valid && !enforceExistingPassword iconStyle: InfoTextArea.IconStyle.Warn + backgroundColor: constants.darkerDialogBackground } InfoTextArea { Layout.alignment: Qt.AlignCenter @@ -116,6 +116,7 @@ WizardComponent { qsTr("Creating new wallets with different passwords is not supported.") ].join("\n") iconStyle: InfoTextArea.IconStyle.Info + backgroundColor: constants.darkerDialogBackground } InfoTextArea { Layout.alignment: Qt.AlignCenter @@ -123,6 +124,13 @@ WizardComponent { visible: password1.text != "" && !valid && enforceExistingPassword text: qsTr('Password does not match any existing wallets password.') iconStyle: InfoTextArea.IconStyle.Warn + backgroundColor: constants.darkerDialogBackground } + + Item { + Layout.preferredWidth: 1 + Layout.fillHeight: true + } + } } diff --git a/electrum/gui/qml/components/wizard/Wizard.qml b/electrum/gui/qml/components/wizard/Wizard.qml index 390bf3c5d..36860db69 100644 --- a/electrum/gui/qml/components/wizard/Wizard.qml +++ b/electrum/gui/qml/components/wizard/Wizard.qml @@ -107,7 +107,7 @@ ElDialog { spacing: 0 // root Item in Wizard, capture back button here and delegate to main - Keys.onReleased: { + Keys.onReleased: (event) => { if (event.key == Qt.Key_Back) { console.log("Back button within wizard") app.close() // this handles unwind of dialogs/stack @@ -159,7 +159,7 @@ ElDialog { } } - ButtonContainer { + DialogButtonContainer { Layout.fillWidth: true FlatButton { From e99b3023e9946cb7f35ae0f85ad95ead18ae1d45 Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Sat, 21 Feb 2026 03:23:30 +0100 Subject: [PATCH 07/28] qml: wizard styling, password dialog styling --- .../gui/qml/components/PasswordDialog.qml | 39 ++++++++----------- .../qml/components/wizard/WCConfirmSeed.qml | 1 + electrum/gui/qml/components/wizard/Wizard.qml | 4 ++ 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/electrum/gui/qml/components/PasswordDialog.qml b/electrum/gui/qml/components/PasswordDialog.qml index 550f00b23..9f8c0bf19 100644 --- a/electrum/gui/qml/components/PasswordDialog.qml +++ b/electrum/gui/qml/components/PasswordDialog.qml @@ -39,32 +39,23 @@ ElDialog { text: infotext Layout.bottomMargin: constants.paddingMedium Layout.fillWidth: true - } - - Label { - Layout.fillWidth: true - text: qsTr('Password') - color: Material.accentColor + backgroundColor: constants.darkerDialogBackground + compact: true } PasswordField { id: pw_1 - Layout.leftMargin: constants.paddingXLarge - } - - Label { - Layout.fillWidth: true - text: qsTr('Password (again)') - visible: confirmPassword - color: Material.accentColor + Layout.bottomMargin: constants.paddingSmall + placeholderText: qsTr('Password') } PasswordField { id: pw_2 - Layout.leftMargin: constants.paddingXLarge + Layout.bottomMargin: constants.paddingSmall visible: confirmPassword showReveal: false echoMode: pw_1.echoMode + placeholderText: qsTr('Password (again)') } RowLayout { @@ -82,7 +73,7 @@ ElDialog { } PasswordStrengthIndicator { - Layout.fillWidth: true + Layout.preferredWidth: passworddialog.width / 2 password: pw_1.text } } @@ -98,13 +89,17 @@ ElDialog { } } - FlatButton { + DialogButtonContainer { Layout.fillWidth: true - text: qsTr("Ok") - icon.source: '../../icons/confirmed.png' - enabled: confirmPassword ? pw_1.text.length >= 6 && pw_1.text == pw_2.text : true - onClicked: { - passwordEntered(pw_1.text) + + FlatButton { + Layout.fillWidth: true + text: qsTr("Ok") + icon.source: '../../icons/confirmed.png' + enabled: confirmPassword ? pw_1.text.length >= 6 && pw_1.text == pw_2.text : true + onClicked: { + passwordEntered(pw_1.text) + } } } } diff --git a/electrum/gui/qml/components/wizard/WCConfirmSeed.qml b/electrum/gui/qml/components/wizard/WCConfirmSeed.qml index eb239c698..5aebaf0b7 100644 --- a/electrum/gui/qml/components/wizard/WCConfirmSeed.qml +++ b/electrum/gui/qml/components/wizard/WCConfirmSeed.qml @@ -43,6 +43,7 @@ WizardComponent { SeedTextArea { id: confirm Layout.fillWidth: true + Layout.topMargin: constants.paddingSmall placeholderText: qsTr('Enter your seed') onTextChanged: checkValid() } diff --git a/electrum/gui/qml/components/wizard/Wizard.qml b/electrum/gui/qml/components/wizard/Wizard.qml index 36860db69..e231dc570 100644 --- a/electrum/gui/qml/components/wizard/Wizard.qml +++ b/electrum/gui/qml/components/wizard/Wizard.qml @@ -165,6 +165,7 @@ ElDialog { FlatButton { Layout.fillWidth: true Layout.preferredWidth: 1 + Layout.preferredHeight: constants.fingerWidth visible: pages.currentIndex == 0 text: qsTr("Cancel") onClicked: wizard.doReject() @@ -172,6 +173,7 @@ ElDialog { FlatButton { Layout.fillWidth: true Layout.preferredWidth: 1 + Layout.preferredHeight: constants.fingerWidth visible: pages.currentIndex > 0 text: qsTr('Back') onClicked: pages.prev() @@ -179,6 +181,7 @@ ElDialog { FlatButton { Layout.fillWidth: true Layout.preferredWidth: 1 + Layout.preferredHeight: constants.fingerWidth text: qsTr("Next") visible: !pages.lastpage enabled: pages.pagevalid @@ -188,6 +191,7 @@ ElDialog { id: finishButton Layout.fillWidth: true Layout.preferredWidth: 1 + Layout.preferredHeight: constants.fingerWidth text: qsTr("Finish") visible: pages.lastpage enabled: pages.pagevalid From 28f744f778cc594082b2266f73b788be2aae6b83 Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Sat, 21 Feb 2026 16:25:53 +0100 Subject: [PATCH 08/28] qml: additional styling updates --- electrum/gui/qml/components/Addresses.qml | 6 ++-- .../components/ChannelOpenProgressDialog.qml | 4 ++- electrum/gui/qml/components/Channels.qml | 5 ++-- .../gui/qml/components/CloseChannelDialog.qml | 4 ++- .../gui/qml/components/ConfirmTxDialog.qml | 29 ++++++++++++------- .../gui/qml/components/CpfpBumpFeeDialog.qml | 20 ++++++++----- .../gui/qml/components/ExportTxDialog.qml | 4 ++- .../gui/qml/components/GenericShareDialog.qml | 5 ++-- electrum/gui/qml/components/InvoiceDialog.qml | 15 +++++----- electrum/gui/qml/components/Invoices.qml | 6 ++-- .../gui/qml/components/NostrConfigDialog.qml | 2 +- .../gui/qml/components/RbfBumpFeeDialog.qml | 22 +++++++++----- .../gui/qml/components/RbfCancelDialog.qml | 20 ++++++++----- electrum/gui/qml/components/ReceiveDialog.qml | 2 +- .../gui/qml/components/ReceiveRequests.qml | 7 ++--- electrum/gui/qml/components/SwapDialog.qml | 1 + electrum/gui/qml/components/SweepDialog.qml | 4 +-- electrum/gui/qml/components/Wallets.qml | 4 +-- .../components/controls/ButtonContainer.qml | 15 ++++------ .../controls/DialogButtonContainer.qml | 16 ++++++++++ .../controls/DialogHighlightPane.qml | 5 ++++ .../controls/PaneInsetBackground.qml | 16 +++++++--- .../components/wizard/WCCosignerKeystore.qml | 3 +- .../qml/components/wizard/WCHaveMasterKey.qml | 3 +- .../gui/qml/components/wizard/WCHaveSeed.qml | 3 +- 25 files changed, 136 insertions(+), 85 deletions(-) create mode 100644 electrum/gui/qml/components/controls/DialogHighlightPane.qml diff --git a/electrum/gui/qml/components/Addresses.qml b/electrum/gui/qml/components/Addresses.qml index 368a04662..4da07dd1a 100644 --- a/electrum/gui/qml/components/Addresses.qml +++ b/electrum/gui/qml/components/Addresses.qml @@ -107,10 +107,9 @@ Pane { Layout.fillWidth: true Layout.fillHeight: true - verticalPadding: 0 + verticalPadding: bg.lineWidth horizontalPadding: 0 - bottomPadding: 1 - background: PaneInsetBackground {} + background: PaneInsetBackground { id: bg; vertical: false } ElListView { id: listview @@ -250,7 +249,6 @@ Pane { ButtonContainer { Layout.fillWidth: true - headerComponent: null FlatButton { Layout.fillWidth: true diff --git a/electrum/gui/qml/components/ChannelOpenProgressDialog.qml b/electrum/gui/qml/components/ChannelOpenProgressDialog.qml index 3cea2e135..d251f7ee1 100644 --- a/electrum/gui/qml/components/ChannelOpenProgressDialog.qml +++ b/electrum/gui/qml/components/ChannelOpenProgressDialog.qml @@ -81,7 +81,7 @@ ElDialog { } } - TextHighlightPane { + DialogHighlightPane { Layout.alignment: Qt.AlignHCenter Layout.preferredWidth: dialog.width * 3/4 Label { @@ -100,6 +100,7 @@ ElDialog { visible: false iconStyle: InfoTextArea.IconStyle.Error textFormat: TextEdit.PlainText + backgroundColor: constants.darkerDialogBackground } InfoTextArea { @@ -108,6 +109,7 @@ ElDialog { Layout.preferredWidth: dialog.width * 2/3 visible: false textFormat: TextEdit.PlainText + backgroundColor: constants.darkerDialogBackground } } diff --git a/electrum/gui/qml/components/Channels.qml b/electrum/gui/qml/components/Channels.qml index 8a7d80055..4b015acf5 100644 --- a/electrum/gui/qml/components/Channels.qml +++ b/electrum/gui/qml/components/Channels.qml @@ -64,9 +64,9 @@ Pane { Layout.fillHeight: true Layout.topMargin: constants.paddingLarge - verticalPadding: 0 + verticalPadding: bg.lineWidth horizontalPadding: 0 - background: PaneInsetBackground {} + background: PaneInsetBackground { id: bg; vertical: false } ColumnLayout { spacing: 0 @@ -118,7 +118,6 @@ Pane { ButtonContainer { Layout.fillWidth: true - headerComponent: null FlatButton { Layout.fillWidth: true diff --git a/electrum/gui/qml/components/CloseChannelDialog.qml b/electrum/gui/qml/components/CloseChannelDialog.qml index cfae22c69..1862ad7c8 100644 --- a/electrum/gui/qml/components/CloseChannelDialog.qml +++ b/electrum/gui/qml/components/CloseChannelDialog.qml @@ -70,7 +70,7 @@ ElDialog { color: Material.accentColor } - TextHighlightPane { + DialogHighlightPane { Layout.columnSpan: 2 Layout.fillWidth: true @@ -91,6 +91,7 @@ ElDialog { Layout.fillWidth: true Layout.bottomMargin: constants.paddingLarge text: channeldetails.messageForceClose + backgroundColor: constants.darkerDialogBackground } Label { @@ -140,6 +141,7 @@ ElDialog { Layout.maximumWidth: parent.width visible: !channeldetails.isClosing && errorText.text iconStyle: InfoTextArea.IconStyle.Error + backgroundColor: constants.darkerDialogBackground } Label { Layout.alignment: Qt.AlignHCenter diff --git a/electrum/gui/qml/components/ConfirmTxDialog.qml b/electrum/gui/qml/components/ConfirmTxDialog.qml index 94617d305..2faa088fa 100644 --- a/electrum/gui/qml/components/ConfirmTxDialog.qml +++ b/electrum/gui/qml/components/ConfirmTxDialog.qml @@ -71,7 +71,7 @@ ElDialog { color: Material.accentColor } - TextHighlightPane { + DialogHighlightPane { Layout.columnSpan: 2 Layout.fillWidth: true GridLayout { @@ -122,7 +122,7 @@ ElDialog { color: Material.accentColor } - TextHighlightPane { + DialogHighlightPane { Layout.columnSpan: 2 Layout.fillWidth: true height: feepicker.height @@ -153,7 +153,7 @@ ElDialog { visible: showOptions } - TextHighlightPane { + DialogHighlightPane { Layout.columnSpan: 2 Layout.fillWidth: true visible: optionstoggle.visible && !optionstoggle.collapsed @@ -214,6 +214,7 @@ ElDialog { visible: finalizer.warning != '' text: finalizer.warning iconStyle: InfoTextArea.IconStyle.Warn + backgroundColor: constants.darkerDialogBackground } ToggleLabel { @@ -234,6 +235,7 @@ ElDialog { Layout.columnSpan: 2 Layout.fillWidth: true visible: finalizer.valid + backgroundColor: constants.darkerDialogBackground idx: index model: modelData @@ -258,6 +260,7 @@ ElDialog { Layout.columnSpan: 2 Layout.fillWidth: true visible: finalizer.valid + backgroundColor: constants.darkerDialogBackground allowShare: false allowClickAddress: false @@ -270,15 +273,19 @@ ElDialog { } } - FlatButton { - id: sendButton + DialogButtonContainer { Layout.fillWidth: true - text: (Daemon.currentWallet.isWatchOnly || !Daemon.currentWallet.canSignWithoutCosigner) - ? qsTr('Finalize...') - : qsTr('Pay...') - icon.source: '../../icons/confirmed.png' - enabled: finalizer.valid - onClicked: doAccept() + + FlatButton { + id: sendButton + Layout.fillWidth: true + text: (Daemon.currentWallet.isWatchOnly || !Daemon.currentWallet.canSignWithoutCosigner) + ? qsTr('Finalize...') + : qsTr('Pay...') + icon.source: '../../icons/confirmed.png' + enabled: finalizer.valid + onClicked: doAccept() + } } } diff --git a/electrum/gui/qml/components/CpfpBumpFeeDialog.qml b/electrum/gui/qml/components/CpfpBumpFeeDialog.qml index 1c8f7b127..69803bf50 100644 --- a/electrum/gui/qml/components/CpfpBumpFeeDialog.qml +++ b/electrum/gui/qml/components/CpfpBumpFeeDialog.qml @@ -59,7 +59,7 @@ ElDialog { color: Material.accentColor } - TextHighlightPane { + DialogHighlightPane { Layout.columnSpan: 2 Layout.fillWidth: true height: feepicker_childinfo.height @@ -82,7 +82,7 @@ ElDialog { color: Material.accentColor } - TextHighlightPane { + DialogHighlightPane { Layout.columnSpan: 2 Layout.fillWidth: true @@ -159,6 +159,7 @@ ElDialog { visible: cpfpfeebumper.warning != '' text: cpfpfeebumper.warning iconStyle: InfoTextArea.IconStyle.Warn + backgroundColor: constants.darkerDialogBackground } ToggleLabel { @@ -213,13 +214,16 @@ ElDialog { } } - FlatButton { - id: sendButton + DialogButtonContainer { Layout.fillWidth: true - text: qsTr('Ok') - icon.source: '../../icons/confirmed.png' - enabled: cpfpfeebumper.valid - onClicked: doAccept() + FlatButton { + id: sendButton + Layout.fillWidth: true + text: qsTr('Ok') + icon.source: '../../icons/confirmed.png' + enabled: cpfpfeebumper.valid + onClicked: doAccept() + } } } } diff --git a/electrum/gui/qml/components/ExportTxDialog.qml b/electrum/gui/qml/components/ExportTxDialog.qml index 998079d03..230c72dc7 100644 --- a/electrum/gui/qml/components/ExportTxDialog.qml +++ b/electrum/gui/qml/components/ExportTxDialog.qml @@ -39,7 +39,7 @@ ElDialog { width: parent.width spacing: constants.paddingMedium - TextHighlightPane { + DialogHighlightPane { Layout.fillWidth: true Layout.leftMargin: constants.paddingMedium Layout.rightMargin: constants.paddingMedium @@ -61,6 +61,7 @@ ElDialog { Layout.margins: constants.paddingLarge visible: dialog.text_help text: dialog.text_help + backgroundColor: constants.darkerDialogBackground } InfoTextArea { @@ -72,6 +73,7 @@ ElDialog { visible: dialog.text_warn text: dialog.text_warn iconStyle: InfoTextArea.IconStyle.Warn + backgroundColor: constants.darkerDialogBackground } } } diff --git a/electrum/gui/qml/components/GenericShareDialog.qml b/electrum/gui/qml/components/GenericShareDialog.qml index 4f5886d48..b14730dc6 100644 --- a/electrum/gui/qml/components/GenericShareDialog.qml +++ b/electrum/gui/qml/components/GenericShareDialog.qml @@ -42,7 +42,7 @@ ElDialog { width: parent.width spacing: constants.paddingMedium - TextHighlightPane { + DialogHighlightPane { Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true Layout.leftMargin: constants.paddingMedium @@ -60,7 +60,7 @@ ElDialog { Layout.bottomMargin: constants.paddingMedium } - TextHighlightPane { + DialogHighlightPane { Layout.fillWidth: true visible: dialog.text @@ -84,6 +84,7 @@ ElDialog { visible: dialog.text_help text: dialog.text_help Layout.fillWidth: true + backgroundColor: constants.darkerDialogBackground } } } diff --git a/electrum/gui/qml/components/InvoiceDialog.qml b/electrum/gui/qml/components/InvoiceDialog.qml index a5465f3d7..f2edb0f8c 100644 --- a/electrum/gui/qml/components/InvoiceDialog.qml +++ b/electrum/gui/qml/components/InvoiceDialog.qml @@ -67,6 +67,7 @@ ElDialog { ? InfoTextArea.IconStyle.Pending : InfoTextArea.IconStyle.Error : InfoTextArea.IconStyle.Info + backgroundColor: constants.darkerDialogBackground } Label { @@ -77,7 +78,7 @@ ElDialog { color: Material.accentColor } - TextHighlightPane { + DialogHighlightPane { Layout.columnSpan: 2 Layout.fillWidth: true visible: invoice.invoiceType == Invoice.OnchainInvoice @@ -114,7 +115,7 @@ ElDialog { color: Material.accentColor } - TextHighlightPane { + DialogHighlightPane { Layout.columnSpan: 2 Layout.fillWidth: true @@ -137,7 +138,7 @@ ElDialog { color: Material.accentColor } - TextHighlightPane { + DialogHighlightPane { id: amountContainer Layout.columnSpan: 2 @@ -320,7 +321,7 @@ ElDialog { color: Material.accentColor } - TextHighlightPane { + DialogHighlightPane { Layout.columnSpan: 2 Layout.fillWidth: true @@ -358,7 +359,7 @@ ElDialog { color: Material.accentColor } - TextHighlightPane { + DialogHighlightPane { Layout.columnSpan: 2 Layout.fillWidth: true @@ -401,7 +402,7 @@ ElDialog { visible: 'r' in invoice.lnprops && invoice.lnprops.r.length model: invoice.lnprops.r - TextHighlightPane { + DialogHighlightPane { Layout.columnSpan: 2 Layout.fillWidth: true @@ -428,7 +429,7 @@ ElDialog { color: Material.accentColor } - TextHighlightPane { + DialogHighlightPane { Layout.columnSpan: 2 Layout.fillWidth: true visible: invoice.invoiceType == Invoice.LightningInvoice && invoice.address diff --git a/electrum/gui/qml/components/Invoices.qml b/electrum/gui/qml/components/Invoices.qml index e57223fd9..f4767d7e3 100644 --- a/electrum/gui/qml/components/Invoices.qml +++ b/electrum/gui/qml/components/Invoices.qml @@ -18,7 +18,6 @@ Pane { ColumnLayout { Layout.fillWidth: true - Layout.margins: constants.paddingLarge InfoTextArea { Layout.fillWidth: true @@ -32,9 +31,9 @@ Pane { } Frame { - background: PaneInsetBackground {} + background: PaneInsetBackground {id: bg; vertical: false} - verticalPadding: 0 + verticalPadding: bg.lineWidth horizontalPadding: 0 Layout.fillHeight: true Layout.fillWidth: true @@ -87,6 +86,7 @@ Pane { ButtonContainer { Layout.fillWidth: true + FlatButton { Layout.fillWidth: true Layout.preferredWidth: 1 diff --git a/electrum/gui/qml/components/NostrConfigDialog.qml b/electrum/gui/qml/components/NostrConfigDialog.qml index b9e2aa8c4..2a62fc748 100644 --- a/electrum/gui/qml/components/NostrConfigDialog.qml +++ b/electrum/gui/qml/components/NostrConfigDialog.qml @@ -52,7 +52,7 @@ ElDialog { Layout.rightMargin: constants.paddingLarge Layout.bottomMargin: constants.paddingLarge - TextHighlightPane { + DialogHighlightPane { Layout.fillWidth: true Label { text: qsTr('Enter the list of Nostr relays') + '

    ' + diff --git a/electrum/gui/qml/components/RbfBumpFeeDialog.qml b/electrum/gui/qml/components/RbfBumpFeeDialog.qml index 4d9fdcf9c..f3e5fcdf6 100644 --- a/electrum/gui/qml/components/RbfBumpFeeDialog.qml +++ b/electrum/gui/qml/components/RbfBumpFeeDialog.qml @@ -46,6 +46,7 @@ ElDialog { Layout.fillWidth: true Layout.bottomMargin: constants.paddingLarge text: qsTr('Move the slider to increase your transaction\'s fee. This will improve its position in the mempool') + backgroundColor: constants.darkerDialogBackground } Label { @@ -107,7 +108,7 @@ ElDialog { color: Material.accentColor } - TextHighlightPane { + DialogHighlightPane { Layout.columnSpan: 2 Layout.fillWidth: true height: feepicker.height @@ -127,7 +128,7 @@ ElDialog { color: Material.accentColor } - TextHighlightPane { + DialogHighlightPane { Layout.columnSpan: 2 Layout.fillWidth: true visible: !optionstoggle.collapsed @@ -167,6 +168,7 @@ ElDialog { iconStyle: InfoTextArea.IconStyle.Warn visible: rbffeebumper.warning != '' text: rbffeebumper.warning + backgroundColor: constants.darkerDialogBackground } ToggleLabel { @@ -221,13 +223,17 @@ ElDialog { } } - FlatButton { - id: sendButton + DialogButtonContainer { Layout.fillWidth: true - text: qsTr('Ok') - icon.source: '../../icons/confirmed.png' - enabled: rbffeebumper.valid - onClicked: doAccept() + + FlatButton { + id: sendButton + Layout.fillWidth: true + text: qsTr('Ok') + icon.source: '../../icons/confirmed.png' + enabled: rbffeebumper.valid + onClicked: doAccept() + } } } } diff --git a/electrum/gui/qml/components/RbfCancelDialog.qml b/electrum/gui/qml/components/RbfCancelDialog.qml index b8832b10d..5e9b5d7b6 100644 --- a/electrum/gui/qml/components/RbfCancelDialog.qml +++ b/electrum/gui/qml/components/RbfCancelDialog.qml @@ -43,6 +43,7 @@ ElDialog { Layout.fillWidth: true Layout.bottomMargin: constants.paddingLarge text: qsTr('Cancel an unconfirmed transaction by double-spending its inputs back to your wallet with a higher fee.') + backgroundColor: constants.darkerDialogBackground } Label { @@ -79,7 +80,7 @@ ElDialog { color: Material.accentColor } - TextHighlightPane { + DialogHighlightPane { Layout.columnSpan: 2 Layout.fillWidth: true height: feepicker.height @@ -99,6 +100,7 @@ ElDialog { iconStyle: InfoTextArea.IconStyle.Warn visible: txcanceller.warning != '' text: txcanceller.warning + backgroundColor: constants.darkerDialogBackground } ToggleLabel { @@ -153,13 +155,17 @@ ElDialog { } } - FlatButton { - id: confirmButton + DialogButtonContainer { Layout.fillWidth: true - text: qsTr('Ok') - icon.source: '../../icons/confirmed.png' - enabled: txcanceller.valid - onClicked: doAccept() + + FlatButton { + id: confirmButton + Layout.fillWidth: true + text: qsTr('Ok') + icon.source: '../../icons/confirmed.png' + enabled: txcanceller.valid + onClicked: doAccept() + } } } } diff --git a/electrum/gui/qml/components/ReceiveDialog.qml b/electrum/gui/qml/components/ReceiveDialog.qml index 9b7ca92db..b7ebf3cc9 100644 --- a/electrum/gui/qml/components/ReceiveDialog.qml +++ b/electrum/gui/qml/components/ReceiveDialog.qml @@ -50,7 +50,7 @@ ElDialog { width: parent.width spacing: constants.paddingMedium - TextHighlightPane { + DialogHighlightPane { Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true diff --git a/electrum/gui/qml/components/ReceiveRequests.qml b/electrum/gui/qml/components/ReceiveRequests.qml index c6792fdd1..ffdd99c50 100644 --- a/electrum/gui/qml/components/ReceiveRequests.qml +++ b/electrum/gui/qml/components/ReceiveRequests.qml @@ -21,7 +21,6 @@ Pane { ColumnLayout { Layout.fillWidth: true - Layout.margins: constants.paddingLarge InfoTextArea { Layout.fillWidth: true @@ -31,13 +30,13 @@ Pane { } Heading { - text: qsTr('Pending requests') + text: qsTr('Pending payment requests') } Frame { - background: PaneInsetBackground {} + background: PaneInsetBackground {id: bg; vertical: false} - verticalPadding: 0 + verticalPadding: bg.lineWidth horizontalPadding: 0 Layout.fillHeight: true Layout.fillWidth: true diff --git a/electrum/gui/qml/components/SwapDialog.qml b/electrum/gui/qml/components/SwapDialog.qml index 02f02a82d..12bd5d668 100644 --- a/electrum/gui/qml/components/SwapDialog.qml +++ b/electrum/gui/qml/components/SwapDialog.qml @@ -42,6 +42,7 @@ ElDialog { : swaphelper.state == SwapHelper.NoService ? InfoTextArea.IconStyle.Warn : InfoTextArea.IconStyle.Info + backgroundColor: constants.darkerDialogBackground } GridLayout { diff --git a/electrum/gui/qml/components/SweepDialog.qml b/electrum/gui/qml/components/SweepDialog.qml index 14d2898ed..6ea763f50 100644 --- a/electrum/gui/qml/components/SweepDialog.qml +++ b/electrum/gui/qml/components/SweepDialog.qml @@ -58,9 +58,9 @@ ElDialog { RowLayout { Layout.fillWidth: true - TextHighlightPane { + + DialogHighlightPane { Layout.fillWidth: true - backgroundColor: constants.darkerDialogBackground Label { text: qsTr('Enter the list of private keys to sweep into this wallet') diff --git a/electrum/gui/qml/components/Wallets.qml b/electrum/gui/qml/components/Wallets.qml index 0d0435695..635bd19dd 100644 --- a/electrum/gui/qml/components/Wallets.qml +++ b/electrum/gui/qml/components/Wallets.qml @@ -41,9 +41,9 @@ Pane { id: detailsFrame Layout.fillWidth: true Layout.fillHeight: true - verticalPadding: 0 + verticalPadding: bg.lineWidth horizontalPadding: 0 - background: PaneInsetBackground {} + background: PaneInsetBackground { id: bg; vertical: false } ElListView { id: listview diff --git a/electrum/gui/qml/components/controls/ButtonContainer.qml b/electrum/gui/qml/components/controls/ButtonContainer.qml index 511168d38..59cc6fe3a 100644 --- a/electrum/gui/qml/components/controls/ButtonContainer.qml +++ b/electrum/gui/qml/components/controls/ButtonContainer.qml @@ -8,20 +8,16 @@ Container { property bool showSeparator: true property color separatorColor: constants.darkerBackground - property Component headerComponent: Component { - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 2 - Layout.leftMargin: constants.paddingSmall - Layout.rightMargin: constants.paddingSmall - color: root.separatorColor - } - } + property Component headerComponent: null property var _contentRootItem property var _headerItem property Item _layout + background: Rectangle { + color: constants.highlightBackground + } + function fillContentItem() { var outerLayout = rootLayout.createObject(root) if (headerComponent != null) @@ -97,3 +93,4 @@ Container { } } + diff --git a/electrum/gui/qml/components/controls/DialogButtonContainer.qml b/electrum/gui/qml/components/controls/DialogButtonContainer.qml index 35e526921..3c7f9f1ba 100644 --- a/electrum/gui/qml/components/controls/DialogButtonContainer.qml +++ b/electrum/gui/qml/components/controls/DialogButtonContainer.qml @@ -1,3 +1,19 @@ +import QtQuick +import QtQuick.Layouts + ButtonContainer { + id: root separatorColor: constants.darkerDialogBackground + background: Rectangle { + color: "transparent" + } + headerComponent: Component { + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 2 + Layout.leftMargin: constants.paddingSmall + Layout.rightMargin: constants.paddingSmall + color: root.separatorColor + } + } } diff --git a/electrum/gui/qml/components/controls/DialogHighlightPane.qml b/electrum/gui/qml/components/controls/DialogHighlightPane.qml new file mode 100644 index 000000000..1913972ec --- /dev/null +++ b/electrum/gui/qml/components/controls/DialogHighlightPane.qml @@ -0,0 +1,5 @@ +import QtQuick + +TextHighlightPane { + backgroundColor: constants.darkerDialogBackground +} diff --git a/electrum/gui/qml/components/controls/PaneInsetBackground.qml b/electrum/gui/qml/components/controls/PaneInsetBackground.qml index 9762e4101..8733a5705 100644 --- a/electrum/gui/qml/components/controls/PaneInsetBackground.qml +++ b/electrum/gui/qml/components/controls/PaneInsetBackground.qml @@ -3,25 +3,33 @@ import QtQuick.Controls.Material Rectangle { property color baseColor: Material.background + property bool vertical: true + property bool horizontal: true + property int lineWidth: 2 + Rectangle { anchors { left: parent.left; top: parent.top; right: parent.right } - height: 1 + height: lineWidth color: Qt.darker(baseColor, 1.50) + visible: horizontal } Rectangle { anchors { left: parent.left; top: parent.top; bottom: parent.bottom } - width: 1 + width: lineWidth color: Qt.darker(baseColor, 1.50) + visible: vertical } Rectangle { anchors { left: parent.left; bottom: parent.bottom; right: parent.right } - height: 1 + height: lineWidth color: Qt.lighter(baseColor, 1.50) + visible: horizontal } Rectangle { anchors { right: parent.right; top: parent.top; bottom: parent.bottom } - width: 1 + width: lineWidth color: Qt.lighter(baseColor, 1.50) + visible: vertical } color: Qt.darker(baseColor, 1.15) } diff --git a/electrum/gui/qml/components/wizard/WCCosignerKeystore.qml b/electrum/gui/qml/components/wizard/WCCosignerKeystore.qml index ab14a5db0..bac431183 100644 --- a/electrum/gui/qml/components/wizard/WCCosignerKeystore.qml +++ b/electrum/gui/qml/components/wizard/WCCosignerKeystore.qml @@ -39,11 +39,10 @@ WizardComponent { wrapMode: Text.Wrap } - TextHighlightPane { + DialogHighlightPane { Layout.fillWidth: true visible: cosigner - backgroundColor: constants.darkerDialogBackground RowLayout { width: parent.width diff --git a/electrum/gui/qml/components/wizard/WCHaveMasterKey.qml b/electrum/gui/qml/components/wizard/WCHaveMasterKey.qml index 91702d8b6..b71174e7c 100644 --- a/electrum/gui/qml/components/wizard/WCHaveMasterKey.qml +++ b/electrum/gui/qml/components/wizard/WCHaveMasterKey.qml @@ -71,11 +71,10 @@ WizardComponent { wrapMode: Text.Wrap } - TextHighlightPane { + DialogHighlightPane { Layout.fillWidth: true visible: cosigner - backgroundColor: constants.darkerDialogBackground RowLayout { width: parent.width diff --git a/electrum/gui/qml/components/wizard/WCHaveSeed.qml b/electrum/gui/qml/components/wizard/WCHaveSeed.qml index eb0f53878..95ce6cd6f 100644 --- a/electrum/gui/qml/components/wizard/WCHaveSeed.qml +++ b/electrum/gui/qml/components/wizard/WCHaveSeed.qml @@ -117,12 +117,11 @@ WizardComponent { wrapMode: Text.Wrap } - TextHighlightPane { + DialogHighlightPane { Layout.columnSpan: 2 Layout.fillWidth: true visible: cosigner - backgroundColor: constants.darkerDialogBackground RowLayout { width: parent.width From 738992ac9e211105d2eb8163b8ff551d0dd6b37d Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Sun, 22 Feb 2026 13:30:56 +0100 Subject: [PATCH 09/28] qml: don't add navigationbar padding when on-screen keyboard is visible, also allow stackview pages to override navigationbar background color to allow correct color runoff below buttons --- electrum/gui/qml/components/Addresses.qml | 1 + .../gui/qml/components/ChannelDetails.qml | 2 +- electrum/gui/qml/components/Channels.qml | 2 +- electrum/gui/qml/components/Invoices.qml | 2 ++ .../gui/qml/components/NetworkOverview.qml | 1 + .../gui/qml/components/ReceiveRequests.qml | 1 + electrum/gui/qml/components/TxDetails.qml | 1 + electrum/gui/qml/components/WalletDetails.qml | 1 + .../gui/qml/components/WalletMainView.qml | 1 + electrum/gui/qml/components/Wallets.qml | 36 ++++++++++--------- .../gui/qml/components/controls/ElDialog.qml | 2 +- electrum/gui/qml/components/main.qml | 11 +++++- 12 files changed, 41 insertions(+), 20 deletions(-) diff --git a/electrum/gui/qml/components/Addresses.qml b/electrum/gui/qml/components/Addresses.qml index 4da07dd1a..71d441121 100644 --- a/electrum/gui/qml/components/Addresses.qml +++ b/electrum/gui/qml/components/Addresses.qml @@ -276,6 +276,7 @@ Pane { } } + property color navigationBarBackgroundColor: constants.highlightBackground Component { id: sectionDelegate diff --git a/electrum/gui/qml/components/ChannelDetails.qml b/electrum/gui/qml/components/ChannelDetails.qml index ab18e6e45..42d55c37d 100644 --- a/electrum/gui/qml/components/ChannelDetails.qml +++ b/electrum/gui/qml/components/ChannelDetails.qml @@ -466,8 +466,8 @@ Pane { } } } - } + property color navigationBarBackgroundColor: constants.highlightBackground ChannelDetails { id: channeldetails diff --git a/electrum/gui/qml/components/Channels.qml b/electrum/gui/qml/components/Channels.qml index 4b015acf5..ea8c1283a 100644 --- a/electrum/gui/qml/components/Channels.qml +++ b/electrum/gui/qml/components/Channels.qml @@ -155,8 +155,8 @@ Pane { icon.source: '../../icons/lightning.png' } } - } + property color navigationBarBackgroundColor: constants.highlightBackground Component { id: openChannelDialog diff --git a/electrum/gui/qml/components/Invoices.qml b/electrum/gui/qml/components/Invoices.qml index f4767d7e3..513449f0d 100644 --- a/electrum/gui/qml/components/Invoices.qml +++ b/electrum/gui/qml/components/Invoices.qml @@ -112,4 +112,6 @@ Pane { } } } + property color navigationBarBackgroundColor: constants.highlightBackground + } diff --git a/electrum/gui/qml/components/NetworkOverview.qml b/electrum/gui/qml/components/NetworkOverview.qml index 12ffdc65a..ffb893c1c 100644 --- a/electrum/gui/qml/components/NetworkOverview.qml +++ b/electrum/gui/qml/components/NetworkOverview.qml @@ -314,6 +314,7 @@ Pane { } } } + property color navigationBarBackgroundColor: constants.highlightBackground Component { id: serverConfig diff --git a/electrum/gui/qml/components/ReceiveRequests.qml b/electrum/gui/qml/components/ReceiveRequests.qml index ffdd99c50..e389a48b1 100644 --- a/electrum/gui/qml/components/ReceiveRequests.qml +++ b/electrum/gui/qml/components/ReceiveRequests.qml @@ -107,4 +107,5 @@ Pane { } } } + property color navigationBarBackgroundColor: constants.highlightBackground } diff --git a/electrum/gui/qml/components/TxDetails.qml b/electrum/gui/qml/components/TxDetails.qml index 42f462a2e..0a5d217be 100644 --- a/electrum/gui/qml/components/TxDetails.qml +++ b/electrum/gui/qml/components/TxDetails.qml @@ -480,6 +480,7 @@ Pane { } } + property color navigationBarBackgroundColor: constants.highlightBackground TxDetails { id: txdetails diff --git a/electrum/gui/qml/components/WalletDetails.qml b/electrum/gui/qml/components/WalletDetails.qml index 2676c5862..de2337e95 100644 --- a/electrum/gui/qml/components/WalletDetails.qml +++ b/electrum/gui/qml/components/WalletDetails.qml @@ -518,6 +518,7 @@ Pane { } } } + property color navigationBarBackgroundColor: constants.highlightBackground Connections { target: Daemon diff --git a/electrum/gui/qml/components/WalletMainView.qml b/electrum/gui/qml/components/WalletMainView.qml index 593e49f9d..8fdbb6469 100644 --- a/electrum/gui/qml/components/WalletMainView.qml +++ b/electrum/gui/qml/components/WalletMainView.qml @@ -325,6 +325,7 @@ Item { } } } + property color navigationBarBackgroundColor: constants.highlightBackground PIResolver { id: piResolver diff --git a/electrum/gui/qml/components/Wallets.qml b/electrum/gui/qml/components/Wallets.qml index 635bd19dd..e98e82c14 100644 --- a/electrum/gui/qml/components/Wallets.qml +++ b/electrum/gui/qml/components/Wallets.qml @@ -116,27 +116,31 @@ Pane { } - FlatButton { + ButtonContainer { Layout.fillWidth: true - text: qsTr('Create Wallet') - icon.source: '../../icons/add.png' - onClicked: { - if (Daemon.availableWallets.rowCount() > 0 && Config.walletShouldUseSinglePassword - && (!Daemon.singlePassword || Daemon.numWalletsWithPassword(Daemon.singlePassword) < 1)) { - // if the user has wallets but hasn't unlocked any wallet yet force them to do so. - // this ensures they know at least one wallets password and can complete the wizard - // where they will need to enter the password of an existing wallet. - var dialog = app.messageDialog.createObject(app, { - title: qsTr('Wallet unlock required'), - text: qsTr("You have to unlock any existing wallet first before creating a new wallet."), - }) - dialog.open() - } else { - rootItem.createWallet() + FlatButton { + Layout.fillWidth: true + text: qsTr('Create Wallet') + icon.source: '../../icons/add.png' + onClicked: { + if (Daemon.availableWallets.rowCount() > 0 && Config.walletShouldUseSinglePassword + && (!Daemon.singlePassword || Daemon.numWalletsWithPassword(Daemon.singlePassword) < 1)) { + // if the user has wallets but hasn't unlocked any wallet yet force them to do so. + // this ensures they know at least one wallets password and can complete the wizard + // where they will need to enter the password of an existing wallet. + var dialog = app.messageDialog.createObject(app, { + title: qsTr('Wallet unlock required'), + text: qsTr("You have to unlock any existing wallet first before creating a new wallet."), + }) + dialog.open() + } else { + rootItem.createWallet() + } } } } } + property color navigationBarBackgroundColor: constants.highlightBackground Connections { target: Daemon diff --git a/electrum/gui/qml/components/controls/ElDialog.qml b/electrum/gui/qml/components/controls/ElDialog.qml index 2c7572816..b90402c9e 100644 --- a/electrum/gui/qml/components/controls/ElDialog.qml +++ b/electrum/gui/qml/components/controls/ElDialog.qml @@ -17,7 +17,7 @@ Dialog { property bool _wasOpened: false // Add bottom padding for Android navigation bar if needed - bottomPadding: needsSystemBarPadding ? app.navigationBarHeight : 0 + bottomPadding: needsSystemBarPadding && app.keyboardFreeZone.state != 'visible' ? app.navigationBarHeight : 0 // called to finally close dialog after checks by onClosing handler in main.qml function doClose() { diff --git a/electrum/gui/qml/components/main.qml b/electrum/gui/qml/components/main.qml index ce01372c9..7e4d7d251 100644 --- a/electrum/gui/qml/components/main.qml +++ b/electrum/gui/qml/components/main.qml @@ -36,6 +36,7 @@ ApplicationWindow property alias stack: mainStackView property alias keyboardFreeZone: _keyboardFreeZone property alias infobanner: _infobanner + property color _navigationBarBackgroundColor: 'transparent' property string pendingIntent: "" @@ -287,13 +288,21 @@ ApplicationWindow mainStackView.clear() mainStackView.push(Qt.resolvedUrl(item_url)) } + function updateStylingFromItem(item) { + _navigationBarBackgroundColor = item && 'navigationBarBackgroundColor' in item + ? item.navigationBarBackgroundColor + : 'transparent' + } + onCurrentItemChanged: updateStylingFromItem(currentItem) } // Add bottom padding for navigation bar on Android when UI is edge-to-edge Item { - visible: app.navigationBarHeight > 0 + visible: app.navigationBarHeight > 0 && _keyboardFreeZone.state != 'visible' Layout.fillWidth: true Layout.preferredHeight: app.navigationBarHeight + + Rectangle { anchors.fill: parent; color: _navigationBarBackgroundColor } } } From 1c0851c6ebbcc0d5ef648ab2a64a050a6a6f587d Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Mon, 23 Feb 2026 12:31:02 +0100 Subject: [PATCH 10/28] styling OpenChannelDialog --- .../gui/qml/components/OpenChannelDialog.qml | 136 +++++++++--------- 1 file changed, 67 insertions(+), 69 deletions(-) diff --git a/electrum/gui/qml/components/OpenChannelDialog.qml b/electrum/gui/qml/components/OpenChannelDialog.qml index 99aa6cd2d..6282c54fa 100644 --- a/electrum/gui/qml/components/OpenChannelDialog.qml +++ b/electrum/gui/qml/components/OpenChannelDialog.qml @@ -67,78 +67,77 @@ ElDialog { ].join(' ') } - Label { - text: qsTr('Node') - Layout.columnSpan: 3 - color: Material.accentColor - } - // gossip - TextArea { - id: node - visible: Config.useGossip - Layout.columnSpan: 2 - Layout.fillWidth: true - font.family: FixedFont - wrapMode: Text.Wrap - placeholderText: qsTr('Paste or scan node uri/pubkey') - inputMethodHints: Qt.ImhSensitiveData | Qt.ImhNoPredictiveText | Qt.ImhNoAutoUppercase - onTextChanged: { - if (activeFocus) - channelopener.connectStr = text - } - onActiveFocusChanged: { - if (!activeFocus) - channelopener.connectStr = text - } - } - RowLayout { + Layout.columnSpan: 3 visible: Config.useGossip spacing: 0 - ToolButton { - icon.source: '../../icons/paste.png' - icon.height: constants.iconSizeMedium - icon.width: constants.iconSizeMedium - onClicked: { - var cliptext = AppController.clipboardToText() - if (!cliptext) - return - if (channelopener.validateConnectString(cliptext)) { - channelopener.connectStr = cliptext - node.text = channelopener.connectStr - } else { - var dialog = app.messageDialog.createObject(app, { - text: qsTr('Invalid node-id or connect string') + + TextArea { + id: nodeUri + visible: Config.useGossip + Layout.fillWidth: true + Layout.minimumHeight: nodeUriFontMetrics.lineSpacing * 4 + topPadding + bottomPadding + Layout.topMargin: constants.paddingSmall + font.family: FixedFont + wrapMode: Text.Wrap + placeholderText: qsTr('Paste or scan node uri/pubkey') + inputMethodHints: Qt.ImhSensitiveData | Qt.ImhNoPredictiveText | Qt.ImhNoAutoUppercase + onTextChanged: { + if (activeFocus) + channelopener.connectStr = text + } + onActiveFocusChanged: { + if (!activeFocus) + channelopener.connectStr = text + } + } + ColumnLayout { + spacing: 0 + ToolButton { + icon.source: '../../icons/paste.png' + icon.height: constants.iconSizeMedium + icon.width: constants.iconSizeMedium + onClicked: { + var cliptext = AppController.clipboardToText() + if (!cliptext) + return + if (channelopener.validateConnectString(cliptext)) { + channelopener.connectStr = cliptext + nodeUri.text = channelopener.connectStr + } else { + var dialog = app.messageDialog.createObject(app, { + text: qsTr('Invalid node-id or connect string') + }) + dialog.open() + } + } + } + ToolButton { + icon.source: '../../icons/qrcode.png' + icon.height: constants.iconSizeMedium + icon.width: constants.iconSizeMedium + scale: 1.2 + onClicked: { + var dialog = app.scanDialog.createObject(app, { + hint: qsTr('Scan a node-id or a connect string') + }) + dialog.onFoundText.connect(function(data) { + if (channelopener.validateConnectString(data)) { + channelopener.connectStr = data + nodeUri.text = channelopener.connectStr + } else { + var errdialog = app.messageDialog.createObject(app, { + text: qsTr('Invalid node-id or connect string') + }) + errdialog.open() + } + dialog.close() }) dialog.open() } } } - ToolButton { - icon.source: '../../icons/qrcode.png' - icon.height: constants.iconSizeMedium - icon.width: constants.iconSizeMedium - scale: 1.2 - onClicked: { - var dialog = app.scanDialog.createObject(app, { - hint: qsTr('Scan a node-id or a connect string') - }) - dialog.onFoundText.connect(function(data) { - if (channelopener.validateConnectString(data)) { - channelopener.connectStr = data - node.text = channelopener.connectStr - } else { - var errdialog = app.messageDialog.createObject(app, { - text: qsTr('Invalid node-id or connect string') - }) - errdialog.open() - } - dialog.close() - }) - dialog.open() - } - } } // trampoline @@ -160,15 +159,10 @@ ElDialog { } } - Label { - text: qsTr('Amount') - Layout.columnSpan: 3 - color: Material.accentColor - } - BtcField { id: amountBtc fiatfield: amountFiat + Layout.topMargin: constants.paddingLarge Layout.preferredWidth: amountFontMetrics.advanceWidth('0') * 14 + leftPadding + rightPadding onTextAsSatsChanged: { if (!is_max.checked) @@ -327,4 +321,8 @@ ElDialog { id: amountFontMetrics font: amountBtc.font } + FontMetrics { + id: nodeUriFontMetrics + font: nodeUri.font + } } From 3a740256c5958c93fa6dc67ecfa20ba8935ff513 Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Mon, 23 Feb 2026 13:01:18 +0100 Subject: [PATCH 11/28] qml: add missing button containers --- .../gui/qml/components/CloseChannelDialog.qml | 22 +++++++++++-------- .../qml/components/LnurlPayRequestDialog.qml | 17 ++++++++------ .../components/LnurlWithdrawRequestDialog.qml | 20 ++++++++++------- 3 files changed, 35 insertions(+), 24 deletions(-) diff --git a/electrum/gui/qml/components/CloseChannelDialog.qml b/electrum/gui/qml/components/CloseChannelDialog.qml index 1862ad7c8..aafb51e82 100644 --- a/electrum/gui/qml/components/CloseChannelDialog.qml +++ b/electrum/gui/qml/components/CloseChannelDialog.qml @@ -156,17 +156,21 @@ ElDialog { } } - FlatButton { + DialogButtonContainer { Layout.columnSpan: 2 Layout.fillWidth: true - text: qsTr('Close channel') - icon.source: '../../icons/closebutton.png' - enabled: !channeldetails.isClosing - onClicked: { - if (closetypegroup.checkedButton.closetype == 'local_force') { - showBackupThenClose() - } else { - doCloseChannel() + + FlatButton { + Layout.fillWidth: true + text: qsTr('Close channel') + icon.source: '../../icons/closebutton.png' + enabled: !channeldetails.isClosing + onClicked: { + if (closetypegroup.checkedButton.closetype == 'local_force') { + showBackupThenClose() + } else { + doCloseChannel() + } } } } diff --git a/electrum/gui/qml/components/LnurlPayRequestDialog.qml b/electrum/gui/qml/components/LnurlPayRequestDialog.qml index 64f45453d..195eea37b 100644 --- a/electrum/gui/qml/components/LnurlPayRequestDialog.qml +++ b/electrum/gui/qml/components/LnurlPayRequestDialog.qml @@ -129,15 +129,18 @@ ElDialog { } } - FlatButton { + DialogButtonContainer { Layout.topMargin: constants.paddingLarge Layout.fillWidth: true - text: qsTr('Pay...') - icon.source: '../../icons/confirmed.png' - enabled: valid - onClicked: { - invoiceParser.lnurlGetInvoice(comment.text) - dialog.close() + FlatButton { + Layout.fillWidth: true + text: qsTr('Pay...') + icon.source: '../../icons/confirmed.png' + enabled: valid + onClicked: { + invoiceParser.lnurlGetInvoice(comment.text) + dialog.close() + } } } } diff --git a/electrum/gui/qml/components/LnurlWithdrawRequestDialog.qml b/electrum/gui/qml/components/LnurlWithdrawRequestDialog.qml index 02a9be479..4e047e524 100644 --- a/electrum/gui/qml/components/LnurlWithdrawRequestDialog.qml +++ b/electrum/gui/qml/components/LnurlWithdrawRequestDialog.qml @@ -50,6 +50,7 @@ ElDialog { ColumnLayout { width: parent.width + spacing: 0 GridLayout { id: rootLayout @@ -160,16 +161,19 @@ ElDialog { } } - FlatButton { + DialogButtonContainer { Layout.topMargin: constants.paddingLarge Layout.fillWidth: true - text: qsTr('Withdraw...') - icon.source: '../../icons/confirmed.png' - enabled: valid && !requestDetails.busy - onClicked: { - var satsAmount = amountBtc.textAsSats.satsInt; - requestDetails.lnurlRequestWithdrawal(satsAmount); - dialog.close(); + FlatButton { + Layout.fillWidth: true + text: qsTr('Withdraw...') + icon.source: '../../icons/confirmed.png' + enabled: valid && !requestDetails.busy + onClicked: { + var satsAmount = amountBtc.textAsSats.satsInt; + requestDetails.lnurlRequestWithdrawal(satsAmount); + dialog.close(); + } } } } From 87bb63e4427a92fa01403d855c85967dbb5493a3 Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Mon, 23 Feb 2026 13:03:47 +0100 Subject: [PATCH 12/28] qml: use standard Button for buttons outside of buttoncontainer --- .../gui/qml/components/ChannelDetails.qml | 24 ++++------ electrum/gui/qml/components/SwapDialog.qml | 41 ++++++++--------- .../wizard/WCScriptAndDerivation.qml | 46 ++++++++----------- 3 files changed, 45 insertions(+), 66 deletions(-) diff --git a/electrum/gui/qml/components/ChannelDetails.qml b/electrum/gui/qml/components/ChannelDetails.qml index 42d55c37d..d43e1efdd 100644 --- a/electrum/gui/qml/components/ChannelDetails.qml +++ b/electrum/gui/qml/components/ChannelDetails.qml @@ -145,14 +145,10 @@ Pane { Layout.fillWidth: true Layout.preferredHeight: 1 } - Pane { - background: Rectangle { color: Material.dialogColor } - padding: 0 - FlatButton { - Layout.minimumWidth: implicitWidth - text: channeldetails.frozenForSending ? qsTr('Unfreeze') : qsTr('Freeze') - onClicked: channeldetails.freezeForSending() - } + Button { + Layout.minimumWidth: implicitWidth + text: channeldetails.frozenForSending ? qsTr('Unfreeze') : qsTr('Freeze') + onClicked: channeldetails.freezeForSending() } } @@ -182,14 +178,10 @@ Pane { Layout.fillWidth: true Layout.preferredHeight: 1 } - Pane { - background: Rectangle { color: Material.dialogColor } - padding: 0 - FlatButton { - Layout.minimumWidth: implicitWidth - text: channeldetails.frozenForReceiving ? qsTr('Unfreeze') : qsTr('Freeze') - onClicked: channeldetails.freezeForReceiving() - } + Button { + Layout.minimumWidth: implicitWidth + text: channeldetails.frozenForReceiving ? qsTr('Unfreeze') : qsTr('Freeze') + onClicked: channeldetails.freezeForReceiving() } } diff --git a/electrum/gui/qml/components/SwapDialog.qml b/electrum/gui/qml/components/SwapDialog.qml index 12bd5d668..d3eead881 100644 --- a/electrum/gui/qml/components/SwapDialog.qml +++ b/electrum/gui/qml/components/SwapDialog.qml @@ -253,31 +253,26 @@ ElDialog { } - Pane { + Button { Layout.alignment: Qt.AlignHCenter visible: _swaphelper.isNostr() - background: Rectangle { color: constants.darkerDialogBackground } - padding: 0 - - FlatButton { - text: qsTr('Choose swap provider') - enabled: _swaphelper.state != SwapHelper.Initializing - && _swaphelper.state != SwapHelper.Started - && _swaphelper.state != SwapHelper.Success - && _swaphelper.availableSwapServers.count - onClicked: { - var dialog = app.nostrSwapServersDialog.createObject(app, { - swaphelper: _swaphelper, - selectedPubkey: Config.swapServerNPub - }) - dialog.accepted.connect(function() { - if (Config.swapServerNPub != dialog.selectedPubkey) { - Config.swapServerNPub = dialog.selectedPubkey - _swaphelper.setReadyState() - } - }) - dialog.open() - } + text: qsTr('Choose swap provider') + enabled: _swaphelper.state != SwapHelper.Initializing + && _swaphelper.state != SwapHelper.Started + && _swaphelper.state != SwapHelper.Success + && _swaphelper.availableSwapServers.count + onClicked: { + var dialog = app.nostrSwapServersDialog.createObject(app, { + swaphelper: _swaphelper, + selectedPubkey: Config.swapServerNPub + }) + dialog.accepted.connect(function() { + if (Config.swapServerNPub != dialog.selectedPubkey) { + Config.swapServerNPub = dialog.selectedPubkey + _swaphelper.setReadyState() + } + }) + dialog.open() } } diff --git a/electrum/gui/qml/components/wizard/WCScriptAndDerivation.qml b/electrum/gui/qml/components/wizard/WCScriptAndDerivation.qml index 9464c8912..80d7d07cd 100644 --- a/electrum/gui/qml/components/wizard/WCScriptAndDerivation.qml +++ b/electrum/gui/qml/components/wizard/WCScriptAndDerivation.qml @@ -186,39 +186,31 @@ WizardComponent { iconStyle: InfoTextArea.IconStyle.Error } - Pane { + Button { Layout.alignment: Qt.AlignHCenter Layout.topMargin: constants.paddingLarge - padding: 0 visible: !isMultisig - background: Rectangle { - color: Qt.lighter(Material.dialogColor, 1.5) - } - - FlatButton { - text: qsTr('Detect Existing Accounts') - onClicked: { - var dialog = bip39recoveryDialog.createObject(mainLayout, { - walletType: wizard_data['wallet_type'], - seed: wizard_data['seed'], - seedExtraWords: wizard_data['seed_extra_words'] - }) - dialog.accepted.connect(function () { - // select matching script type button and set derivation path - for (var i = 0; i < scripttypegroup.buttons.length; i++) { - var btn = scripttypegroup.buttons[i] - if (btn.visible && btn.scripttype == dialog.scriptType) { - btn.checked = true - derivationpathtext.text = dialog.derivationPath - return - } + text: qsTr('Detect Existing Accounts') + onClicked: { + var dialog = bip39recoveryDialog.createObject(mainLayout, { + walletType: wizard_data['wallet_type'], + seed: wizard_data['seed'], + seedExtraWords: wizard_data['seed_extra_words'] + }) + dialog.accepted.connect(function () { + // select matching script type button and set derivation path + for (var i = 0; i < scripttypegroup.buttons.length; i++) { + var btn = scripttypegroup.buttons[i] + if (btn.visible && btn.scripttype == dialog.scriptType) { + btn.checked = true + derivationpathtext.text = dialog.derivationPath + return } - }) - dialog.open() - } + } + }) + dialog.open() } } - } } From 895679a6bef4f4ecfcf434036c3309a038ae846a Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Mon, 23 Feb 2026 13:16:48 +0100 Subject: [PATCH 13/28] qml: styling History, ProxyConfig and NostrConfigDialog --- electrum/gui/qml/components/History.qml | 4 +- .../gui/qml/components/NostrConfigDialog.qml | 20 +++--- .../qml/components/controls/ProxyConfig.qml | 72 +++++++++---------- 3 files changed, 46 insertions(+), 50 deletions(-) diff --git a/electrum/gui/qml/components/History.qml b/electrum/gui/qml/components/History.qml index c99be7599..7b2782bb7 100644 --- a/electrum/gui/qml/components/History.qml +++ b/electrum/gui/qml/components/History.qml @@ -14,8 +14,8 @@ Pane { padding: 0 clip: true - background: Rectangle { - color: constants.darkerBackground + background: PaneInsetBackground { + vertical: false } ElListView { diff --git a/electrum/gui/qml/components/NostrConfigDialog.qml b/electrum/gui/qml/components/NostrConfigDialog.qml index 2a62fc748..0eb6cfd86 100644 --- a/electrum/gui/qml/components/NostrConfigDialog.qml +++ b/electrum/gui/qml/components/NostrConfigDialog.qml @@ -52,16 +52,18 @@ ElDialog { Layout.rightMargin: constants.paddingLarge Layout.bottomMargin: constants.paddingLarge - DialogHighlightPane { + InfoTextArea { Layout.fillWidth: true - Label { - text: qsTr('Enter the list of Nostr relays') + '

    ' + - qsTr('Nostr relays are used to send and receive submarine swap offers.') + - ' ' + qsTr('For multisig wallets, nostr is also used to relay transactions to your co-signers.') + - ' ' + qsTr('Connections to nostr are only made when required, and ephemerally.') - width: parent.width - wrapMode: Text.Wrap - } + Layout.bottomMargin: constants.paddingLarge + compact: true + text: qsTr('Nostr relays are used to send and receive submarine swap offers.') + + ' ' + qsTr('For multisig wallets, nostr is also used to relay transactions to your co-signers.') + + ' ' + qsTr('Connections to nostr are only made when required, and ephemerally.') + backgroundColor: constants.darkerDialogBackground + } + + Label { + text: qsTr('Enter the list of Nostr relays') } RowLayout { diff --git a/electrum/gui/qml/components/controls/ProxyConfig.qml b/electrum/gui/qml/components/controls/ProxyConfig.qml index 05bc05b2b..e1e665d6e 100644 --- a/electrum/gui/qml/components/controls/ProxyConfig.qml +++ b/electrum/gui/qml/components/controls/ProxyConfig.qml @@ -53,68 +53,62 @@ Item { model: proxy_type_map } - GridLayout { - columns: 2 + ColumnLayout { + // columns: 2 Layout.fillWidth: true + spacing: constants.paddingSmall - Label { - text: qsTr("Address") - enabled: address.enabled - } + RowLayout { + Layout.fillWidth: true + Layout.rightMargin: constants.paddingLarge - TextField { - id: address - enabled: proxy_enabled_cb.checked - inputMethodHints: Qt.ImhNoPredictiveText + TextField { + id: address + Layout.fillWidth: true + enabled: proxy_enabled_cb.checked + inputMethodHints: Qt.ImhNoPredictiveText + placeholderText: qsTr("Address") + } + + TextField { + id: port + Layout.fillWidth: true + enabled: proxy_enabled_cb.checked + inputMethodHints: Qt.ImhDigitsOnly + placeholderText: qsTr("Port") + } } Label { - text: qsTr("Port") - enabled: port.enabled - } - - TextField { - id: port - enabled: proxy_enabled_cb.checked - inputMethodHints: Qt.ImhDigitsOnly - } - - Label { - text: qsTr("Username") + Layout.topMargin: constants.paddingLarge + text: qsTr("Authentication") enabled: username_tf.enabled } TextField { id: username_tf + Layout.fillWidth: true + Layout.rightMargin: constants.paddingLarge enabled: proxy_enabled_cb.checked inputMethodHints: Qt.ImhNoPredictiveText - } - - Label { - text: qsTr("Password") - enabled: password_tf.enabled + placeholderText: qsTr("Username") } PasswordField { id: password_tf enabled: proxy_enabled_cb.checked + placeholderText: qsTr("Password") } } - Pane { + Button { Layout.alignment: Qt.AlignHCenter Layout.topMargin: constants.paddingLarge - padding: 0 - background: Rectangle { - color: constants.darkerDialogBackground - } - FlatButton { - enabled: proxy_enabled_cb.checked && !_probing - text: qsTr('Detect Tor proxy') - onClicked: { - _probing = true - Network.probeTor() - } + enabled: proxy_enabled_cb.checked && !_probing + text: qsTr('Detect Tor proxy') + onClicked: { + _probing = true + Network.probeTor() } } From 7c83e749efd736443177595e308e746070be5d4c Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Mon, 23 Feb 2026 13:55:00 +0100 Subject: [PATCH 14/28] icons: square closebutton.png and copy_bw.png so they don't resize on highlight (qml) and upscale qrcode-[_white].png for the same reason and so we don't need to apply scaling --- electrum/gui/icons/closebutton.png | Bin 3521 -> 691 bytes electrum/gui/icons/copy_bw.png | Bin 880 -> 833 bytes electrum/gui/icons/qrcode.png | Bin 314 -> 394 bytes electrum/gui/icons/qrcode_white.png | Bin 380 -> 412 bytes .../components/ImportAddressesKeysDialog.qml | 1 - .../gui/qml/components/OpenChannelDialog.qml | 1 - electrum/gui/qml/components/SweepDialog.qml | 1 - .../qml/components/wizard/WCHaveMasterKey.qml | 1 - .../gui/qml/components/wizard/WCImport.qml | 1 - 9 files changed, 5 deletions(-) diff --git a/electrum/gui/icons/closebutton.png b/electrum/gui/icons/closebutton.png index 349241801c9d537ff2e54a57e08ee156c59f193c..ecd8a0de4b3bac6c62a20ea20c78688ed2ecc15b 100644 GIT binary patch literal 691 zcmV;k0!;mhP)?ozg zzBIWv(ij_KW9)x~ZM9k_zVBZ(8jYhUCP%J=onSRIkw|<(_hmYrzOV&1o6R~lKici~ zX{6W{=%LrzHEww+|25PGEO!A7 zpL*MBjg6&3Gnq`}V7UwE{W|)JTdlFNOsHN*KMRZUl8m*0h6_Hm9)5~k&skUub`gh6 zsGx#f*9|I_%2yT^1Bb<8aSSjQY78zE3L>A+AEV2=vifAR*=!nM@i&-Jl&l47XfBtV zqx&9lzBYsYQrHqFh@a!}SoyxMI-QORf2#{P-L4vqMmo<7 z@ow8#(+QT~e!p)6eE`O%kuj8lU4qr0W&>RXcMzwhW2h5o@KprRFb*ikJMmvjF?P?e ZzX1{dK5erZhvWbN002ovPDHLkV1jzVJa_;A literal 3521 zcmV;y4LX+uL$Nkc;* zP;zf(X>4Tx07wm;mUmQB*%pV-y*Itk5+Wca^cs2zAksTX6$DXM^`x7XQc?|s+0 z08spb1j2M!0f022SQPH-!CVp(%f$Br7!UytSOLJ{W@ZFO_(THK{JlMynW#v{v-a*T zfMmPdEWc1DbJqWVks>!kBnAKqMb$PuekK>?0+ds;#ThdH1j_W4DKdsJG8Ul;qO2n0 z#IJ1jr{*iW$(WZWsE0n`c;fQ!l&-AnmjxZO1uWyz`0VP>&nP`#itsL#`S=Q!g`M=rU9)45( zJ;-|dRq-b5&z?byo>|{)?5r=n76A4nTALlSzLiw~v~31J<>9PP?;rs31pu_(obw)r zY+jPY;tVGXi|p)da{-@gE-UCa`=5eu%D;v=_nFJ?`&K)q7e9d`Nfk3?MdhZarb|T3 z%nS~f&t(1g5dY)AIcd$w!z`Siz!&j_=v7hZlnI21XuE|xfmo0(WD10T)!}~_HYW!e zew}L+XmwuzeT6wtxJd`dZ#@7*BLgIEKY9Xv>st^p3dp{^Xswa2bB{85{^$B13tWnB z;Y>jyQ|9&zk7RNsqAVGs--K+z0uqo1bf5|}fi5rtEMN^BfHQCd-XH*kfJhJnmIE$G z0%<@5vOzxB0181d*a3EfYH$G5fqKvcPJ%XY23!PJzzuK<41h;K3WmW;Fah3yX$XSw z5EY_9s*o0>51B&N5F1(uc|$=^I1~fLLy3?Ol0f;;Ca4%HgQ}rJP(Ab`bQ-z{U4#0d z2hboi2K@njgb|nm(_szR0JebHusa+GN5aeCM0gdP2N%HG;Yzp`J`T6S7vUT504#-H z!jlL<$Or?`Mpy_N@kBz9SR?@vA#0H$qyni$nvf2p8@Y{0k#Xb$28W?xm>3qu8RLgp zjNxKdVb)?wFx8l2m{v>|<~C*!GlBVnrDD~wrdTJeKXwT=5u1%I#8zOBU|X=4u>;s) z>^mF|$G{ol9B_WP7+f-LHLe7=57&&lfa}8z;U@8Tyei%l?}87(bMRt(A-)QK9Dg3) zj~~XrCy)tR1Z#p1A(kK{Y$Q|=8VKhI{e%(1G*N-5Pjn)N5P8I0VkxnX*g?EW941ba z6iJ387g8iCnY4jaNopcpCOsy-A(P2EWJhusSwLP-t|XrzUnLKcKTwn?CKOLf97RIe zPB}`sKzTrUL#0v;sBY9)s+hW+T2H-1eM)^VN0T#`^Oxhvt&^*fYnAJldnHel*Ozyf zUoM{~Um<@={-*r60#U(0!Bc^wuvVc);k3d%g-J!4qLpHZVwz%!VuRu}#Ze`^l7W)9 z5>Kf>>9Eozr6C$Z)1`URxU@~QI@)F0FdauXr2Es8>BaOP=)Lp_WhG@>R;lZ?BJkMlIuMhw8ApiF&yDYW2hFJ?fJhni{?u z85&g@mo&yT8JcdI$(rSw=QPK(Xj%)k1X|@<=e1rim6`6$RAwc!i#egKuI;BS(LSWz zt39n_sIypSqfWEV6J3%nTQ@-4i zi$R;gsG*9XzhRzXqv2yCs*$VFDx+GXJH|L;wsDH_KI2;^u!)^Xl1YupO;gy^-c(?^ z&$Q1BYvyPsG^;hc$D**@Sy`+`)}T4VJji^bd7Jqw3q6Zii=7tT7GEswEK@D(EFW1Z zSp`^awCb?>!`j4}Yh7b~$A)U-W3$et-R8BesV(1jzwLcHnq9En7Q0Tn&-M=XBKs!$ zF$X<|c!#|X_tWYh)GZit z(Q)Cp9CDE^WG;+fcyOWARoj*0TI>4EP1lX*cEoMO-Pk?Z{kZ!p4@(b`M~lalr<3Oz z&kJ6Nm#vN_+kA5{dW4@^Vjg_`q%qU1ULk& z3Fr!>1V#i_2R;ij2@(Z$1jE4r!MlPVFVbHmT+|iPIq0wy5aS{>yK?9ZAjVh%SOwMWgFjair&;wpi!{CU}&@N=Eg#~ zLQ&zpEzVmGY{hI9Z0+4-0xS$$Xe-OToc?Y*V;rTcf_ zb_jRe-RZjXSeas3UfIyD;9afd%<`i0x4T#DzE)vdabOQ=k7SRuGN`h>O0Q~1)u-yD z>VX=Mn&!Rgd$;YK+Q-}1zu#?t(*cbG#Ronf6db&N$oEidtwC+YVcg-Y!_VuY>bk#Y ze_ww@?MU&F&qswvrN_dLb=5o6*Egs)ls3YRlE$&)amR1{;Ppd$6RYV^Go!iq1UMl% z@#4q$AMc(FJlT1QeX8jv{h#)>&{~RGq1N2iiMFIRX?sk2-|2wUogK~{EkB$8eDsX= znVPf8XG_nK&J~=SIiGia@9y}|z3FhX{g&gcj=lwb=lWgyFW&aLedUh- zof`v-2Kw$UzI*>(+&$@i-u=-BsSjR1%z8NeX#HdC`Hh-Z(6xI-`hmHDqv!v)W&&nrf>M(RhcN6(D;jNN*%^u_SYjF;2ng}*8Ow)d6M ztDk;%`@Lsk$;9w$(d(H%O5UixIr`T2ZRcd@e5nM;e)P!z}0cWrUep`CU> zXrT)WZWZiqoUIEt&ce^&ckyG0pFnWq;L3$A9Z;dr7fovy1*O;*jsF{ZO6Vl%eVphx z)4<^*H}^b#_ukyxo5UQ)v2N25zfG`$^#FX+d;HMDhaSFRExwCA`J*R|CH5;%8_O3`O7K=SFfflf6 zybHLx7mvqZb3wBJ@4`|q=t2`L>?<2zNiJw(;2>CCXdhT|#R6>%jJ5a#87(L)KP}tS zA+Y4ug+?Pkr2pdmpp}tm_|s?A2=ZDKuEf&G0Nk3_Z6dsZ{D$ zsZ_Fbp=Du{$)r`OR8H#k`Xf(#rBe>zuZZ{r&_f+)6@3QF5Txs!&;)-o(aWWWTw zZ&4AH9PEtt(?NbC0XBuDWTSv)2bP7U*;seOL5-rSS%FnyGntILM*f@7Y{06p*!75w zz^U#oOSNaIJ`1oW>=}~y0MXuB5`>C21~!1DEuPZa8xz@mJpT_>AiZ^sfssG~;(nU- zI`*E^1hpB)qGt(~s!2l^3I)4bt$rp$Rc#E6;FDspI0LkAYzbJh+Y5bkbY$i8`4brY zyB%dJrH1>`LmC~NVFhe4X~@pCqtIrtak^m;wV zwrvONViY@HAjc4uI1SXd7mI~67z~{0bc#!733(1t@pGl_l>!5YTjv@404(2D|7XoVMtEB^K>APo00000NkvXXu0mjfP_Muz+v}CkP*50pTPjL&p7* zZ2PWRoSmt<_LlD~^V1)`8_r#3xqV6K?Tsuyb3Q}#K=i@%9AVk0#J|gI=5<^CIU#-9 zEdI_d&e;0$w$l}<;CK0#ZI$Qa0>_9sjXD}E#KKJ^Oap(BPkMAzM zNsIZO`0`Z8%DW#*|K#Lwnw>Y}hqxuF>DXRf((|t4l7rzSR-a7x;jxFhDSAKl=|9s`FiO=n?JlcIleW`8swn}8r zCow@S1Ub&Z2NFI4!dR4m+)kDfL})=n0NLs20SZ*&n9s``zxw2!cSdIe6<-^Ae-`-j zZEfZM?RL#n)sBvbkoCXjb#Zt)%(6* z+7q#F)%%BDI1__RN$lO{3;w+i`v3mk?wXIwa%)A+TsE9#CMDbgHtA2lX11fkxZWO^ PCm1|k{an^LB{Ts5*wsP7 literal 880 zcmeAS@N?(olHy`uVBq!ia0vp^&w%(c2OE%_dN+S5kYY>nc6VX;4}uH!E}sk(;Vkfo zEM{Qf76xHPhFNnYfP(BLp1!W^kJ)%Q#8`StRz6~2VEXIn;uunK>+M}bBUML+;}8Gm z>~%>oHgfSg!n>ofh^g-Z+qwh1CsN*UPLqC<=G6CqMS0?)E$hthe%`mCFJH!o z_M68V_dJ$Zpu3-kkwaKO4H+x=2&lpEg-J(Q=2y;qa^OPwTbq;ihYi*ne4i{M^=sW) zX(sPTkUEDM$hg74afU;urd*OdSO0HeYpE=2|C`J&n-|WGpRx4)-`(Ctk0*teMoBCy zH;+2iWWbonbciL9X=&B3T}Sr6c)i=})9wd5#GTFe8yr`aKYm<~w{fWs%q1IA_UWz6 zw>~*@<<+gvRj#iVdv<5P-c7Hf#JuviiLVTFQxDq0T@BF#ahXAQ+4L>Nb)KIxyu!B5 zOxb>XXIi{lt^vvq55alEF>^IR}DUI1jNPFo$ppCj#RRFXXTQ zu|eU1z>J9?Kf^E?7Ou!JJebbcdVH?R&CHZL^CsO1v)g~LBqwbh?`nx>5fyhn{`Tj7 zXU|z2^Jzywy^s2Sv1ywg$YG7%8LNSjwBPg7jh{PgYd=}<_P&)^e$qVVe2vkK*hf(j zr<$svF^h0L$R~~bIy{c0ORa^kO_Gt^x9Z!p8s65M8!pY|l=l25wQt?}g-dmI9EXM* zBD_Fh_oJ!(#_MX||L@ZxOaCwb$J|!<_Hr#MXtnEC@$juR{}J!l9mt6mteu2r!|!bOY2U0XQ7XM2tE@@`E=7 zkX(#c9_YhO#Hhn1Kd3^08S100L=_-VbwD3_qe+ou&VYvi*#$Y!hda>X+JhW(22UCQ z`e-+rk6f{d6OxDV(dBXR2TvLRx@;fRWlqR^Bp;I`j!PcK9~QabAev9@iIIni!}xadv0GC0ct7#rwva~yGg5*r_;o>cyz3;|X&AD)A{*a(|E zj1NIP~EX-wvhI2TdB_ ohq5`KG&7Cu{0pUj42lo{06G4yRX~OJR{#J207*qoM6N<$g5%MeYXATM literal 314 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5X?s&R5hE&{2`t$$4J+o?qAcsSVvOxCYldTLNrF3#6c5of} zsvzu8Av?wG`TE03ZHLmEX0a_cU+{6}hK_|FF7k35ICOk<8|$e=wnu9t4sobF>-Fc= z5h!ew4Y%d5U_oN*LR}VwO(D={>(B!Ohk7Pb%o$W6z=Y<~4N#v1;BYJxG3p4( z58eiXL@-RNSJTCs=NdrKa?Ss0^37L=NW0J&i$;0@=A{QJ)^Qk>C@-T52 ze^BLuqeSIfCMIh%A00*W`B7wbSj=S^oaF$F4RpCVjyOMwjSo{#Dt}Oh04tgg&p};m zgiRjChpEG*j}U+GlmoyX(rvH+Bcm!dg}CGiiNh_zVeY`E0ia9epzJ$Pmp>;)BTOC8 zXOh_T;SvY(w?mofgCh+9UBnM%b3kck8r%67O8*!XApih9T-7w(E16#a0000y{D6rAs2rD2XskIMF-&u1PnGC-hE7r0003ANkl@Fw zNpz`c_y7-}NI{E4L%|<-2I4;m(L^9cLQ0V$O*$&l=L(6NTFs1Y*=zgG zA_D(8W@eD|0N^}3NZv|%Y~jzWE+0~K+<#80EX7q>K_S6 z-W<@~n6b7jY0(Zy(t_l5FUHL5=!7M`ma131HNf4M?!L@C>mJqwZxwqy>$(l()wI{$ zSMI(#q$`f4*LB+($vMf(>XWo4IZv@;p24Q6y!j304{0h@wpjz1ifxj#C3%tY`?(SE zk)NM+%iGv7BF;1b>>^^{&LJZ9W_DVAnMcG6fIEwb8GsW2{NtZ20H0qZ7yY}8ONmba O0000 Date: Mon, 23 Feb 2026 16:32:27 +0100 Subject: [PATCH 15/28] qml: various styling updates --- electrum/gui/qml/components/AddressDetails.qml | 1 + electrum/gui/qml/components/BalanceDetails.qml | 2 +- electrum/gui/qml/components/OpenChannelDialog.qml | 5 ++++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/electrum/gui/qml/components/AddressDetails.qml b/electrum/gui/qml/components/AddressDetails.qml index b631e4b6b..eaf4c610e 100644 --- a/electrum/gui/qml/components/AddressDetails.qml +++ b/electrum/gui/qml/components/AddressDetails.qml @@ -329,6 +329,7 @@ Pane { } } } + property color navigationBarBackgroundColor: constants.highlightBackground AddressDetails { id: addressdetails diff --git a/electrum/gui/qml/components/BalanceDetails.qml b/electrum/gui/qml/components/BalanceDetails.qml index 49b92a462..03efe73e7 100644 --- a/electrum/gui/qml/components/BalanceDetails.qml +++ b/electrum/gui/qml/components/BalanceDetails.qml @@ -233,8 +233,8 @@ Pane { } } - } + property color navigationBarBackgroundColor: constants.highlightBackground Component { id: openChannelDialog diff --git a/electrum/gui/qml/components/OpenChannelDialog.qml b/electrum/gui/qml/components/OpenChannelDialog.qml index b18da4d36..4315510a2 100644 --- a/electrum/gui/qml/components/OpenChannelDialog.qml +++ b/electrum/gui/qml/components/OpenChannelDialog.qml @@ -55,6 +55,7 @@ ElDialog { '\n\n', qsTr('If you want to have recoverable channels, you must create a new wallet with an Electrum seed') ].join('') + backgroundColor: constants.darkerDialogBackground } InfoTextArea { @@ -65,6 +66,7 @@ ElDialog { text: [ qsTr('You currently have recoverable channels setting disabled.'), qsTr('This means your channels cannot be recovered from seed.') ].join(' ') + backgroundColor: constants.darkerDialogBackground } // gossip @@ -158,10 +160,11 @@ ElDialog { } } + Item { Layout.columnSpan: 3; width: 1; height: constants.paddingLarge } + BtcField { id: amountBtc fiatfield: amountFiat - Layout.topMargin: constants.paddingLarge Layout.preferredWidth: amountFontMetrics.advanceWidth('0') * 14 + leftPadding + rightPadding onTextAsSatsChanged: { if (!is_max.checked) From 8e78d74723835a2719a4186336625448338270a2 Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Mon, 23 Feb 2026 13:18:01 +0100 Subject: [PATCH 16/28] qml: remove unused components --- .../components/ImportChannelBackupDialog.qml | 98 -------------- electrum/gui/qml/components/WalletSummary.qml | 128 ------------------ 2 files changed, 226 deletions(-) delete mode 100644 electrum/gui/qml/components/ImportChannelBackupDialog.qml delete mode 100644 electrum/gui/qml/components/WalletSummary.qml diff --git a/electrum/gui/qml/components/ImportChannelBackupDialog.qml b/electrum/gui/qml/components/ImportChannelBackupDialog.qml deleted file mode 100644 index 4de22020e..000000000 --- a/electrum/gui/qml/components/ImportChannelBackupDialog.qml +++ /dev/null @@ -1,98 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Controls - -import org.electrum 1.0 - -import "controls" - -ElDialog { - id: root - - property bool valid: false - - width: parent.width - height: parent.height - - padding: 0 - - title: qsTr('Import channel backup') - iconSource: Qt.resolvedUrl('../../icons/file.png') - - function verifyChannelBackup(text) { - return valid = Daemon.currentWallet.isValidChannelBackup(text) - } - - onAccepted: { - Daemon.currentWallet.importChannelBackup(channelbackup_ta.text) - } - - ColumnLayout { - anchors.fill: parent - spacing: 0 - - RowLayout { - Layout.fillWidth: true - Layout.leftMargin: constants.paddingLarge - - TextArea { - id: channelbackup_ta - Layout.fillWidth: true - Layout.minimumHeight: 80 - font.family: FixedFont - focus: true - wrapMode: TextEdit.WrapAnywhere - onTextChanged: verifyChannelBackup(text) - } - ColumnLayout { - ToolButton { - icon.source: '../../icons/paste.png' - icon.height: constants.iconSizeMedium - icon.width: constants.iconSizeMedium - onClicked: { - channelbackup_ta.text = AppController.clipboardToText() - } - } - ToolButton { - icon.source: '../../icons/qrcode.png' - icon.height: constants.iconSizeMedium - icon.width: constants.iconSizeMedium - scale: 1.2 - onClicked: { - var dialog = app.scanDialog.createObject(app, { - hint: qsTr('Scan a channel backup') - }) - dialog.onFoundText.connect(function(data) { - channelbackup_ta.text = data - dialog.close() - }) - dialog.open() - } - } - } - } - - TextArea { - id: validationtext - visible: text - Layout.fillWidth: true - Layout.leftMargin: constants.paddingLarge - - readOnly: true - wrapMode: TextInput.WordWrap - background: Rectangle { - color: 'transparent' - } - } - - Item { Layout.preferredWidth: 1; Layout.fillHeight: true } - - FlatButton { - Layout.fillWidth: true - enabled: valid - text: qsTr('Import') - onClicked: doAccept() - } - } - -} diff --git a/electrum/gui/qml/components/WalletSummary.qml b/electrum/gui/qml/components/WalletSummary.qml deleted file mode 100644 index baddd0645..000000000 --- a/electrum/gui/qml/components/WalletSummary.qml +++ /dev/null @@ -1,128 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Controls -import QtQuick.Controls.Material - -import org.electrum 1.0 - -import "controls" - -Item { - id: root - clip: true - implicitHeight: 0 - - function open() { - state = 'opened' - } - function close() { - state = '' - } - function toggle() { - if (state == 'opened') - state = '' - else - state = 'opened' - } - - states: [ - State { - name: 'opened' - PropertyChanges { target: root; implicitHeight: detailsPane.height } - } - ] - - transitions: [ - Transition { - from: '' - to: 'opened' - NumberAnimation { target: root; properties: 'implicitHeight'; duration: 200 } - }, - Transition { - from: 'opened' - to: '' - NumberAnimation { target: root; properties: 'implicitHeight'; duration: 100 } - } - ] - - Pane { - id: detailsPane - width: parent.width - anchors.bottom: parent.bottom - padding: 0 - background: Rectangle { - color: Material.dialogColor - } - - ColumnLayout { - id: rootLayout - width: parent.width - spacing: constants.paddingXLarge - - Item { Layout.preferredWidth: 1; Layout.preferredHeight: 1 } - - RowLayout { - Layout.fillWidth: true - FlatButton { - text: qsTr('More details') - Layout.fillWidth: true - Layout.preferredWidth: 1 - enabled: app.stack.currentItem.objectName != 'WalletDetails' - onClicked: { - root.close() - app.stack.pushOnRoot(Qt.resolvedUrl('WalletDetails.qml')) - } - } - FlatButton { - text: qsTr('Switch wallet') - Layout.fillWidth: true - icon.source: '../../icons/file.png' - Layout.preferredWidth: 1 - enabled: app.stack.currentItem.objectName != 'Wallets' - onClicked: { - root.close() - app.stack.pushOnRoot(Qt.resolvedUrl('Wallets.qml')) - } - } - } - } - } - - property string formattedTotalBalance - property string formattedTotalBalanceFiat - - function setBalances() { - root.formattedTotalBalance = Config.formatSats(Daemon.currentWallet.totalBalance) - if (Daemon.fx.enabled) { - root.formattedTotalBalanceFiat = Daemon.fx.fiatValue(Daemon.currentWallet.totalBalance, false) - } - } - - - // instead of all these explicit connections, we should expose - // formatted balances directly as a property - Connections { - target: Config - function onBaseUnitChanged() { setBalances() } - function onThousandsSeparatorChanged() { setBalances() } - } - - Connections { - target: Daemon - function onWalletLoaded() { setBalances() } - } - - Connections { - target: Daemon.fx - function onEnabledUpdated() { setBalances() } - function onQuotesUpdated() { setBalances() } - } - - Connections { - target: Daemon.currentWallet - function onBalanceChanged() { - setBalances() - } - } - -} From 31b197408993d81140341b6636305cc1c09e23c9 Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Mon, 23 Feb 2026 20:53:13 +0100 Subject: [PATCH 17/28] qml: FlatButton: show indicator for press-and-hold functionality --- .../gui/qml/components/WalletMainView.qml | 2 + .../qml/components/controls/FlatButton.qml | 42 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/electrum/gui/qml/components/WalletMainView.qml b/electrum/gui/qml/components/WalletMainView.qml index 8fdbb6469..55f73519d 100644 --- a/electrum/gui/qml/components/WalletMainView.qml +++ b/electrum/gui/qml/components/WalletMainView.qml @@ -303,6 +303,7 @@ Item { var dialog = receiveDetailsDialog.createObject(mainView) dialog.open() } + pressAndHoldIndicator: true onPressAndHold: { Config.userKnowsPressAndHold = true Daemon.currentWallet.deleteExpiredRequests() @@ -317,6 +318,7 @@ Item { text: qsTr('Send') enabled: !invoiceParser.busy && !piResolver.busy && !requestDetails.busy onClicked: openSendDialog() + pressAndHoldIndicator: true onPressAndHold: { Config.userKnowsPressAndHold = true app.stack.push(Qt.resolvedUrl('Invoices.qml')) diff --git a/electrum/gui/qml/components/controls/FlatButton.qml b/electrum/gui/qml/components/controls/FlatButton.qml index 621dec49b..ab3f0e409 100644 --- a/electrum/gui/qml/components/controls/FlatButton.qml +++ b/electrum/gui/qml/components/controls/FlatButton.qml @@ -9,6 +9,7 @@ TabButton { checkable: false property bool textUnderIcon: true + property bool pressAndHoldIndicator: false font.pixelSize: constants.fontSizeSmall icon.width: constants.iconSizeMedium @@ -25,4 +26,45 @@ TabButton { font: control.font color: !control.enabled ? control.Material.hintTextColor : control.down || control.checked ? control.Material.accentColor : control.Material.foreground } + + Rectangle { + id: indicator + anchors.top: control.top + anchors.horizontalCenter: control.horizontalCenter + width: 0 + opacity: 0 + height: 3 + color: control.Material.accentColor + + states: State { + name: 'pressing' + when: pressAndHoldIndicator && control.pressed + PropertyChanges { + target: indicator + width: control.width + opacity: 1 + } + } + + transitions: Transition { + to: 'pressing' + SequentialAnimation { + PauseAnimation { + duration: 200 + } + ParallelAnimation { + NumberAnimation { + target: indicator + property: "width" + duration: 600 + } + NumberAnimation { + target: indicator + property: "opacity" + duration: 600 + } + } + } + } + } } From 3f34e6bee0967f934cc7dcdeb2db4ac7493c4493 Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Wed, 25 Feb 2026 13:15:07 +0100 Subject: [PATCH 18/28] qml: additional styling InfoTextArea in dialogs --- electrum/gui/qml/components/InvoiceDialog.qml | 2 ++ .../gui/qml/components/LnurlPayRequestDialog.qml | 1 + .../qml/components/LnurlWithdrawRequestDialog.qml | 9 ++++++--- electrum/gui/qml/components/OpenChannelDialog.qml | 14 +++++++++----- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/electrum/gui/qml/components/InvoiceDialog.qml b/electrum/gui/qml/components/InvoiceDialog.qml index f2edb0f8c..2a91e5888 100644 --- a/electrum/gui/qml/components/InvoiceDialog.qml +++ b/electrum/gui/qml/components/InvoiceDialog.qml @@ -295,6 +295,8 @@ ElDialog { id: maxAmountMessage visible: amountMax.checked && text compact: true + backgroundColor: constants.darkerDialogBackground + Connections { target: invoice function onMaxAmountMessage(message) { diff --git a/electrum/gui/qml/components/LnurlPayRequestDialog.qml b/electrum/gui/qml/components/LnurlPayRequestDialog.qml index 195eea37b..da2a9f857 100644 --- a/electrum/gui/qml/components/LnurlPayRequestDialog.qml +++ b/electrum/gui/qml/components/LnurlPayRequestDialog.qml @@ -43,6 +43,7 @@ ElDialog { compact: true visible: invoiceParser.lnurlData['min_sendable_sat'] != invoiceParser.lnurlData['max_sendable_sat'] text: qsTr('Amount must be between %1 and %2 %3').arg(Config.formatSats(invoiceParser.lnurlData['min_sendable_sat'])).arg(Config.formatSats(invoiceParser.lnurlData['max_sendable_sat'])).arg(Config.baseUnit) + backgroundColor: constants.darkerDialogBackground } Label { diff --git a/electrum/gui/qml/components/LnurlWithdrawRequestDialog.qml b/electrum/gui/qml/components/LnurlWithdrawRequestDialog.qml index 4e047e524..e68c3843a 100644 --- a/electrum/gui/qml/components/LnurlWithdrawRequestDialog.qml +++ b/electrum/gui/qml/components/LnurlWithdrawRequestDialog.qml @@ -76,6 +76,7 @@ ElDialog { + '\n\n' + qsTr('Do a submarine swap in the \'Channels\' tab to get more incoming liquidity.') iconStyle: InfoTextArea.IconStyle.Error + backgroundColor: constants.darkerDialogBackground } InfoTextArea { @@ -84,9 +85,10 @@ ElDialog { compact: true visible: !dialog.insufficientLiquidity && dialog.providerMinWithdrawable != dialog.providerMaxWithdrawable text: qsTr('Amount must be between %1 and %2 %3') - .arg(Config.formatSats(dialog.effectiveMinWithdrawable)) - .arg(Config.formatSats(dialog.effectiveMaxWithdrawable)) - .arg(Config.baseUnit) + .arg(Config.formatSats(dialog.effectiveMinWithdrawable)) + .arg(Config.formatSats(dialog.effectiveMaxWithdrawable)) + .arg(Config.baseUnit) + backgroundColor: constants.darkerDialogBackground } InfoTextArea { @@ -100,6 +102,7 @@ ElDialog { + ' ' + qsTr('You may need to do a submarine swap to increase your incoming liquidity.') iconStyle: InfoTextArea.IconStyle.Warn + backgroundColor: constants.darkerDialogBackground } Label { diff --git a/electrum/gui/qml/components/OpenChannelDialog.qml b/electrum/gui/qml/components/OpenChannelDialog.qml index 4315510a2..a9b6f3d31 100644 --- a/electrum/gui/qml/components/OpenChannelDialog.qml +++ b/electrum/gui/qml/components/OpenChannelDialog.qml @@ -234,17 +234,21 @@ ElDialog { text: channelopener.warning visible: text compact: true + backgroundColor: constants.darkerDialogBackground } } } - FlatButton { + DialogButtonContainer { Layout.fillWidth: true - text: qsTr('Open Channel...') - icon.source: '../../icons/confirmed.png' - enabled: channelopener.valid - onClicked: channelopener.openChannel() + FlatButton { + Layout.fillWidth: true + text: qsTr('Open Channel...') + icon.source: '../../icons/confirmed.png' + enabled: channelopener.valid + onClicked: channelopener.openChannel() + } } } From 323189874b1551b9ea93ff028d30c6e5126046b2 Mon Sep 17 00:00:00 2001 From: f321x Date: Thu, 5 Mar 2026 16:57:54 +0100 Subject: [PATCH 19/28] android: update Qt6 to 6.10.2, PyQt6 to 6.10.2 --- contrib/android/Dockerfile | 2 +- contrib/android/p4a_recipes/pyqt6/__init__.py | 4 ++-- contrib/android/p4a_recipes/qt6/__init__.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/contrib/android/Dockerfile b/contrib/android/Dockerfile index 3520ffe76..b2d704a9c 100644 --- a/contrib/android/Dockerfile +++ b/contrib/android/Dockerfile @@ -235,7 +235,7 @@ RUN cd /opt \ && /opt/venv/bin/python3 -m pip install --no-build-isolation --no-dependencies -e . # install python-for-android -ENV P4A_CHECKOUT_COMMIT="375a05de21b538d704174b1efeb3fc85d151f94e" +ENV P4A_CHECKOUT_COMMIT="ee6886ec3048670b182afb18237e0ae69fde1254" # ^ from branch electrum_202602 (note: careful with force-pushing! see #8162) RUN cd /opt \ && git clone https://github.com/spesmilo/python-for-android \ diff --git a/contrib/android/p4a_recipes/pyqt6/__init__.py b/contrib/android/p4a_recipes/pyqt6/__init__.py index 2ca63803e..6a1821b28 100644 --- a/contrib/android/p4a_recipes/pyqt6/__init__.py +++ b/contrib/android/p4a_recipes/pyqt6/__init__.py @@ -6,13 +6,13 @@ from pythonforandroid.util import load_source util = load_source('util', os.path.join(os.path.dirname(os.path.dirname(__file__)), 'util.py')) -assert PyQt6Recipe._version == "6.10.1" +assert PyQt6Recipe._version == "6.10.2" assert PyQt6Recipe.depends == ['qt6', 'pyjnius', 'setuptools', 'pyqt6sip', 'hostpython3', 'python3'], PyQt6Recipe.depends assert PyQt6Recipe.python_depends == [] class PyQt6RecipePinned(util.InheritedRecipeMixin, PyQt6Recipe): - sha512sum = "af9bb54b20fd177cf1dac5fe8fb0ff289e1e7e42716d09093d49dd99a7d8065c6b6f34784ed19e21e7e07ba0d550b270cb6be7273f7180e2bf886160fc773d01" + sha512sum = "d58515d181530fdd71edc3edfa0b647a3aeeb56cbc33f4d7fd0d40a7a99d52298ac5bb4438b5dadea5439759e52cc459e601f1fab5d9afdd61f2a492d0bae1ef" recipe = PyQt6RecipePinned() diff --git a/contrib/android/p4a_recipes/qt6/__init__.py b/contrib/android/p4a_recipes/qt6/__init__.py index 741826e7c..dcb443d9a 100644 --- a/contrib/android/p4a_recipes/qt6/__init__.py +++ b/contrib/android/p4a_recipes/qt6/__init__.py @@ -5,13 +5,13 @@ from pythonforandroid.util import load_source util = load_source('util', os.path.join(os.path.dirname(os.path.dirname(__file__)), 'util.py')) -assert Qt6Recipe._version == "6.10.1" +assert Qt6Recipe._version == "6.10.2" assert Qt6Recipe.depends == ['python3', 'hostqt6'] assert Qt6Recipe.python_depends == [] class Qt6RecipePinned(util.InheritedRecipeMixin, Qt6Recipe): - sha512sum = "62e8a8fcdef84187bff43e6185a1ba983e3db4d927ec01cd0ff5247d12eb7fd116a8f67323b3e44ba23f2e1792ade8c54e033cf28f34ec42a776ec204b9c2d8d" + sha512sum = "bf1a1d42d57b4d2e77f7227f4cbe01e847fd65035461b89481063b32f25a57be6e5a07889acc4af65ca9ff9d27b7fe63bd2fe60b8aa7fa19d554394d799fbaa1" recipe = Qt6RecipePinned() From f2e8b466cd63480d12e7ed5dc0992b424e38d146 Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Fri, 13 Mar 2026 15:25:15 +0100 Subject: [PATCH 20/28] qml: add type hints for QVariant pyqtProperty workarounds --- electrum/gui/qml/qeaddressdetails.py | 2 +- electrum/gui/qml/qechannelopener.py | 4 ++-- electrum/gui/qml/qeinvoice.py | 4 ++-- electrum/gui/qml/qelnpaymentdetails.py | 2 +- electrum/gui/qml/qerequestdetails.py | 2 +- electrum/gui/qml/qeswaphelper.py | 18 +++++++++--------- electrum/gui/qml/qetxdetails.py | 2 +- electrum/gui/qml/qetxfinalizer.py | 16 ++++++++-------- 8 files changed, 25 insertions(+), 25 deletions(-) diff --git a/electrum/gui/qml/qeaddressdetails.py b/electrum/gui/qml/qeaddressdetails.py index c17f50d4c..fbe110127 100644 --- a/electrum/gui/qml/qeaddressdetails.py +++ b/electrum/gui/qml/qeaddressdetails.py @@ -36,7 +36,7 @@ class QEAddressDetails(AuthMixin, QObject): walletChanged = pyqtSignal() @pyqtProperty(QVariant, notify=walletChanged) - def wallet(self): + def wallet(self) -> QEWallet: return self._wallet @wallet.setter diff --git a/electrum/gui/qml/qechannelopener.py b/electrum/gui/qml/qechannelopener.py index f01fd7713..957aa30d2 100644 --- a/electrum/gui/qml/qechannelopener.py +++ b/electrum/gui/qml/qechannelopener.py @@ -55,7 +55,7 @@ class QEChannelOpener(QObject, AuthMixin): walletChanged = pyqtSignal() @pyqtProperty(QVariant, notify=walletChanged) - def wallet(self): + def wallet(self) -> QEWallet: return self._wallet @wallet.setter @@ -80,7 +80,7 @@ class QEChannelOpener(QObject, AuthMixin): amountChanged = pyqtSignal() @pyqtProperty(QVariant, notify=amountChanged) - def amount(self): + def amount(self) -> QEAmount: return self._amount @amount.setter diff --git a/electrum/gui/qml/qeinvoice.py b/electrum/gui/qml/qeinvoice.py index e9d30fc32..696506ffd 100644 --- a/electrum/gui/qml/qeinvoice.py +++ b/electrum/gui/qml/qeinvoice.py @@ -112,7 +112,7 @@ class QEInvoice(QObject, QtEventListener): walletChanged = pyqtSignal() @pyqtProperty(QVariant, notify=walletChanged) - def wallet(self): + def wallet(self) -> QEWallet: return self._wallet @wallet.setter @@ -155,7 +155,7 @@ class QEInvoice(QObject, QtEventListener): return self._amount @pyqtProperty(QVariant, notify=amountOverrideChanged) - def amountOverride(self): + def amountOverride(self) -> QEAmount: return self._amountOverride @amountOverride.setter diff --git a/electrum/gui/qml/qelnpaymentdetails.py b/electrum/gui/qml/qelnpaymentdetails.py index 2857c1828..94e96138c 100644 --- a/electrum/gui/qml/qelnpaymentdetails.py +++ b/electrum/gui/qml/qelnpaymentdetails.py @@ -28,7 +28,7 @@ class QELnPaymentDetails(QObject): walletChanged = pyqtSignal() @pyqtProperty(QVariant, notify=walletChanged) - def wallet(self): + def wallet(self) -> QEWallet: return self._wallet @wallet.setter diff --git a/electrum/gui/qml/qerequestdetails.py b/electrum/gui/qml/qerequestdetails.py index 37fd70b5c..f0f2155e9 100644 --- a/electrum/gui/qml/qerequestdetails.py +++ b/electrum/gui/qml/qerequestdetails.py @@ -75,7 +75,7 @@ class QERequestDetails(QObject, QtEventListener): walletChanged = pyqtSignal() @pyqtProperty(QVariant, notify=walletChanged) - def wallet(self): + def wallet(self) -> QEWallet: return self._wallet @wallet.setter diff --git a/electrum/gui/qml/qeswaphelper.py b/electrum/gui/qml/qeswaphelper.py index c112acf42..8bbf9483d 100644 --- a/electrum/gui/qml/qeswaphelper.py +++ b/electrum/gui/qml/qeswaphelper.py @@ -206,7 +206,7 @@ class QESwapHelper(AuthMixin, QObject, QtEventListener): walletChanged = pyqtSignal() @pyqtProperty(QVariant, notify=walletChanged) - def wallet(self): + def wallet(self) -> QEWallet: return self._wallet @wallet.setter @@ -296,11 +296,11 @@ class QESwapHelper(AuthMixin, QObject, QtEventListener): tosendChanged = pyqtSignal() @pyqtProperty(QVariant, notify=tosendChanged) - def tosend(self): + def tosend(self) -> QEAmount: return self._tosend @tosend.setter - def tosend(self, tosend): + def tosend(self, tosend: QEAmount): assert tosend is None or isinstance(tosend, QEAmount) if self._tosend != tosend: self._tosend = tosend @@ -308,11 +308,11 @@ class QESwapHelper(AuthMixin, QObject, QtEventListener): toreceiveChanged = pyqtSignal() @pyqtProperty(QVariant, notify=toreceiveChanged) - def toreceive(self): + def toreceive(self) -> QEAmount: return self._toreceive @toreceive.setter - def toreceive(self, toreceive): + def toreceive(self, toreceive: QEAmount): assert toreceive is None or isinstance(toreceive, QEAmount) if self._toreceive != toreceive: self._toreceive = toreceive @@ -320,11 +320,11 @@ class QESwapHelper(AuthMixin, QObject, QtEventListener): serverMiningfeeChanged = pyqtSignal() @pyqtProperty(QVariant, notify=serverMiningfeeChanged) - def serverMiningfee(self): + def serverMiningfee(self) -> QEAmount: return self._server_miningfee @serverMiningfee.setter - def serverMiningfee(self, server_miningfee): + def serverMiningfee(self, server_miningfee: QEAmount): assert server_miningfee is None or isinstance(server_miningfee, QEAmount) if self._server_miningfee != server_miningfee: self._server_miningfee = server_miningfee @@ -343,11 +343,11 @@ class QESwapHelper(AuthMixin, QObject, QtEventListener): miningfeeChanged = pyqtSignal() @pyqtProperty(QVariant, notify=miningfeeChanged) - def miningfee(self): + def miningfee(self) -> QEAmount: return self._miningfee @miningfee.setter - def miningfee(self, miningfee): + def miningfee(self, miningfee: QEAmount): assert miningfee is None or isinstance(miningfee, QEAmount) if self._miningfee != miningfee: self._miningfee = miningfee diff --git a/electrum/gui/qml/qetxdetails.py b/electrum/gui/qml/qetxdetails.py index cfbf2f67a..a5e4dabe0 100644 --- a/electrum/gui/qml/qetxdetails.py +++ b/electrum/gui/qml/qetxdetails.py @@ -104,7 +104,7 @@ class QETxDetails(QObject, QtEventListener): walletChanged = pyqtSignal() @pyqtProperty(QVariant, notify=walletChanged) - def wallet(self): + def wallet(self) -> QEWallet: return self._wallet @wallet.setter diff --git a/electrum/gui/qml/qetxfinalizer.py b/electrum/gui/qml/qetxfinalizer.py index 2e46eb0f7..d0457c80e 100644 --- a/electrum/gui/qml/qetxfinalizer.py +++ b/electrum/gui/qml/qetxfinalizer.py @@ -69,7 +69,7 @@ class FeeSlider(QObject): walletChanged = pyqtSignal() @pyqtProperty(QVariant, notify=walletChanged) - def wallet(self): + def wallet(self) -> QEWallet: return self._wallet @wallet.setter @@ -172,11 +172,11 @@ class TxFeeSlider(FeeSlider): feeChanged = pyqtSignal() @pyqtProperty(QVariant, notify=feeChanged) - def fee(self): + def fee(self) -> QEAmount: return self._fee @fee.setter - def fee(self, fee): + def fee(self, fee: QEAmount): assert fee is None or isinstance(fee, QEAmount) if self._fee != fee: self._fee.copyFrom(fee) @@ -422,7 +422,7 @@ class QETxFinalizer(TxFeeSlider): amountChanged = pyqtSignal() @pyqtProperty(QVariant, notify=amountChanged) - def amount(self): + def amount(self) -> QEAmount: return self._amount @amount.setter @@ -440,7 +440,7 @@ class QETxFinalizer(TxFeeSlider): extraFeeChanged = pyqtSignal() @pyqtProperty(QVariant, notify=extraFeeChanged) - def extraFee(self): + def extraFee(self) -> QEAmount: return self._extraFee @extraFee.setter @@ -671,7 +671,7 @@ class QETxRbfFeeBumper(TxFeeSlider, TxMonMixin): oldfeeChanged = pyqtSignal() @pyqtProperty(QVariant, notify=oldfeeChanged) - def oldfee(self): + def oldfee(self) -> QEAmount: return self._oldfee @oldfee.setter @@ -812,7 +812,7 @@ class QETxCanceller(TxFeeSlider, TxMonMixin): oldfeeChanged = pyqtSignal() @pyqtProperty(QVariant, notify=oldfeeChanged) - def oldfee(self): + def oldfee(self) -> QEAmount: return self._oldfee @oldfee.setter @@ -945,7 +945,7 @@ class QETxCpfpFeeBumper(TxFeeSlider, TxMonMixin): totalFeeChanged = pyqtSignal() @pyqtProperty(QVariant, notify=totalFeeChanged) - def totalFee(self): + def totalFee(self) -> QEAmount: return self._total_fee @totalFee.setter From 74f3c0427df8413417fdae79ab8b1e7d6b3770d7 Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Tue, 17 Mar 2026 15:45:08 +0100 Subject: [PATCH 21/28] android: pin hostpython3 PyProjectRecipe versions, pin android and pyjnius recipes Cython version --- contrib/android/Dockerfile | 2 +- .../android/p4a_recipes/android/__init__.py | 20 +++++++++++++ .../p4a_recipes/hostpython3/__init__.py | 28 ++++++++++++++++++- .../android/p4a_recipes/pyjnius/__init__.py | 7 ++++- 4 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 contrib/android/p4a_recipes/android/__init__.py diff --git a/contrib/android/Dockerfile b/contrib/android/Dockerfile index b2d704a9c..d396fbe68 100644 --- a/contrib/android/Dockerfile +++ b/contrib/android/Dockerfile @@ -235,7 +235,7 @@ RUN cd /opt \ && /opt/venv/bin/python3 -m pip install --no-build-isolation --no-dependencies -e . # install python-for-android -ENV P4A_CHECKOUT_COMMIT="ee6886ec3048670b182afb18237e0ae69fde1254" +ENV P4A_CHECKOUT_COMMIT="3aa0db1ed9b7349625c5fdeff96df4e564403541" # ^ from branch electrum_202602 (note: careful with force-pushing! see #8162) RUN cd /opt \ && git clone https://github.com/spesmilo/python-for-android \ diff --git a/contrib/android/p4a_recipes/android/__init__.py b/contrib/android/p4a_recipes/android/__init__.py new file mode 100644 index 000000000..17bd29ad2 --- /dev/null +++ b/contrib/android/p4a_recipes/android/__init__.py @@ -0,0 +1,20 @@ +import os + +from pythonforandroid.recipes.android import AndroidRecipe +from pythonforandroid.util import load_source, HashPinnedDependency + +util = load_source('util', os.path.join(os.path.dirname(os.path.dirname(__file__)), 'util.py')) + + +assert AndroidRecipe.depends == [('sdl3', 'sdl2', 'genericndkbuild', 'qt6'), 'pyjnius', 'python3'], AndroidRecipe.depends +assert AndroidRecipe.python_depends == [] + + +class AndroidRecipePinned(util.InheritedRecipeMixin, AndroidRecipe): + hostpython_prerequisites = [ + HashPinnedDependency(package="Cython==3.1.8", + hashes=['sha256:282b3c8e6abc3fea421919e862e898ffdd86fc0796009bdb5ffdf8211413219f']) + ] + + +recipe = AndroidRecipePinned() diff --git a/contrib/android/p4a_recipes/hostpython3/__init__.py b/contrib/android/p4a_recipes/hostpython3/__init__.py index 9d1dcb89b..5d9f0d6eb 100644 --- a/contrib/android/p4a_recipes/hostpython3/__init__.py +++ b/contrib/android/p4a_recipes/hostpython3/__init__.py @@ -1,7 +1,7 @@ import os from pythonforandroid.recipes.hostpython3 import HostPython3Recipe -from pythonforandroid.util import load_source +from pythonforandroid.util import load_source, HashPinnedDependency util = load_source('util', os.path.join(os.path.dirname(os.path.dirname(__file__)), 'util.py')) @@ -15,5 +15,31 @@ class HostPython3RecipePinned(util.InheritedRecipeMixin, HostPython3Recipe): version = "3.11.14" sha512sum = "4642f6d59c76c6e5dbd827fdb28694376a9cc76e513146d092b49afb41513b3c9dff2339cfcebfb5b260f5cdc49a59a69906e284e5d478b2189d3374e9e24fd5" + # this property overrides the default hostpython dependencies for PyProjectRecipe recipies + pyproject_base_dependencies = [ + HashPinnedDependency(package="build[virtualenv]==1.4.0", + hashes=['sha256:6a07c1b8eb6f2b311b96fcbdbce5dab5fe637ffda0fd83c9cac622e927501596']), + HashPinnedDependency(package="pip==24.0", + hashes=['sha256:ba0d021a166865d2265246961bec0152ff124de910c5cc39f1156ce3fa7c69dc']), + HashPinnedDependency(package="setuptools==80.9.0", + hashes=['sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922']), + + # pin deptree build[virtualenv]==1.4.0 + HashPinnedDependency(package="packaging==26.0", + hashes=['sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529']), + HashPinnedDependency(package="pyproject_hooks==1.2.0", + hashes=['sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913']), + HashPinnedDependency(package="virtualenv==21.2.0", + hashes=['sha256:1bd755b504931164a5a496d217c014d098426cddc79363ad66ac78125f9d908f']), + HashPinnedDependency(package="distlib==0.4.0", + hashes=['sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16']), + HashPinnedDependency(package="filelock==3.25.2", + hashes=['sha256:ca8afb0da15f229774c9ad1b455ed96e85a81373065fb10446672f64444ddf70']), + HashPinnedDependency(package="platformdirs==4.9.4", + hashes=['sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868']), + HashPinnedDependency(package="python_discovery==1.1.3", + hashes=['sha256:90e795f0121bc84572e737c9aa9966311b9fde44ffb88a5953b3ec9b31c6945e']), + ] + recipe = HostPython3RecipePinned() diff --git a/contrib/android/p4a_recipes/pyjnius/__init__.py b/contrib/android/p4a_recipes/pyjnius/__init__.py index 7e850c922..267f5d9a3 100644 --- a/contrib/android/p4a_recipes/pyjnius/__init__.py +++ b/contrib/android/p4a_recipes/pyjnius/__init__.py @@ -1,7 +1,7 @@ import os from pythonforandroid.recipes.pyjnius import PyjniusRecipe -from pythonforandroid.util import load_source +from pythonforandroid.util import load_source, HashPinnedDependency util = load_source('util', os.path.join(os.path.dirname(os.path.dirname(__file__)), 'util.py')) @@ -12,6 +12,11 @@ assert PyjniusRecipe.python_depends == [] class PyjniusRecipePinned(util.InheritedRecipeMixin, PyjniusRecipe): + hostpython_prerequisites = [ + HashPinnedDependency(package="Cython==3.1.8", + hashes=['sha256:282b3c8e6abc3fea421919e862e898ffdd86fc0796009bdb5ffdf8211413219f']) + ] + sha512sum = "a192c30ef87ca9601455976feb49f03dfdb8e1bf2545744a7b771a6d0930a56b334c7a2a39d30fb8855c070f16e4673dc5ff6920b04a6155ab5f9247b271df76" From 854f95b7948601985291ee417b5729a321098f1d Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Wed, 18 Mar 2026 22:50:13 +0100 Subject: [PATCH 22/28] android: openssl 3.0.18 --- contrib/android/p4a_recipes/openssl/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/contrib/android/p4a_recipes/openssl/__init__.py b/contrib/android/p4a_recipes/openssl/__init__.py index 501d0bcd0..062b18e58 100644 --- a/contrib/android/p4a_recipes/openssl/__init__.py +++ b/contrib/android/p4a_recipes/openssl/__init__.py @@ -6,13 +6,14 @@ from pythonforandroid.util import load_source util = load_source('util', os.path.join(os.path.dirname(os.path.dirname(__file__)), 'util.py')) -assert OpenSSLRecipe._version == "3.3.1" +# assert OpenSSLRecipe._version == "3.3.1" assert OpenSSLRecipe.depends == [] assert OpenSSLRecipe.python_depends == [] class OpenSSLRecipePinned(util.InheritedRecipeMixin, OpenSSLRecipe): - sha512sum = "d3682a5ae0721748c6b9ec2f1b74d2b1ba61ee6e4c0d42387b5037a56ef34312833b6abb522d19400b45d807dd65cc834156f5e891cb07fbaf69fcf67e1c595d" + version = "3.0.18" + sha512sum = "6bdd16f33b83ae2a12777230c4ff00d0595bbc00253ac8c3ac31e1375e818fc74d7f491bd2e507ff33cab9f0498cfb28fa8690f75a98663568d40901523cdf3c" recipe = OpenSSLRecipePinned() From c8f5798d4ea0fa91149ebf5b602bdc883498bf6d Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Wed, 18 Mar 2026 23:46:48 +0100 Subject: [PATCH 23/28] android: build pyqt_builder and sip ourselves, hash pin all hostpython_prerequisites --- contrib/android/Dockerfile | 2 +- contrib/android/p4a_recipes/pyparsing/__init__.py | 8 ++++++++ contrib/android/p4a_recipes/pyqt6/__init__.py | 4 ++-- contrib/android/p4a_recipes/pyqt_builder/__init__.py | 4 +++- contrib/android/p4a_recipes/setuptools/__init__.py | 6 ++++++ contrib/android/p4a_recipes/sip/__init__.py | 2 +- 6 files changed, 21 insertions(+), 5 deletions(-) diff --git a/contrib/android/Dockerfile b/contrib/android/Dockerfile index d396fbe68..8a136c28e 100644 --- a/contrib/android/Dockerfile +++ b/contrib/android/Dockerfile @@ -235,7 +235,7 @@ RUN cd /opt \ && /opt/venv/bin/python3 -m pip install --no-build-isolation --no-dependencies -e . # install python-for-android -ENV P4A_CHECKOUT_COMMIT="3aa0db1ed9b7349625c5fdeff96df4e564403541" +ENV P4A_CHECKOUT_COMMIT="0b9f7e763866cd3533417418d4b31e7b316b624e" # ^ from branch electrum_202602 (note: careful with force-pushing! see #8162) RUN cd /opt \ && git clone https://github.com/spesmilo/python-for-android \ diff --git a/contrib/android/p4a_recipes/pyparsing/__init__.py b/contrib/android/p4a_recipes/pyparsing/__init__.py index 04f515dbc..c6a3e9e1e 100644 --- a/contrib/android/p4a_recipes/pyparsing/__init__.py +++ b/contrib/android/p4a_recipes/pyparsing/__init__.py @@ -1,4 +1,5 @@ from pythonforandroid.recipes.pyparsing import PyparsingRecipe +from pythonforandroid.util import HashPinnedDependency assert PyparsingRecipe._version == "3.0.7" @@ -15,5 +16,12 @@ class PyparsingRecipePinned(PyparsingRecipe): # see "PyProjectRecipe" from https://github.com/kivy/python-for-android/pull/3007 sha512sum = "1e692f4cdaa6b6e8ca2729d0a3e2ba16d978f1957c538b6de3a4220ec7d996bdbe87c41c43abab851fffa3b0498a05841373e435602917b8c095042e273badb5" + hostpython_prerequisites = [ + HashPinnedDependency(package="setuptools==80.9.0", + hashes=['sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922']), + HashPinnedDependency(package="pip==24.0", + hashes=['sha256:ba0d021a166865d2265246961bec0152ff124de910c5cc39f1156ce3fa7c69dc']), + ] + recipe = PyparsingRecipePinned() diff --git a/contrib/android/p4a_recipes/pyqt6/__init__.py b/contrib/android/p4a_recipes/pyqt6/__init__.py index 6a1821b28..1f08245d2 100644 --- a/contrib/android/p4a_recipes/pyqt6/__init__.py +++ b/contrib/android/p4a_recipes/pyqt6/__init__.py @@ -1,13 +1,13 @@ import os from pythonforandroid.recipes.pyqt6 import PyQt6Recipe -from pythonforandroid.util import load_source +from pythonforandroid.util import load_source, HashPinnedDependency util = load_source('util', os.path.join(os.path.dirname(os.path.dirname(__file__)), 'util.py')) assert PyQt6Recipe._version == "6.10.2" -assert PyQt6Recipe.depends == ['qt6', 'pyjnius', 'setuptools', 'pyqt6sip', 'hostpython3', 'python3'], PyQt6Recipe.depends +assert PyQt6Recipe.depends == ['qt6', 'pyjnius', 'setuptools', 'pyqt6sip', 'hostpython3', 'pyqt_builder', 'python3'], PyQt6Recipe.depends assert PyQt6Recipe.python_depends == [] diff --git a/contrib/android/p4a_recipes/pyqt_builder/__init__.py b/contrib/android/p4a_recipes/pyqt_builder/__init__.py index 516dc99aa..3849943fe 100644 --- a/contrib/android/p4a_recipes/pyqt_builder/__init__.py +++ b/contrib/android/p4a_recipes/pyqt_builder/__init__.py @@ -1,12 +1,14 @@ from pythonforandroid.recipes.pyqt_builder import PyQtBuilderRecipe +from pythonforandroid.util import HashPinnedDependency assert PyQtBuilderRecipe._version == "1.19.1" -assert PyQtBuilderRecipe.depends == ["sip", "packaging", "python3"] +assert PyQtBuilderRecipe.depends == ["packaging", "sip", "python3"], PyQtBuilderRecipe.depends assert PyQtBuilderRecipe.python_depends == [] class PyQtBuilderRecipePinned(PyQtBuilderRecipe): sha512sum = "2308c51f93c37b1d13f312e4f2475d26b22d374ef284925fead9eab4aa89b994770431aca45170ac2154b4813fff151798f113f56d4cbf6c6e544fb463104a6d" + recipe = PyQtBuilderRecipePinned() diff --git a/contrib/android/p4a_recipes/setuptools/__init__.py b/contrib/android/p4a_recipes/setuptools/__init__.py index 23c3596aa..d60d39043 100644 --- a/contrib/android/p4a_recipes/setuptools/__init__.py +++ b/contrib/android/p4a_recipes/setuptools/__init__.py @@ -1,4 +1,5 @@ from pythonforandroid.recipes.setuptools import SetuptoolsRecipe +from pythonforandroid.util import HashPinnedDependency assert SetuptoolsRecipe._version == "80.9.0" @@ -7,6 +8,11 @@ assert SetuptoolsRecipe.python_depends == [] class SetuptoolsRecipePinned(SetuptoolsRecipe): + hostpython_prerequisites = [ + HashPinnedDependency(package='setuptools==80.9.0', + hashes=[]), + ] + sha512sum = "36eb1f219d29c6b9e135936bde2001ad70a971c8069cd0175d3a5325b450e6843a903d3f70043c9f534768ebeab8ab0c544b8f44456555d333f1ed72daa5c18b" diff --git a/contrib/android/p4a_recipes/sip/__init__.py b/contrib/android/p4a_recipes/sip/__init__.py index 417c9be16..456f707dc 100644 --- a/contrib/android/p4a_recipes/sip/__init__.py +++ b/contrib/android/p4a_recipes/sip/__init__.py @@ -2,7 +2,7 @@ from pythonforandroid.recipes.sip import SipRecipe assert SipRecipe._version == "6.15.1" -assert SipRecipe.depends == ["setuptools", "packaging", "tomli", "python3"], SipRecipe.depends +assert SipRecipe.depends == ["setuptools", "packaging", "python3"], SipRecipe.depends assert SipRecipe.python_depends == [] From 9d5b4a7cd933b0e2a132194def62d4daa39e618d Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Mon, 23 Mar 2026 18:11:50 +0100 Subject: [PATCH 24/28] android: use plain 'build' dependency (using 'venv') instead of 'build[virtualenv]', remove setuptools as its use is now pinned via hostpython_prerequisites where applicable, update depends asserts in pyqt6sip, sip, pyqt_builder --- contrib/android/Dockerfile | 2 +- .../p4a_recipes/hostpython3/__init__.py | 14 ++------------ .../android/p4a_recipes/packaging/__init__.py | 6 ++---- .../p4a_recipes/pycryptodomex/__init__.py | 8 ++++++-- .../android/p4a_recipes/pyqt6sip/__init__.py | 2 +- .../p4a_recipes/pyqt_builder/__init__.py | 2 +- .../p4a_recipes/setuptools/__init__.py | 19 ------------------- contrib/android/p4a_recipes/sip/__init__.py | 2 +- 8 files changed, 14 insertions(+), 41 deletions(-) delete mode 100644 contrib/android/p4a_recipes/setuptools/__init__.py diff --git a/contrib/android/Dockerfile b/contrib/android/Dockerfile index 8a136c28e..6e43b83f2 100644 --- a/contrib/android/Dockerfile +++ b/contrib/android/Dockerfile @@ -235,7 +235,7 @@ RUN cd /opt \ && /opt/venv/bin/python3 -m pip install --no-build-isolation --no-dependencies -e . # install python-for-android -ENV P4A_CHECKOUT_COMMIT="0b9f7e763866cd3533417418d4b31e7b316b624e" +ENV P4A_CHECKOUT_COMMIT="43dfdfaec0865fbd9c7d4f57457323601c4afae1" # ^ from branch electrum_202602 (note: careful with force-pushing! see #8162) RUN cd /opt \ && git clone https://github.com/spesmilo/python-for-android \ diff --git a/contrib/android/p4a_recipes/hostpython3/__init__.py b/contrib/android/p4a_recipes/hostpython3/__init__.py index 5d9f0d6eb..a4a63d7bd 100644 --- a/contrib/android/p4a_recipes/hostpython3/__init__.py +++ b/contrib/android/p4a_recipes/hostpython3/__init__.py @@ -17,28 +17,18 @@ class HostPython3RecipePinned(util.InheritedRecipeMixin, HostPython3Recipe): # this property overrides the default hostpython dependencies for PyProjectRecipe recipies pyproject_base_dependencies = [ - HashPinnedDependency(package="build[virtualenv]==1.4.0", + HashPinnedDependency(package="build==1.4.0", hashes=['sha256:6a07c1b8eb6f2b311b96fcbdbce5dab5fe637ffda0fd83c9cac622e927501596']), HashPinnedDependency(package="pip==24.0", hashes=['sha256:ba0d021a166865d2265246961bec0152ff124de910c5cc39f1156ce3fa7c69dc']), HashPinnedDependency(package="setuptools==80.9.0", hashes=['sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922']), - # pin deptree build[virtualenv]==1.4.0 + # pin deptree build==1.4.0 HashPinnedDependency(package="packaging==26.0", hashes=['sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529']), HashPinnedDependency(package="pyproject_hooks==1.2.0", hashes=['sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913']), - HashPinnedDependency(package="virtualenv==21.2.0", - hashes=['sha256:1bd755b504931164a5a496d217c014d098426cddc79363ad66ac78125f9d908f']), - HashPinnedDependency(package="distlib==0.4.0", - hashes=['sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16']), - HashPinnedDependency(package="filelock==3.25.2", - hashes=['sha256:ca8afb0da15f229774c9ad1b455ed96e85a81373065fb10446672f64444ddf70']), - HashPinnedDependency(package="platformdirs==4.9.4", - hashes=['sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868']), - HashPinnedDependency(package="python_discovery==1.1.3", - hashes=['sha256:90e795f0121bc84572e737c9aa9966311b9fde44ffb88a5953b3ec9b31c6945e']), ] diff --git a/contrib/android/p4a_recipes/packaging/__init__.py b/contrib/android/p4a_recipes/packaging/__init__.py index a8f225ae2..b16d67d47 100644 --- a/contrib/android/p4a_recipes/packaging/__init__.py +++ b/contrib/android/p4a_recipes/packaging/__init__.py @@ -1,15 +1,13 @@ from pythonforandroid.recipes.packaging import PackagingRecipe -assert PackagingRecipe._version == "21.3" +assert PackagingRecipe._version == "26.0" assert PackagingRecipe.depends == ["setuptools", "pyparsing", "python3"] assert PackagingRecipe.python_depends == [] class PackagingRecipePinned(PackagingRecipe): - #version = "21.3" - # note: 21.3 is the last version to use setup.py, so newer versions don't work. see comment for PyparsingRecipePinned - sha512sum = "2e3aa276a4229ac7dc0654d586799473ced9761a83aa4159660d37ae1a2a8f30e987248dd0e260e2834106b589f259a57ce9936eef0dcc3c430a99ac6b663e05" + sha512sum = "27a066a7d65ba76189212973b6a0d162f3d361848b1b0c34a82865cf180b3284a837cc34206c297f002a73feae414e25a26c5960bb884a74ea337f582585f1d2" recipe = PackagingRecipePinned() diff --git a/contrib/android/p4a_recipes/pycryptodomex/__init__.py b/contrib/android/p4a_recipes/pycryptodomex/__init__.py index 0e0a580bf..0491968e5 100644 --- a/contrib/android/p4a_recipes/pycryptodomex/__init__.py +++ b/contrib/android/p4a_recipes/pycryptodomex/__init__.py @@ -1,5 +1,5 @@ from pythonforandroid.recipe import PythonRecipe - +from pythonforandroid.util import HashPinnedDependency assert PythonRecipe.depends == ['python3'] assert PythonRecipe.python_depends == [] @@ -9,7 +9,11 @@ class PycryptodomexRecipe(PythonRecipe): version = "3.23.0" sha512sum = "951cebaad2e19b9f9d04fe85c73ab1ff8b515069c1e0e8e3cd6845ec9ccd5ef3e5737259e0934ed4a6536e289dee6aabac58e1c822a5a6393e86b482c60afc89" url = "https://github.com/Legrandin/pycryptodome/archive/v{version}x.tar.gz" - depends = ["setuptools", "cffi"] + depends = ["cffi"] + hostpython_prerequisites = [ + HashPinnedDependency(package="setuptools==80.9.0", + hashes=['sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922']), + ] recipe = PycryptodomexRecipe() diff --git a/contrib/android/p4a_recipes/pyqt6sip/__init__.py b/contrib/android/p4a_recipes/pyqt6sip/__init__.py index 6c354250e..9aa70d616 100644 --- a/contrib/android/p4a_recipes/pyqt6sip/__init__.py +++ b/contrib/android/p4a_recipes/pyqt6sip/__init__.py @@ -7,7 +7,7 @@ util = load_source('util', os.path.join(os.path.dirname(os.path.dirname(__file__ assert PyQt6SipRecipe._version == "13.10.3" -assert PyQt6SipRecipe.depends == ['setuptools', 'python3'] +assert PyQt6SipRecipe.depends == ['python3'] assert PyQt6SipRecipe.python_depends == [] diff --git a/contrib/android/p4a_recipes/pyqt_builder/__init__.py b/contrib/android/p4a_recipes/pyqt_builder/__init__.py index 3849943fe..8fa65576d 100644 --- a/contrib/android/p4a_recipes/pyqt_builder/__init__.py +++ b/contrib/android/p4a_recipes/pyqt_builder/__init__.py @@ -3,7 +3,7 @@ from pythonforandroid.util import HashPinnedDependency assert PyQtBuilderRecipe._version == "1.19.1" -assert PyQtBuilderRecipe.depends == ["packaging", "sip", "python3"], PyQtBuilderRecipe.depends +assert PyQtBuilderRecipe.depends == ["sip", "python3"], PyQtBuilderRecipe.depends assert PyQtBuilderRecipe.python_depends == [] diff --git a/contrib/android/p4a_recipes/setuptools/__init__.py b/contrib/android/p4a_recipes/setuptools/__init__.py deleted file mode 100644 index d60d39043..000000000 --- a/contrib/android/p4a_recipes/setuptools/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -from pythonforandroid.recipes.setuptools import SetuptoolsRecipe -from pythonforandroid.util import HashPinnedDependency - - -assert SetuptoolsRecipe._version == "80.9.0" -assert SetuptoolsRecipe.depends == ['python3'] -assert SetuptoolsRecipe.python_depends == [] - - -class SetuptoolsRecipePinned(SetuptoolsRecipe): - hostpython_prerequisites = [ - HashPinnedDependency(package='setuptools==80.9.0', - hashes=[]), - ] - - sha512sum = "36eb1f219d29c6b9e135936bde2001ad70a971c8069cd0175d3a5325b450e6843a903d3f70043c9f534768ebeab8ab0c544b8f44456555d333f1ed72daa5c18b" - - -recipe = SetuptoolsRecipePinned() diff --git a/contrib/android/p4a_recipes/sip/__init__.py b/contrib/android/p4a_recipes/sip/__init__.py index 456f707dc..c603c516a 100644 --- a/contrib/android/p4a_recipes/sip/__init__.py +++ b/contrib/android/p4a_recipes/sip/__init__.py @@ -2,7 +2,7 @@ from pythonforandroid.recipes.sip import SipRecipe assert SipRecipe._version == "6.15.1" -assert SipRecipe.depends == ["setuptools", "packaging", "python3"], SipRecipe.depends +assert SipRecipe.depends == ["python3"], SipRecipe.depends assert SipRecipe.python_depends == [] From 7b7d7028bdd207ac049ce164352a825c18134c64 Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Tue, 24 Mar 2026 09:12:40 +0100 Subject: [PATCH 25/28] android: hash-pin hostpython prerequisites for pyqt6sip and sip --- contrib/android/p4a_recipes/pyqt6sip/__init__.py | 9 ++++++++- contrib/android/p4a_recipes/sip/__init__.py | 7 ++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/contrib/android/p4a_recipes/pyqt6sip/__init__.py b/contrib/android/p4a_recipes/pyqt6sip/__init__.py index 9aa70d616..dea22376f 100644 --- a/contrib/android/p4a_recipes/pyqt6sip/__init__.py +++ b/contrib/android/p4a_recipes/pyqt6sip/__init__.py @@ -1,7 +1,7 @@ import os from pythonforandroid.recipes.pyqt6sip import PyQt6SipRecipe -from pythonforandroid.util import load_source +from pythonforandroid.util import load_source, HashPinnedDependency util = load_source('util', os.path.join(os.path.dirname(os.path.dirname(__file__)), 'util.py')) @@ -14,5 +14,12 @@ assert PyQt6SipRecipe.python_depends == [] class PyQt6SipRecipePinned(util.InheritedRecipeMixin, PyQt6SipRecipe): sha512sum = "555b061eec3db6a66388fae07de21f58d756f6f12b13e4ede729c3348d2c8997ac5a59d3006ee45c3a09b5cde673f579265fa254bc583a4ba721748cf8f3a617" + hostpython_prerequisites = [ + HashPinnedDependency(package="setuptools==80.9.0", + hashes=['sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922']), + HashPinnedDependency(package="packaging==26.0", + hashes=['sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529']), + ] + recipe = PyQt6SipRecipePinned() diff --git a/contrib/android/p4a_recipes/sip/__init__.py b/contrib/android/p4a_recipes/sip/__init__.py index c603c516a..af6fdffb4 100644 --- a/contrib/android/p4a_recipes/sip/__init__.py +++ b/contrib/android/p4a_recipes/sip/__init__.py @@ -1,5 +1,5 @@ from pythonforandroid.recipes.sip import SipRecipe - +from pythonforandroid.util import HashPinnedDependency assert SipRecipe._version == "6.15.1" assert SipRecipe.depends == ["python3"], SipRecipe.depends @@ -9,5 +9,10 @@ assert SipRecipe.python_depends == [] class SipRecipePinned(SipRecipe): sha512sum = "30a312419ba82c0221c0cf03c3fb3ad7d45bb8fe633d1d7477025a7986b0a7f7b7b781a8d9cd6bcdb78f3b872231fd1eed123a761b497861822f2e35093f574d" + hostpython_prerequisites = [ + HashPinnedDependency(package="setuptools==80.9.0", + hashes=['sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922']), + ] + recipe = SipRecipePinned() From 29b5e1672b12583ef1e5a01c9bb2bd03cd9e3a21 Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Wed, 22 Apr 2026 11:17:30 +0200 Subject: [PATCH 26/28] p4a ref 1098be6964cfc2156959e435e81c2c50f8398586 --- contrib/android/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/android/Dockerfile b/contrib/android/Dockerfile index 6e43b83f2..73493413b 100644 --- a/contrib/android/Dockerfile +++ b/contrib/android/Dockerfile @@ -235,7 +235,7 @@ RUN cd /opt \ && /opt/venv/bin/python3 -m pip install --no-build-isolation --no-dependencies -e . # install python-for-android -ENV P4A_CHECKOUT_COMMIT="43dfdfaec0865fbd9c7d4f57457323601c4afae1" +ENV P4A_CHECKOUT_COMMIT="1098be6964cfc2156959e435e81c2c50f8398586" # ^ from branch electrum_202602 (note: careful with force-pushing! see #8162) RUN cd /opt \ && git clone https://github.com/spesmilo/python-for-android \ From 83b6770021e30f112d148dc55a14cf647070fe05 Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Wed, 22 Apr 2026 11:26:06 +0200 Subject: [PATCH 27/28] android: remove unneeded dl-ndk-ci.sh --- contrib/android/dl-ndk-ci.sh | 8 -------- 1 file changed, 8 deletions(-) delete mode 100755 contrib/android/dl-ndk-ci.sh diff --git a/contrib/android/dl-ndk-ci.sh b/contrib/android/dl-ndk-ci.sh deleted file mode 100755 index 41b1922f2..000000000 --- a/contrib/android/dl-ndk-ci.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh -if [ -z "$1" ]; then - echo "missing url" - exit 1 -fi -echo $1 - -curl $1 | grep "var JSVariables" | python3 -c "import sys; line=sys.stdin.read(); line=line[line.find('{'):-2]; import json; j=json.loads(line); print(j['artifactUrl'])" | wget -i - -O android-ndk-ci-linux-x86_64.zip From 96a3345ab5a3c41c89b4b5bac2d8bde12d753392 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 24 Apr 2026 16:48:34 +0000 Subject: [PATCH 28/28] setup.py: "qml_gui" extra: update pyqt version --- contrib/android/Readme.md | 2 -- setup.py | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/contrib/android/Readme.md b/contrib/android/Readme.md index e9ccf879b..6e348effd 100644 --- a/contrib/android/Readme.md +++ b/contrib/android/Readme.md @@ -108,8 +108,6 @@ Run electrum with the `-g` switch: `electrum -g qml` Notes: -- pyqt ~6.4 would work best, as the gui has not yet been adapted to styling changes in 6.5 -- However, pyqt6 as distributed on PyPI does not include a required module (PyQt6.QtQml) until 6.5 - Installing these deps from your OS package manager should also work, except many don't distribute pyqt6 yet. For pyqt5 on debian-based distros, this used to look like this: diff --git a/setup.py b/setup.py index ba15de1ba..99cb955e6 100755 --- a/setup.py +++ b/setup.py @@ -45,7 +45,7 @@ extras_require = { 'gui': ['pyqt6'], 'crypto': ['cryptography>=2.6'], 'tests': ['pycryptodomex>=3.7', 'cryptography>=2.6', 'pyaes>=0.1a1'], - 'qml_gui': ['pyqt6<6.6', 'pyqt6-qt6<6.6'] + 'qml_gui': ['pyqt6~=6.10', 'pyqt6-qt6~=6.10'], # should be same-ish version as Android build uses? } # 'full' extra that tries to grab everything an enduser would need (except for libsecp256k1...) extras_require['full'] = [pkg for sublist in